Skip to content

Commit

Permalink
feat(Apple Pay): allow for customization of event updates
Browse files Browse the repository at this point in the history
Adds the ability for listeners on the various Apple Pay emitters to
accept a `complete` callback as the second argument. If the callback
names this argument, then the SDK will defer the execution to that
callback instead of resolving itself. The interface for each callback
depends on the triggering event, but this opens up the ability to fetch
custom taxes on payment method selection, update shipping costs on
shipping contact/method selection and validate email/phone addresses on
authorization.

This also enhances the `token` event to include the raw payment event as
the second argument.
  • Loading branch information
cbarton committed Apr 5, 2023
1 parent ed0b38d commit 588864a
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 92 deletions.
88 changes: 57 additions & 31 deletions lib/recurly/apple-pay/apple-pay.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,20 @@ export class ApplePay extends Emitter {
debug('Creating new Apple Pay session', paymentRequest);

const session = new window.ApplePaySession(MINIMUM_SUPPORTED_VERSION, paymentRequest);
session.onvalidatemerchant = this.onValidateMerchant.bind(this);
session.onshippingcontactselected = this.onShippingContactSelected.bind(this);
session.onshippingmethodselected = this.onShippingMethodSelected.bind(this);
session.onpaymentmethodselected = this.onPaymentMethodSelected.bind(this);
session.onpaymentauthorized = this.onPaymentAuthorized.bind(this);
session.oncancel = this.onCancel.bind(this);
session.onvalidatemerchant = this.onValidateMerchant.bind(this);

session.onpaymentmethodselected = registerEventHandler(
this, 'paymentMethodSelected', this.onPaymentMethodSelected.bind(this));

session.onshippingcontactselected = registerEventHandler(
this, 'shippingContactSelected', this.onShippingContactSelected.bind(this));

session.onshippingmethodselected = registerEventHandler(
this, 'shippingMethodSelected', this.onShippingMethodSelected.bind(this));

session.onpaymentauthorized = registerEventHandler(
this, 'paymentAuthorized', this.onPaymentAuthorized.bind(this));

return this._session = session;
}
Expand Down Expand Up @@ -137,7 +145,7 @@ export class ApplePay extends Emitter {
else return this.initError = this.error('apple-pay-factory-only');

if (options.form) this.config.form = options.form;
if (options.recurring) this.config.recurring = options.recurring;
if ('recurring' in options) this.config.recurring = options.recurring;

this.config.i18n = {
...I18N,
Expand Down Expand Up @@ -221,7 +229,7 @@ export class ApplePay extends Emitter {
* @private
*/
onValidateMerchant (event) {
debug('Validating Apple Pay merchant session', event);
debug('validateMerchant', event);

const validationURL = event.validationURL;

Expand All @@ -235,6 +243,14 @@ export class ApplePay extends Emitter {
});
}

/**
* sets the `contact` to the proper address on the pricing object.
*
* @param {string} addressType 'shippingAddress' or 'address'
* @param {object} contact The Apple Pay contact
* @param {Function} done
* @private
*/
setAddress (addressType, contact, done) {
if (!contact || !this.config.pricing) return done?.();

Expand All @@ -249,16 +265,13 @@ export class ApplePay extends Emitter {
* @param {Event} event
* @private
*/
onPaymentMethodSelected (event) {
debug('Payment method selected', event);

this.emit('paymentMethodSelected', event);

this.setAddress('address', event.paymentMethod.billingContact, () => {
onPaymentMethodSelected ({ paymentMethod: { billingContact } }, update) {
this.setAddress('address', billingContact, () => {
this.session.completePaymentMethodSelection({
newTotal: this.finalTotalLineItem,
newLineItems: this.lineItems,
...(this.recurringPaymentRequest && { newRecurringPaymentRequest: this.recurringPaymentRequest }),
newRecurringPaymentRequest: this.recurringPaymentRequest,
...update,
});
});
}
Expand All @@ -269,15 +282,13 @@ export class ApplePay extends Emitter {
* @param {Event} event
* @private
*/
onShippingContactSelected (event) {
this.emit('shippingContactSelected', event);

this.setAddress('shippingAddress', event.shippingContact, () => {
onShippingContactSelected ({ shippingContact }, update) {
this.setAddress('shippingAddress', shippingContact, () => {
this.session.completeShippingContactSelection({
newTotal: this.finalTotalLineItem,
newLineItems: this.lineItems,
newShippingMethods: [],
...(this.recurringPaymentRequest && { newRecurringPaymentRequest: this.recurringPaymentRequest }),
newRecurringPaymentRequest: this.recurringPaymentRequest,
...update,
});
});
}
Expand All @@ -288,14 +299,12 @@ export class ApplePay extends Emitter {
* @param {Event} event
* @private
*/
onShippingMethodSelected (event) {
this.emit('shippingMethodSelected', event);

onShippingMethodSelected (event, update) {
this.session.completeShippingMethodSelection({
newTotal: this.finalTotalLineItem,
newLineItems: this.lineItems,
newShippingMethods: [],
...(this.recurringPaymentRequest && { newRecurringPaymentRequest: this.recurringPaymentRequest }),
newRecurringPaymentRequest: this.recurringPaymentRequest,
...update
});
}

Expand All @@ -307,12 +316,15 @@ export class ApplePay extends Emitter {
* @emit 'token'
* @private
*/
onPaymentAuthorized (event) {
debug('Payment authorization received', event);
onPaymentAuthorized (event, { errors } = {}) {
this.emit('authorized', event); // deprecated

this.emit('paymentAuthorized', event);
if (typeof errors === 'object' && errors.length > 0) {
this.session.completePayment({ status: this.session.STATUS_FAILURE, errors });
return;
}

return this.token(event);
this.token(event);
}

token (event) {
Expand All @@ -330,8 +342,7 @@ export class ApplePay extends Emitter {
debug('Token received', token);

this.session.completePayment({ status: this.session.STATUS_SUCCESS });
this.emit('authorized', event);
this.emit('token', token);
this.emit('token', token, event);
}
});
}
Expand Down Expand Up @@ -383,6 +394,21 @@ export class ApplePay extends Emitter {
}
}

function registerEventHandler (emitter, eventName, handler) {
return function (event) {
debug(eventName, event);

// listener accepts the `done` callback as an argument
if (!emitter.listeners(eventName).some(l => l.length >= 2)) {
emitter.emit(eventName, event);
handler(event);
return;
}

emitter.emit(eventName, event, (...args) => handler(event, ...args));
};
}

function restorePricing (pricing, state, done) {
if (!pricing) return done();

Expand Down
Loading

0 comments on commit 588864a

Please sign in to comment.