Merge pull request #4804 from mempool/nymkappa/prepaid-acceleration
[accelerator] prepaid acceleration
This commit is contained in:
commit
10a41fb0d1
@ -206,7 +206,7 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- LOGIN CTA -->
|
<!-- LOGIN CTA -->
|
||||||
<ng-container *ngIf="stateService.isMempoolSpaceBuild && !isLoggedIn()">
|
<ng-container *ngIf="stateService.isMempoolSpaceBuild && !isLoggedIn() && paymentType === 'bitcoin'">
|
||||||
<tr class="group-first group-last" style="border-top: 1px dashed grey">
|
<tr class="group-first group-last" style="border-top: 1px dashed grey">
|
||||||
<td class="item"></td>
|
<td class="item"></td>
|
||||||
<td class="amt"></td>
|
<td class="amt"></td>
|
||||||
@ -229,7 +229,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3" *ngIf="isLoggedIn()">
|
<div class="row mb-3" *ngIf="isLoggedIn() && paymentType === 'bitcoin'">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="d-flex justify-content-end" *ngIf="user && estimate.hasAccess">
|
<div class="d-flex justify-content-end" *ngIf="user && estimate.hasAccess">
|
||||||
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()" i18n="transaction.accelerate|Accelerate button label">Accelerate</button>
|
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()" i18n="transaction.accelerate|Accelerate button label">Accelerate</button>
|
||||||
@ -237,6 +237,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row d-flex justify-content-end mr-1" style="height: 48px" *ngIf="!hideCashApp">
|
||||||
|
<div id="cash-app-pay" style="max-width: 320px" [style]="showSpinner ? 'opacity: 0' : 'opacity: 1'"></div>
|
||||||
|
<div *ngIf="showSpinner" class="d-flex align-items-center">
|
||||||
|
<span class="mr-2">Loading</span>
|
||||||
|
<div class="spinner-border text-light" style="width: 25px; height: 25px"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core';
|
||||||
import { ApiService } from '../../services/api.service';
|
|
||||||
import { Subscription, catchError, of, tap } from 'rxjs';
|
import { Subscription, catchError, of, tap } from 'rxjs';
|
||||||
import { StorageService } from '../../services/storage.service';
|
import { StorageService } from '../../services/storage.service';
|
||||||
import { Transaction } from '../../interfaces/electrs.interface';
|
import { Transaction } from '../../interfaces/electrs.interface';
|
||||||
@ -40,7 +39,7 @@ export const MAX_BID_RATIO = 4;
|
|||||||
templateUrl: 'accelerate-preview.component.html',
|
templateUrl: 'accelerate-preview.component.html',
|
||||||
styleUrls: ['accelerate-preview.component.scss']
|
styleUrls: ['accelerate-preview.component.scss']
|
||||||
})
|
})
|
||||||
export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges {
|
export class AcceleratePreviewComponent implements OnDestroy, OnChanges {
|
||||||
@Input() tx: Transaction | undefined;
|
@Input() tx: Transaction | undefined;
|
||||||
@Input() scrollEvent: boolean;
|
@Input() scrollEvent: boolean;
|
||||||
|
|
||||||
@ -63,18 +62,38 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
|
|
||||||
maxRateOptions: RateOption[] = [];
|
maxRateOptions: RateOption[] = [];
|
||||||
|
|
||||||
|
// Cashapp payment
|
||||||
|
paymentType: 'bitcoin' | 'cashapp' = 'bitcoin';
|
||||||
|
cashAppSubscription: Subscription;
|
||||||
|
conversionsSubscription: Subscription;
|
||||||
|
payments: any;
|
||||||
|
showSpinner = false;
|
||||||
|
square: any;
|
||||||
|
cashAppPay: any;
|
||||||
|
hideCashApp = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
private servicesApiService: ServicesApiServices,
|
private servicesApiService: ServicesApiServices,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private audioService: AudioService,
|
private audioService: AudioService,
|
||||||
private cd: ChangeDetectorRef
|
private cd: ChangeDetectorRef
|
||||||
) { }
|
) {
|
||||||
|
if (window.document.referrer === 'cash.app') {
|
||||||
|
this.insertSquare();
|
||||||
|
this.paymentType = 'cashapp';
|
||||||
|
} else {
|
||||||
|
this.paymentType = 'bitcoin';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
if (this.estimateSubscription) {
|
if (this.estimateSubscription) {
|
||||||
this.estimateSubscription.unsubscribe();
|
this.estimateSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
if (this.cashAppPay) {
|
||||||
|
this.cashAppPay.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
@ -83,9 +102,16 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngAfterViewInit() {
|
||||||
|
this.showSpinner = true;
|
||||||
|
|
||||||
this.user = this.storageService.getAuth()?.user ?? null;
|
this.user = this.storageService.getAuth()?.user ?? null;
|
||||||
|
|
||||||
|
this.servicesApiService.setupSquare$().subscribe(ids => {
|
||||||
|
this.square = {
|
||||||
|
appId: ids.squareAppId,
|
||||||
|
locationId: ids.squareLocationId
|
||||||
|
};
|
||||||
this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe(
|
this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe(
|
||||||
tap((response) => {
|
tap((response) => {
|
||||||
if (response.status === 204) {
|
if (response.status === 204) {
|
||||||
@ -101,6 +127,11 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
this.estimateSubscription.unsubscribe();
|
this.estimateSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.paymentType === 'cashapp') {
|
||||||
|
this.estimate.userBalance = 999999999;
|
||||||
|
this.estimate.enoughBalance = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) {
|
if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) {
|
||||||
if (this.isLoggedIn()) {
|
if (this.isLoggedIn()) {
|
||||||
this.error = `not_enough_balance`;
|
this.error = `not_enough_balance`;
|
||||||
@ -135,6 +166,9 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
|
|
||||||
if (!this.error) {
|
if (!this.error) {
|
||||||
this.scrollToPreview('acceleratePreviewAnchor', 'start');
|
this.scrollToPreview('acceleratePreviewAnchor', 'start');
|
||||||
|
if (this.paymentType === 'cashapp') {
|
||||||
|
this.setupSquare();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -146,6 +180,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
return of(null);
|
return of(null);
|
||||||
})
|
})
|
||||||
).subscribe();
|
).subscribe();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -216,4 +251,112 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
onResize(): void {
|
onResize(): void {
|
||||||
this.isMobile = window.innerWidth <= 767.98;
|
this.isMobile = window.innerWidth <= 767.98;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CashApp payment
|
||||||
|
*/
|
||||||
|
setupSquare() {
|
||||||
|
const init = () => {
|
||||||
|
this.initSquare();
|
||||||
|
};
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
if (!window.Square) {
|
||||||
|
console.warn('Square.js failed to load properly. Retrying in 1 second.');
|
||||||
|
setTimeout(init, 1000);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async initSquare(): Promise<void> {
|
||||||
|
try {
|
||||||
|
//@ts-ignore
|
||||||
|
this.payments = window.Square.payments(this.square.appId, this.square.locationId)
|
||||||
|
await this.requestCashAppPayment();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
this.error = 'Error loading Square Payments';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestCashAppPayment() {
|
||||||
|
if (this.cashAppSubscription) {
|
||||||
|
this.cashAppSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
if (this.conversionsSubscription) {
|
||||||
|
this.conversionsSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
this.hideCashApp = false;
|
||||||
|
|
||||||
|
|
||||||
|
this.conversionsSubscription = this.stateService.conversions$.subscribe(
|
||||||
|
async (conversions) => {
|
||||||
|
const maxCostUsd = this.maxCost / 100_000_000 * conversions.USD;
|
||||||
|
const paymentRequest = this.payments.paymentRequest({
|
||||||
|
countryCode: 'US',
|
||||||
|
currencyCode: 'USD',
|
||||||
|
total: {
|
||||||
|
amount: maxCostUsd.toString(),
|
||||||
|
label: 'Total',
|
||||||
|
pending: true,
|
||||||
|
productUrl: `https://mempool.space/tx/${this.tx.txid}`,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.cashAppPay = await this.payments.cashAppPay(paymentRequest, {
|
||||||
|
redirectURL: `https://mempool.space/tx/${this.tx.txid}`,
|
||||||
|
referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||||
|
});
|
||||||
|
await this.cashAppPay.attach('#cash-app-pay');
|
||||||
|
this.showSpinner = false;
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
this.cashAppPay.addEventListener('ontokenization', function (event) {
|
||||||
|
const { tokenResult, error } = event.detail;
|
||||||
|
if (error) {
|
||||||
|
this.error = error;
|
||||||
|
} else if (tokenResult.status === 'OK') {
|
||||||
|
that.hideCashApp = true;
|
||||||
|
|
||||||
|
that.accelerationSubscription = that.servicesApiService.accelerateWithCashApp$(
|
||||||
|
that.tx.txid,
|
||||||
|
that.userBid,
|
||||||
|
tokenResult.token,
|
||||||
|
tokenResult.details.cashAppPay.cashtag,
|
||||||
|
tokenResult.details.cashAppPay.referenceId
|
||||||
|
).subscribe({
|
||||||
|
next: () => {
|
||||||
|
that.audioService.playSound('ascend-chime-cartoon');
|
||||||
|
that.showSuccess = true;
|
||||||
|
that.scrollToPreviewWithTimeout('successAlert', 'center');
|
||||||
|
that.estimateSubscription.unsubscribe();
|
||||||
|
},
|
||||||
|
error: (response) => {
|
||||||
|
if (response.status === 403 && response.error === 'not_available') {
|
||||||
|
that.error = 'waitlisted';
|
||||||
|
} else {
|
||||||
|
that.error = response.error;
|
||||||
|
}
|
||||||
|
that.scrollToPreviewWithTimeout('mempoolError', 'center');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
insertSquare(): void {
|
||||||
|
let statsUrl = 'https://sandbox.web.squarecdn.com/v1/square.js';
|
||||||
|
if (document.location.hostname === 'mempool-staging.tk7.mempool.space' || document.location.hostname === 'mempool.space') {
|
||||||
|
statsUrl = 'https://web.squarecdn.com/v1/square.js';
|
||||||
|
}
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
const d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||||
|
// @ts-ignore
|
||||||
|
g.type='text/javascript'; g.src=statsUrl; s.parentNode.insertBefore(g, s);
|
||||||
|
})();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import { EnterpriseService } from '../../services/enterprise.service';
|
|||||||
import { NavigationService } from '../../services/navigation.service';
|
import { NavigationService } from '../../services/navigation.service';
|
||||||
import { MenuComponent } from '../menu/menu.component';
|
import { MenuComponent } from '../menu/menu.component';
|
||||||
import { StorageService } from '../../services/storage.service';
|
import { StorageService } from '../../services/storage.service';
|
||||||
import { ApiService } from '../../services/api.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-master-page',
|
selector: 'app-master-page',
|
||||||
@ -45,7 +44,6 @@ export class MasterPageComponent implements OnInit, OnDestroy {
|
|||||||
private enterpriseService: EnterpriseService,
|
private enterpriseService: EnterpriseService,
|
||||||
private navigationService: NavigationService,
|
private navigationService: NavigationService,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private apiService: ApiService,
|
|
||||||
private router: Router,
|
private router: Router,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
@ -132,6 +132,10 @@ export class ServicesApiServices {
|
|||||||
return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate`, { txInput: txInput, userBid: userBid });
|
return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate`, { txInput: txInput, userBid: userBid });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accelerateWithCashApp$(txInput: string, userBid: number, token: string, cashtag: string, referenceId: string) {
|
||||||
|
return this.httpClient.post<any>(`${SERVICES_API_PREFIX}/accelerator/accelerate/cashapp`, { txInput: txInput, userBid: userBid, token: token, cashtag: cashtag, referenceId: referenceId });
|
||||||
|
}
|
||||||
|
|
||||||
getAccelerations$(): Observable<Acceleration[]> {
|
getAccelerations$(): Observable<Acceleration[]> {
|
||||||
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations`);
|
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations`);
|
||||||
}
|
}
|
||||||
@ -151,4 +155,8 @@ export class ServicesApiServices {
|
|||||||
getAccelerationStats$(): Observable<AccelerationStats> {
|
getAccelerationStats$(): Observable<AccelerationStats> {
|
||||||
return this.httpClient.get<AccelerationStats>(`${SERVICES_API_PREFIX}/accelerator/accelerations/stats`);
|
return this.httpClient.get<AccelerationStats>(`${SERVICES_API_PREFIX}/accelerator/accelerations/stats`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupSquare$(): Observable<{squareAppId: string, squareLocationId: string}> {
|
||||||
|
return this.httpClient.get<{squareAppId: string, squareLocationId: string}>(`${SERVICES_API_PREFIX}/square/setup`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user