[accelerator] add support for acceleration with apple pay
This commit is contained in:
@@ -395,12 +395,16 @@
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (canPayWithCashapp) {
|
||||
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center">
|
||||
<p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container> <app-fiat [value]="cost"></app-fiat> with</p>
|
||||
<img class="paymentMethod mx-2" src="/resources/cash-app.svg" height=55 (click)="moveToStep('cashapp')">
|
||||
</div>
|
||||
}
|
||||
<div class="col-sm text-center d-flex flex-column justify-content-center align-items-center" style="padding-bottom: 50px">
|
||||
<p><ng-container i18n="transaction.pay|Pay button label">Pay</ng-container> <app-fiat [value]="cost"></app-fiat> with</p>
|
||||
@if (canPayWithCashapp) {
|
||||
<img class="paymentMethod mx-2" style="width: 200px" src="/resources/cash-app.svg" height=55 (click)="moveToStep('cashapp')">
|
||||
}
|
||||
@if (canPayWithApplePay) {
|
||||
@if (canPayWithCashapp) { <hr class="w-25 mt-2 mb-2"> }
|
||||
<img style="cursor: pointer;" src="/resources/apple-pay.svg" height=55 (click)="moveToStep('applepay')">
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -421,7 +425,7 @@
|
||||
<button type="button" class="mt-1 btn btn-secondary btn-sm rounded-pill align-self-center" style="width: 200px" (click)="moveToStep('summary')" i18n="go-back">Go back</button>
|
||||
</div>
|
||||
</div>
|
||||
} @else if (step === 'cashapp') {
|
||||
} @else if (step === 'cashapp' || step === 'applepay') {
|
||||
<!-- Show checkout page -->
|
||||
<div class="row mb-md-1 text-center">
|
||||
<div class="col-sm" id="confirm-payment-title">
|
||||
@@ -437,7 +441,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!loadingCashapp) {
|
||||
@if (step === 'cashapp' && !loadingCashapp || step === 'applepay' && !loadingApplePay) {
|
||||
<div class="row text-center mt-1">
|
||||
<div class="col-sm">
|
||||
<div class="form-group w-100">
|
||||
@@ -456,8 +460,12 @@
|
||||
<div class="row text-center mt-1">
|
||||
<div class="col-sm">
|
||||
<div class="form-group w-100">
|
||||
<div id="cash-app-pay" class="d-inline-block" [style]="loadingCashapp ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
|
||||
@if (loadingCashapp) {
|
||||
@if (step === 'applepay') {
|
||||
<div id="apple-pay-button" class="apple-pay-button apple-pay-button-white" [style]="loadingApplePay ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
|
||||
} @else if (step === 'cashapp') {
|
||||
<div id="cash-app-pay" class="d-inline-block" [style]="loadingCashapp ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
|
||||
}
|
||||
@if (loadingCashapp || loadingApplePay) {
|
||||
<div display="d-flex flex-row justify-content-center">
|
||||
<span i18n="accelerator.loading-payment-method">Loading payment method...</span>
|
||||
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
.paymentMethod {
|
||||
padding: 10px;
|
||||
background-color: var(--secondary);
|
||||
border-radius: 15px;
|
||||
border: 2px solid var(--bg);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -202,4 +201,19 @@
|
||||
|
||||
.btn-error-wrapper {
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.apple-pay-button {
|
||||
display: inline-block;
|
||||
-webkit-appearance: -apple-pay-button;
|
||||
-apple-pay-button-type: plain; /* Use any supported button type. */
|
||||
}
|
||||
.apple-pay-button-black {
|
||||
-apple-pay-button-style: black;
|
||||
}
|
||||
.apple-pay-button-white {
|
||||
-apple-pay-button-style: white;
|
||||
}
|
||||
.apple-pay-button-white-with-line {
|
||||
-apple-pay-button-style: white-outline;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef, SimpleChanges, HostListener } from '@angular/core';
|
||||
import { Subscription, tap, of, catchError, Observable, switchMap } from 'rxjs';
|
||||
import { ServicesApiServices } from '../../services/services-api.service';
|
||||
import { nextRoundNumber } from '../../shared/common.utils';
|
||||
import { md5, nextRoundNumber } from '../../shared/common.utils';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { AudioService } from '../../services/audio.service';
|
||||
import { ETA, EtaService } from '../../services/eta.service';
|
||||
@@ -46,7 +46,7 @@ export const MIN_BID_RATIO = 1;
|
||||
export const DEFAULT_BID_RATIO = 2;
|
||||
export const MAX_BID_RATIO = 4;
|
||||
|
||||
type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'processing' | 'paid' | 'success';
|
||||
type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'applepay' | 'processing' | 'paid' | 'success';
|
||||
|
||||
@Component({
|
||||
selector: 'app-accelerate-checkout',
|
||||
@@ -60,6 +60,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
@Input() eta: ETA;
|
||||
@Input() scrollEvent: boolean;
|
||||
@Input() cashappEnabled: boolean = true;
|
||||
@Input() applePayEnabled: boolean = true;
|
||||
@Input() advancedEnabled: boolean = false;
|
||||
@Input() forceMobile: boolean = false;
|
||||
@Input() showDetails: boolean = false;
|
||||
@@ -109,11 +110,12 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
|
||||
// square
|
||||
loadingCashapp = false;
|
||||
loadingApplePay = false;
|
||||
cashappError = false;
|
||||
cashappSubmit: any;
|
||||
payments: any;
|
||||
cashAppPay: any;
|
||||
cashAppSubscription: Subscription;
|
||||
applePay: any;
|
||||
conversionsSubscription: Subscription;
|
||||
conversions: any;
|
||||
|
||||
@@ -212,6 +214,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
this.loadingCashapp = true;
|
||||
this.insertSquare();
|
||||
this.setupSquare();
|
||||
} else if (this._step === 'applepay' && this.applePayEnabled) {
|
||||
this.loadingApplePay = true;
|
||||
this.insertSquare();
|
||||
this.setupSquare();
|
||||
} else if (this._step === 'paid') {
|
||||
this.timePaid = Date.now();
|
||||
this.timeoutTimer = setTimeout(() => {
|
||||
@@ -229,8 +235,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to element id with or without setTimeout
|
||||
*/
|
||||
* Scroll to element id with or without setTimeout
|
||||
*/
|
||||
scrollToElementWithTimeout(id: string, position: ScrollLogicalPosition, timeout: number = 1000): void {
|
||||
setTimeout(() => {
|
||||
this.scrollToElement(id, position);
|
||||
@@ -421,17 +427,112 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
try {
|
||||
//@ts-ignore
|
||||
this.payments = window.Square.payments(this.square.appId, this.square.locationId)
|
||||
await this.requestCashAppPayment();
|
||||
if (this._step === 'cashapp') {
|
||||
await this.requestCashAppPayment();
|
||||
} else if (this._step === 'applepay') {
|
||||
await this.requestApplePayPayment();
|
||||
}
|
||||
} catch (e) {
|
||||
console.debug('Error loading Square Payments', e);
|
||||
this.cashappError = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
async requestCashAppPayment() {
|
||||
if (this.cashAppSubscription) {
|
||||
this.cashAppSubscription.unsubscribe();
|
||||
|
||||
/**
|
||||
* APPLE PAY
|
||||
*/
|
||||
async requestApplePayPayment() {
|
||||
if (this.conversionsSubscription) {
|
||||
this.conversionsSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
this.conversionsSubscription = this.stateService.conversions$.subscribe(
|
||||
async (conversions) => {
|
||||
this.conversions = conversions;
|
||||
if (this.applePay) {
|
||||
this.applePay.destroy();
|
||||
}
|
||||
|
||||
const costUSD = this.cost / 100_000_000 * conversions.USD;
|
||||
const paymentRequest = this.payments.paymentRequest({
|
||||
countryCode: 'US',
|
||||
currencyCode: 'USD',
|
||||
total: {
|
||||
amount: costUSD.toFixed(2),
|
||||
label: 'Total',
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
this.applePay = await this.payments.applePay(paymentRequest);
|
||||
const applePayButton = document.getElementById('apple-pay-button');
|
||||
if (!applePayButton) {
|
||||
console.error(`Unable to find apple pay button id='apple-pay-button'`);
|
||||
// Try again
|
||||
setTimeout(this.requestApplePayPayment.bind(this), 500);
|
||||
return;
|
||||
}
|
||||
this.loadingApplePay = false;
|
||||
applePayButton.addEventListener('click', async event => {
|
||||
event.preventDefault();
|
||||
const tokenResult = await this.applePay.tokenize();
|
||||
if (tokenResult?.status === 'OK') {
|
||||
const card = tokenResult.details?.card;
|
||||
if (!card || !card.brand || !card.expMonth || !card.expYear || !card.last4) {
|
||||
console.error(`Cannot retreive payment card details`);
|
||||
this.accelerateError = 'apple_pay_no_card_details';
|
||||
return;
|
||||
}
|
||||
const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase());
|
||||
this.servicesApiService.accelerateWithApplePay$(
|
||||
this.tx.txid,
|
||||
tokenResult.token,
|
||||
cardTag,
|
||||
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||
this.accelerationUUID
|
||||
).subscribe({
|
||||
next: () => {
|
||||
this.audioService.playSound('ascend-chime-cartoon');
|
||||
if (this.applePay) {
|
||||
this.applePay.destroy();
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.moveToStep('paid');
|
||||
}, 1000);
|
||||
},
|
||||
error: (response) => {
|
||||
this.accelerateError = response.error;
|
||||
if (!(response.status === 403 && response.error === 'not_available')) {
|
||||
setTimeout(() => {
|
||||
// Reset everything by reloading the page :D, can be improved
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
window.location.assign(window.location.toString().replace(`?cash_request_id=${urlParams.get('cash_request_id')}`, ``));
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let errorMessage = `Tokenization failed with status: ${tokenResult.status}`;
|
||||
if (tokenResult.errors) {
|
||||
errorMessage += ` and errors: ${JSON.stringify(
|
||||
tokenResult.errors,
|
||||
)}`;
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* CASHAPP
|
||||
*/
|
||||
async requestCashAppPayment() {
|
||||
if (this.conversionsSubscription) {
|
||||
this.conversionsSubscription.unsubscribe();
|
||||
}
|
||||
@@ -449,7 +550,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
countryCode: 'US',
|
||||
currencyCode: 'USD',
|
||||
total: {
|
||||
amount: costUSD.toString(),
|
||||
amount: costUSD.toFixed(2),
|
||||
label: 'Total',
|
||||
pending: true,
|
||||
productUrl: `${redirectHostname}/tracker/${this.tx.txid}`,
|
||||
@@ -467,23 +568,22 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
}
|
||||
this.loadingCashapp = false;
|
||||
|
||||
const that = this;
|
||||
this.cashAppPay.addEventListener('ontokenization', function (event) {
|
||||
this.cashAppPay.addEventListener('ontokenization', event => {
|
||||
const { tokenResult, error } = event.detail;
|
||||
if (error) {
|
||||
this.accelerateError = error;
|
||||
} else if (tokenResult.status === 'OK') {
|
||||
that.servicesApiService.accelerateWithCashApp$(
|
||||
that.tx.txid,
|
||||
this.servicesApiService.accelerateWithCashApp$(
|
||||
this.tx.txid,
|
||||
tokenResult.token,
|
||||
tokenResult.details.cashAppPay.cashtag,
|
||||
tokenResult.details.cashAppPay.referenceId,
|
||||
that.accelerationUUID
|
||||
this.accelerationUUID
|
||||
).subscribe({
|
||||
next: () => {
|
||||
that.audioService.playSound('ascend-chime-cartoon');
|
||||
if (that.cashAppPay) {
|
||||
that.cashAppPay.destroy();
|
||||
this.audioService.playSound('ascend-chime-cartoon');
|
||||
if (this.cashAppPay) {
|
||||
this.cashAppPay.destroy();
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.moveToStep('paid');
|
||||
@@ -494,7 +594,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
}, 1000);
|
||||
},
|
||||
error: (response) => {
|
||||
that.accelerateError = response.error;
|
||||
this.accelerateError = response.error;
|
||||
if (!(response.status === 403 && response.error === 'not_available')) {
|
||||
setTimeout(() => {
|
||||
// Reset everything by reloading the page :D, can be improved
|
||||
@@ -597,6 +697,22 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
return false;
|
||||
}
|
||||
|
||||
get canPayWithApplePay() {
|
||||
if (!this.applePayEnabled || !this.conversions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const paymentMethod = this.estimate?.availablePaymentMethods?.applePay;
|
||||
if (paymentMethod) {
|
||||
const costUSD = (this.cost / 100_000_000 * this.conversions.USD);
|
||||
if (costUSD >= paymentMethod.min && costUSD <= paymentMethod.max) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get canPayWithBalance() {
|
||||
if (!this.hasAccessToBalanceMode) {
|
||||
return false;
|
||||
|
||||
@@ -137,6 +137,10 @@ export class ServicesApiServices {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/cashapp`, { txInput: txInput, token: token, cashtag: cashtag, referenceId: referenceId, accelerationUUID: accelerationUUID });
|
||||
}
|
||||
|
||||
accelerateWithApplePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string) {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID });
|
||||
}
|
||||
|
||||
getAccelerations$(): Observable<Acceleration[]> {
|
||||
return this.httpClient.get<Acceleration[]>(`${this.stateService.env.SERVICES_API}/accelerator/accelerations`);
|
||||
}
|
||||
|
||||
@@ -181,4 +181,48 @@ export function uncompressDeltaChange(delta: MempoolBlockDeltaCompressed): Mempo
|
||||
acc: !!tx[3],
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/60467595
|
||||
export function md5(inputString): string {
|
||||
var hc="0123456789abcdef";
|
||||
function rh(n) {var j,s="";for(j=0;j<=3;j++) s+=hc.charAt((n>>(j*8+4))&0x0F)+hc.charAt((n>>(j*8))&0x0F);return s;}
|
||||
function ad(x,y) {var l=(x&0xFFFF)+(y&0xFFFF);var m=(x>>16)+(y>>16)+(l>>16);return (m<<16)|(l&0xFFFF);}
|
||||
function rl(n,c) {return (n<<c)|(n>>>(32-c));}
|
||||
function cm(q,a,b,x,s,t) {return ad(rl(ad(ad(a,q),ad(x,t)),s),b);}
|
||||
function ff(a,b,c,d,x,s,t) {return cm((b&c)|((~b)&d),a,b,x,s,t);}
|
||||
function gg(a,b,c,d,x,s,t) {return cm((b&d)|(c&(~d)),a,b,x,s,t);}
|
||||
function hh(a,b,c,d,x,s,t) {return cm(b^c^d,a,b,x,s,t);}
|
||||
function ii(a,b,c,d,x,s,t) {return cm(c^(b|(~d)),a,b,x,s,t);}
|
||||
function sb(x) {
|
||||
var i;var nblk=((x.length+8)>>6)+1;var blks=new Array(nblk*16);for(i=0;i<nblk*16;i++) blks[i]=0;
|
||||
for(i=0;i<x.length;i++) blks[i>>2]|=x.charCodeAt(i)<<((i%4)*8);
|
||||
blks[i>>2]|=0x80<<((i%4)*8);blks[nblk*16-2]=x.length*8;return blks;
|
||||
}
|
||||
var i,x=sb(""+inputString),a=1732584193,b=-271733879,c=-1732584194,d=271733878,olda,oldb,oldc,oldd;
|
||||
for(i=0;i<x.length;i+=16) {olda=a;oldb=b;oldc=c;oldd=d;
|
||||
a=ff(a,b,c,d,x[i+ 0], 7, -680876936);d=ff(d,a,b,c,x[i+ 1],12, -389564586);c=ff(c,d,a,b,x[i+ 2],17, 606105819);
|
||||
b=ff(b,c,d,a,x[i+ 3],22,-1044525330);a=ff(a,b,c,d,x[i+ 4], 7, -176418897);d=ff(d,a,b,c,x[i+ 5],12, 1200080426);
|
||||
c=ff(c,d,a,b,x[i+ 6],17,-1473231341);b=ff(b,c,d,a,x[i+ 7],22, -45705983);a=ff(a,b,c,d,x[i+ 8], 7, 1770035416);
|
||||
d=ff(d,a,b,c,x[i+ 9],12,-1958414417);c=ff(c,d,a,b,x[i+10],17, -42063);b=ff(b,c,d,a,x[i+11],22,-1990404162);
|
||||
a=ff(a,b,c,d,x[i+12], 7, 1804603682);d=ff(d,a,b,c,x[i+13],12, -40341101);c=ff(c,d,a,b,x[i+14],17,-1502002290);
|
||||
b=ff(b,c,d,a,x[i+15],22, 1236535329);a=gg(a,b,c,d,x[i+ 1], 5, -165796510);d=gg(d,a,b,c,x[i+ 6], 9,-1069501632);
|
||||
c=gg(c,d,a,b,x[i+11],14, 643717713);b=gg(b,c,d,a,x[i+ 0],20, -373897302);a=gg(a,b,c,d,x[i+ 5], 5, -701558691);
|
||||
d=gg(d,a,b,c,x[i+10], 9, 38016083);c=gg(c,d,a,b,x[i+15],14, -660478335);b=gg(b,c,d,a,x[i+ 4],20, -405537848);
|
||||
a=gg(a,b,c,d,x[i+ 9], 5, 568446438);d=gg(d,a,b,c,x[i+14], 9,-1019803690);c=gg(c,d,a,b,x[i+ 3],14, -187363961);
|
||||
b=gg(b,c,d,a,x[i+ 8],20, 1163531501);a=gg(a,b,c,d,x[i+13], 5,-1444681467);d=gg(d,a,b,c,x[i+ 2], 9, -51403784);
|
||||
c=gg(c,d,a,b,x[i+ 7],14, 1735328473);b=gg(b,c,d,a,x[i+12],20,-1926607734);a=hh(a,b,c,d,x[i+ 5], 4, -378558);
|
||||
d=hh(d,a,b,c,x[i+ 8],11,-2022574463);c=hh(c,d,a,b,x[i+11],16, 1839030562);b=hh(b,c,d,a,x[i+14],23, -35309556);
|
||||
a=hh(a,b,c,d,x[i+ 1], 4,-1530992060);d=hh(d,a,b,c,x[i+ 4],11, 1272893353);c=hh(c,d,a,b,x[i+ 7],16, -155497632);
|
||||
b=hh(b,c,d,a,x[i+10],23,-1094730640);a=hh(a,b,c,d,x[i+13], 4, 681279174);d=hh(d,a,b,c,x[i+ 0],11, -358537222);
|
||||
c=hh(c,d,a,b,x[i+ 3],16, -722521979);b=hh(b,c,d,a,x[i+ 6],23, 76029189);a=hh(a,b,c,d,x[i+ 9], 4, -640364487);
|
||||
d=hh(d,a,b,c,x[i+12],11, -421815835);c=hh(c,d,a,b,x[i+15],16, 530742520);b=hh(b,c,d,a,x[i+ 2],23, -995338651);
|
||||
a=ii(a,b,c,d,x[i+ 0], 6, -198630844);d=ii(d,a,b,c,x[i+ 7],10, 1126891415);c=ii(c,d,a,b,x[i+14],15,-1416354905);
|
||||
b=ii(b,c,d,a,x[i+ 5],21, -57434055);a=ii(a,b,c,d,x[i+12], 6, 1700485571);d=ii(d,a,b,c,x[i+ 3],10,-1894986606);
|
||||
c=ii(c,d,a,b,x[i+10],15, -1051523);b=ii(b,c,d,a,x[i+ 1],21,-2054922799);a=ii(a,b,c,d,x[i+ 8], 6, 1873313359);
|
||||
d=ii(d,a,b,c,x[i+15],10, -30611744);c=ii(c,d,a,b,x[i+ 6],15,-1560198380);b=ii(b,c,d,a,x[i+13],21, 1309151649);
|
||||
a=ii(a,b,c,d,x[i+ 4], 6, -145523070);d=ii(d,a,b,c,x[i+11],10,-1120210379);c=ii(c,d,a,b,x[i+ 2],15, 718787259);
|
||||
b=ii(b,c,d,a,x[i+ 9],21, -343485551);a=ad(a,olda);b=ad(b,oldb);c=ad(c,oldc);d=ad(d,oldd);
|
||||
}
|
||||
return rh(a)+rh(b)+rh(c)+rh(d);
|
||||
}
|
||||
Reference in New Issue
Block a user