[accelerator] add support for Google Pay payment

This commit is contained in:
nymkappa 2024-07-25 15:54:24 +02:00
parent fe1ad86885
commit 481859bc8f
No known key found for this signature in database
GPG Key ID: 92358FC85D9645DE
4 changed files with 138 additions and 14 deletions

View File

@ -389,13 +389,13 @@
</div>
}
</div>
@if (canPayWithCashapp || canPayWithApplePay) {
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) {
<div class="col-sm text-center flex-grow-0 d-flex flex-column justify-content-center align-items-center">
<p class="text-nowrap">&mdash;<span i18n="or">OR</span>&mdash;</p>
</div>
}
}
@if (canPayWithCashapp || canPayWithApplePay) {
@if (canPayWithCashapp || canPayWithApplePay || canPayWithGooglePay) {
<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>&nbsp;<app-fiat [value]="cost"></app-fiat> with</p>
@if (canPayWithCashapp) {
@ -405,6 +405,10 @@
@if (canPayWithCashapp) { <hr class="w-25 mt-2 mb-2"> }
<img style="cursor: pointer;" src="/resources/apple-pay.svg" height=55 (click)="moveToStep('applepay')">
}
@if (canPayWithGooglePay) {
@if (canPayWithCashapp || canPayWithApplePay) { <hr class="w-25 mt-2 mb-2"> }
<img style="cursor: pointer;" src="/resources/google-pay.png" height=55 (click)="moveToStep('googlepay')">
}
</div>
}
</div>
@ -427,7 +431,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' || step === 'applepay') {
} @else if (step === 'cashapp' || step === 'applepay' || step === 'googlepay') {
<!-- Show checkout page -->
<div class="row mb-md-1 text-center" id="confirm-title">
<div class="col-sm" id="confirm-payment-title">
@ -443,7 +447,7 @@
</div>
</div>
@if (step === 'cashapp' && !loadingCashapp || step === 'applepay' && !loadingApplePay) {
@if (step === 'cashapp' && !loadingCashapp || step === 'applepay' && !loadingApplePay || step === 'googlepay' && !loadingGooglePay) {
<div class="row text-center mt-1">
<div class="col-sm">
<div class="form-group w-100">
@ -466,8 +470,10 @@
<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>
} @else if (step === 'googlepay') {
<div id="google-pay-button" class="d-inline-block" [style]="loadingGooglePay ? 'opacity: 0; width: 0px; height: 0px; pointer-events: none;' : ''"></div>
}
@if (loadingCashapp || loadingApplePay) {
@if (loadingCashapp || loadingApplePay || loadingGooglePay) {
<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>

View File

@ -47,7 +47,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' | 'applepay' | 'processing' | 'paid' | 'success';
type CheckoutStep = 'quote' | 'summary' | 'checkout' | 'cashapp' | 'applepay' | 'googlepay' | 'processing' | 'paid' | 'success';
@Component({
selector: 'app-accelerate-checkout',
@ -62,6 +62,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
@Input() scrollEvent: boolean;
@Input() cashappEnabled: boolean = true;
@Input() applePayEnabled: boolean = false;
@Input() googlePayEnabled: boolean = true;
@Input() advancedEnabled: boolean = false;
@Input() forceMobile: boolean = false;
@Input() showDetails: boolean = false;
@ -83,7 +84,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
private _step: CheckoutStep = 'summary';
simpleMode: boolean = true;
paymentMethod: 'cashapp' | 'btcpay';
timeoutTimer: any;
authSubscription$: Subscription;
@ -112,11 +112,13 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
// square
loadingCashapp = false;
loadingApplePay = false;
loadingGooglePay = false;
cashappError = false;
cashappSubmit: any;
payments: any;
cashAppPay: any;
applePay: any;
googlePay: any;
conversionsSubscription: Subscription;
conversions: any;
@ -228,6 +230,11 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
this.insertSquare();
this.setupSquare();
this.scrollToElementWithTimeout('confirm-title', 'center', 100);
} else if (this._step === 'googlepay' && this.googlePayEnabled) {
this.loadingGooglePay = true;
this.insertSquare();
this.setupSquare();
this.scrollToElementWithTimeout('confirm-title', 'center', 100);
} else if (this._step === 'paid') {
this.timePaid = Date.now();
this.timeoutTimer = setTimeout(() => {
@ -443,6 +450,8 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
await this.requestCashAppPayment();
} else if (this._step === 'applepay') {
await this.requestApplePayPayment();
} else if (this._step === 'googlepay') {
await this.requestGooglePayPayment();
}
} catch (e) {
console.debug('Error loading Square Payments', e);
@ -541,6 +550,92 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
);
}
/**
* GOOGLE PAY
*/
async requestGooglePayPayment() {
if (this.conversionsSubscription) {
this.conversionsSubscription.unsubscribe();
}
this.conversionsSubscription = this.stateService.conversions$.subscribe(
async (conversions) => {
this.conversions = conversions;
if (this.googlePay) {
this.googlePay.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'
}
});
this.googlePay = await this.payments.googlePay(paymentRequest , {
referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
});
await this.googlePay.attach(`#google-pay-button`, {
buttonType: 'pay',
onClick: (e) => { console.log(e, 'hi') }
});
this.loadingGooglePay = false;
document.getElementById('google-pay-button').addEventListener('click', async event => {
event.preventDefault();
const tokenResult = await this.googlePay.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.accelerateWithGooglePay$(
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.googlePay) {
this.googlePay.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);
}
});
}
);
}
/**
* CASHAPP
*/
@ -566,18 +661,14 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
label: 'Total',
pending: true,
productUrl: `${redirectHostname}/tracker/${this.tx.txid}`,
},
button: { shape: 'semiround', size: 'small', theme: 'light'}
}
});
this.cashAppPay = await this.payments.cashAppPay(paymentRequest, {
redirectURL: `${redirectHostname}/tracker/${this.tx.txid}`,
referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
button: { shape: 'semiround', size: 'small', theme: 'light'}
referenceId: `accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`
});
if (this.step === 'cashapp') {
await this.cashAppPay.attach(`#cash-app-pay`, { theme: 'light', size: 'small', shape: 'semiround' })
}
await this.cashAppPay.attach(`#cash-app-pay`, { theme: 'light', size: 'small', shape: 'semiround' })
this.loadingCashapp = false;
this.cashAppPay.addEventListener('ontokenization', event => {
@ -686,6 +777,13 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return !!this.estimate?.availablePaymentMethods?.applePay;
}
get couldPayWithGooglePay() {
if (!this.googlePayEnabled) {
return false;
}
return !!this.estimate?.availablePaymentMethods?.googlePay;
}
get couldPayWithBalance() {
if (!this.hasAccessToBalanceMode) {
return false;
@ -734,6 +832,22 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
return false;
}
get canPayWithGooglePay() {
if (!this.googlePayEnabled || !this.conversions) {
return false;
}
const paymentMethod = this.estimate?.availablePaymentMethods?.googlePay;
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;

View File

@ -141,6 +141,10 @@ export class ServicesApiServices {
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, accelerationUUID: accelerationUUID });
}
accelerateWithGooglePay$(txInput: string, token: string, cardTag: string, referenceId: string, accelerationUUID: string) {
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { 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`);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB