Skip to content

Commit

Permalink
chore(google pay): clean up internals of google pay
Browse files Browse the repository at this point in the history
Add payment data prefetch before the button is made to potentially speed
up the interaction.

Refactor the internals away from an emitter chain.

Always request the billing information as Google pay is the source of
truth.
  • Loading branch information
cbarton committed Apr 7, 2023
1 parent 9bca45b commit 142d5df
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 236 deletions.
98 changes: 52 additions & 46 deletions lib/recurly/google-pay/google-pay.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { FIELDS as TOKEN_FIELDS } from '../token';
import recurlyError from '../errors';
import { payWithGoogle } from './pay-with-google';

const getRecurlyInputsFromHtmlForm = ({ $form, inputNames }) => $form ? normalize($form, inputNames).values : {};
const API_VERSION = { apiVersion: 2, apiVersionMinor: 0 };

const getRecurlyInputsFromHtmlForm = ({ $form, inputNames }) => $form ? normalize($form, inputNames, { parseCard: false }).values : {};

const getBillingAddressFromGoogle = ({ paymentData }) => {
const googleBillingAddress = paymentData?.paymentMethodData?.info?.billingAddress || {};
Expand Down Expand Up @@ -70,32 +72,21 @@ const validateRecurlyMerchantInfo = ({ recurlyMerchantInfo }) => {

const getGoogleInfoFromMerchantInfo = ({ recurlyMerchantInfo, options }) => {
const { siteMode, paymentMethods } = recurlyMerchantInfo;
const {
environment: envOpt,
googleMerchantId,
googleBusinessName,
total,
country,
currency,
requireBillingAddress,
} = options;
const { environment: envOpt, } = options;

const gatewayCodeSelected = paymentMethods.filter(({ gatewayCode }) => gatewayCode)[0]?.gatewayCode;
const gatewayCode = paymentMethods.filter(({ gatewayCode }) => gatewayCode)[0]?.gatewayCode;
const environment = envOpt || (siteMode === 'production' ? 'PRODUCTION' : 'TEST');
const googlePayConfig = {
apiVersion: 2,
apiVersionMinor: 0,
const isReadyToPayRequest = {
...API_VERSION,
allowedPaymentMethods: paymentMethods.map(({ cardNetworks, authMethods, paymentGateway, direct }) => ({
type: 'CARD',
parameters: {
allowedCardNetworks: cardNetworks,
allowedAuthMethods: authMethods,
...(requireBillingAddress && {
billingAddressRequired: true,
billingAddressParameters: {
format: 'FULL',
},
}),
billingAddressRequired: true,
billingAddressParameters: {
format: 'FULL',
},
},
tokenizationSpecification: {
...(paymentGateway && {
Expand All @@ -109,24 +100,30 @@ const getGoogleInfoFromMerchantInfo = ({ recurlyMerchantInfo, options }) => {
},
})),
};

const {
googleMerchantId: merchantId,
googleBusinessName: merchantName,
country: countryCode,
currency: currencyCode,
total: totalPrice,
} = options;
const merchantInfo = options.merchantInfo ?? { merchantId, merchantName };
const paymentDataRequest = {
...googlePayConfig,
merchantInfo: {
merchantId: googleMerchantId,
merchantName: googleBusinessName,
},
...isReadyToPayRequest,
merchantInfo,
transactionInfo: {
totalPriceStatus: 'FINAL', // only when the price will nto change
totalPrice: total,
currencyCode: currency,
countryCode: country
totalPrice,
currencyCode,
countryCode,
},
};

return { gatewayCodeSelected, environment, googlePayConfig, paymentDataRequest };
return { gatewayCode, environment, isReadyToPayRequest, paymentDataRequest };
};

const getGooglePayInfo = ({ recurly, options }) => {
const buildPaymentDataRequest = ({ recurly, options }) => {
const { country, currency, gatewayCode } = options;
const data = { country, currency, gateway_code: gatewayCode };

Expand All @@ -147,23 +144,32 @@ const googlePay = (recurly, options) => {
const handleErr = err => emitter.emit('error', err);
let gatewayCodeSelected;

getGooglePayInfo({ recurly, options })
.then(googlePayInfo => {
gatewayCodeSelected = googlePayInfo.gatewayCodeSelected;
return payWithGoogle({ googlePayInfo, options });
})
.then(({ $button, paymentDataEmitter }) => emitter.emit('ready', $button) && paymentDataEmitter)
.catch(err => {
handleErr(err);
throw err;
const onGooglePayButtonClicked = (err, paymentData) => {
if (err) {
return handleErr(recurlyError('google-pay-payment-failure', err));
}

createRecurlyToken({ recurly, paymentData, gatewayCodeSelected, $form: options.form })
.then(token => emitter.emit('token', token, paymentData))
.catch(handleErr);
};

buildPaymentDataRequest({ recurly, options })
.then(({ gatewayCode, environment, isReadyToPayRequest, paymentDataRequest }) => {
gatewayCodeSelected = gatewayCode;

return payWithGoogle({
environment,
isReadyToPayRequest,
paymentDataRequest,
buttonOptions: {
...options.buttonOptions,
onClick: onGooglePayButtonClicked,
},
});
})
.then(paymentDataEmitter => paymentDataEmitter
.on('payment-data', paymentData => createRecurlyToken({ recurly, paymentData, gatewayCodeSelected, $form: options.form })
.then(token => emitter.emit('token', token))
.catch(handleErr)
)
.on('error', handleErr)
);
.then(button => emitter.emit('ready', button))
.catch(handleErr);

return emitter;
};
Expand Down
64 changes: 39 additions & 25 deletions lib/recurly/google-pay/pay-with-google.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,54 @@
import Emitter from 'component-emitter';
import { loadLibs } from '../../util/dom';
import recurlyError from '../errors';

const GOOGLE_PAY_LIB_URL = 'https://pay.google.com/gp/p/js/pay.js';

const loadGooglePayLib = () => Promise.resolve(
window.google?.payments?.api?.PaymentsClient || loadLibs(GOOGLE_PAY_LIB_URL)
window.google?.payments.api.PaymentsClient || loadLibs(GOOGLE_PAY_LIB_URL)
);

const payWithGoogle = ({
googlePayInfo: {
environment,
googlePayConfig,
paymentDataRequest,
},
options: {
buttonOptions,
function createButton (
googlePayClient,
paymentDataRequest,
{
onClick: onButtonClick,
...buttonOptions
},
) {
const { allowedPaymentMethods, transactionInfo } = paymentDataRequest;

googlePayClient.prefetchPaymentData({
...paymentDataRequest,
transactionInfo: {
...transactionInfo,
totalPriceStatus: 'NOT_CURRENTLY_KNOWN',
},
});

return googlePayClient.createButton({
...buttonOptions,
allowedPaymentMethods,
onClick: () => {
googlePayClient
.loadPaymentData(paymentDataRequest)
.catch(onButtonClick)
.then(paymentData => onButtonClick?.(null, paymentData));
},
});
}

const payWithGoogle = ({
environment,
isReadyToPayRequest,
paymentDataRequest,
buttonOptions,
}) => {
let googlePayClient;
const paymentDataEmitter = new Emitter();

const onGooglePayButtonClicked = () => googlePayClient.loadPaymentData(paymentDataRequest)
.then(paymentData => paymentDataEmitter.emit('payment-data', paymentData))
.catch(err => paymentDataEmitter.emit('error', recurlyError('google-pay-payment-failure', { err })));

return loadGooglePayLib()
.then(() => {
googlePayClient = new window.google.payments.api.PaymentsClient({ environment });
return googlePayClient.isReadyToPay(googlePayConfig);
return googlePayClient.isReadyToPay(isReadyToPayRequest);
})
.catch(err => {
throw recurlyError('google-pay-init-error', { err });
Expand All @@ -37,15 +57,9 @@ const payWithGoogle = ({
if (!isReadyToPay) {
throw recurlyError('google-pay-not-available');
}
})
.then(() => googlePayClient.createButton({
...buttonOptions,
onClick: onGooglePayButtonClicked
}))
.then($button => ({
$button,
paymentDataEmitter,
}));

return createButton(googlePayClient, paymentDataRequest, buttonOptions);
});
};

export { payWithGoogle };
4 changes: 2 additions & 2 deletions test/unit/google-pay/google-pay.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { initRecurly, apiTest, nextTick, assertDone, stubPromise, stubGooglePaym
import { googlePay } from '../../../lib/recurly/google-pay/google-pay';
import dom from '../../../lib/util/dom';

apiTest(requestMethod => describe('Google Pay', function () {
apiTest(requestMethod => describe(`Google Pay (${requestMethod})`, function () {
const cors = requestMethod === 'cors';

before(() => {
Expand Down Expand Up @@ -416,7 +416,7 @@ apiTest(requestMethod => describe('Google Pay', function () {

context('when fails retrieving the user Payment Data', function () {
beforeEach(function () {
this.stubGoogleAPIOpts.loadPaymentData = Promise.reject(recurlyError('google-pay-payment-failure'));
this.stubGoogleAPIOpts.loadPaymentData = Promise.reject('boom');
});

it('emits the same error that the retrieving process throws', function (done) {
Expand Down
Loading

0 comments on commit 142d5df

Please sign in to comment.