UNPKG

react-native-payments-androidx

Version:

Welcome to the best and most comprehensive library for integrating payments like Apple Pay and Google Pay into your React Native app.

227 lines (181 loc) 8.52 kB
import { Platform } from 'react-native'; import { MODULE_SCOPING, SHIPPING_ADDRESS_CHANGE_EVENT, SHIPPING_OPTION_CHANGE_EVENT, INTERNAL_SHIPPING_ADDRESS_CHANGE_EVENT, INTERNAL_SHIPPING_OPTION_CHANGE_EVENT, USER_DISMISS_EVENT, USER_ACCEPT_EVENT, SUPPORTED_METHOD_NAME } from './constants'; const noop = () => {}; import PaymentRequest from '.'; import NativePayments from '../NativeBridge'; // Helpers import { validateTotal, validateDisplayItems, validateShippingOptions, convertDetailAmountsToString } from './helpers'; // Errors import { DOMException } from './errors'; export default class PaymentRequestUpdateEvent { name: string; target: PaymentRequest; _waitForUpdate: boolean; _handleDetailsChange: PaymentDetailsModifier => Promise<any>; _resetEvent: any; constructor(name, target) { if ( name !== SHIPPING_ADDRESS_CHANGE_EVENT && name !== SHIPPING_OPTION_CHANGE_EVENT ) { throw new Error( `Only "${SHIPPING_ADDRESS_CHANGE_EVENT}" and "${SHIPPING_OPTION_CHANGE_EVENT}" event listeners are supported.` ); } this.name = name; this.target = target; this._waitForUpdate = false; this._handleDetailsChange = this._handleDetailsChange.bind(this); this._resetEvent = this._resetEvent.bind(this); } _handleDetailsChange(value: PaymentDetailsBase) { const target = this.target; validateTotal(value.total, DOMException); validateDisplayItems(value.displayItems, DOMException); validateShippingOptions(value.shippingOptions, DOMException); // 1. Let details be the result of converting value to a PaymentDetailsUpdate dictionary. If this throws an exception, abort the update with the thrown exception. const details: PaymentDetailsUpdate = Object.assign(target._details, value); // 2. Let serializedModifierData be an empty list. let serializedModifierData = []; // 3. Let selectedShippingOption be null. let selectedShippingOption = null; // 4. Let shippingOptions be an empty sequence<PaymentShippingOption>. let shippingOptions = []; // 5. Validate and canonicalize the details: // TODO: implmentation // 6. Update the PaymentRequest using the new details: target._details = details; // 6.1 If the total member of details is present, then: // if (details.total) { // target._details = Object.assign({}, target._details, { total: details.total }); // } // // 6.2 If the displayItems member of details is present, then: // if (details.displayItems) { // target._details = Object.assign({}, target._details, { displayItems: details.displayItems }); // } // // 6.3 If the shippingOptions member of details is present, and target.[[options]].requestShipping is true, then: // if (details.shippingOptions && target._options.requestShipping === true) { // // 6.3.1 Set target.[[details]].shippingOptions to shippingOptions. // shippingOptions = details.shippingOptions; // target._details = Object.assign({}, target._details, { shippingOptions }); // // 6.3.2 Set the value of target's shippingOption attribute to selectedShippingOption. // selectedShippingOption = target.shippingOption; // } // // 6.4 If the modifiers member of details is present, then: // if (details.modifiers) { // // 6.4.1 Set target.[[details]].modifiers to details.modifiers. // target._details = Object.assign({}, target._details, { modifiers: details.modifiers }); // // 6.4.2 Set target.[[serializedModifierData]] to serializedModifierData. // target._serializedModifierData = serializedModifierData; // } // 6.5 If target.[[options]].requestShipping is true, and target.[[details]].shippingOptions is empty, // then the developer has signified that there are no valid shipping options for the currently-chosen shipping address (given by target's shippingAddress). // In this case, the user agent should display an error indicating this, and may indicate that that the currently-chosen shipping address is invalid in some way. // The user agent should use the error member of details, if it is present, to give more information about why there are no valid shipping options for that address. // React Native Payments specific 👇 // --------------------------------- const normalizedDetails = convertDetailAmountsToString(target._details); return ( NativePayments.handleDetailsUpdate(normalizedDetails, DOMException) // 14. Upon fulfillment of detailsPromise with value value .then(this._resetEvent()) // On iOS the `selectedShippingMethod` defaults back to the first option // when updating shippingMethods. So we call the `_handleShippingOptionChange` // method with the first shippingOption id so that JS is in sync with Apple Pay. .then(() => { if (Platform.OS !== 'ios') { return; } if ( target._details.shippingOptions && target._details.shippingOptions.length > 0 && value.shippingOptions && ((value.shippingOptions.find(op => op.selected) || {}).id || null) !== target._shippingOption ) { target._handleShippingOptionChange({ selectedShippingOptionId: target._details.shippingOptions[0].id }); } }) // 13. Upon rejection of detailsPromise: .catch(e => { this._resetEvent(); throw new Error('AbortError'); }) ); } _resetEvent() { // 1. Set event.[[waitForUpdate]] to false. this._waitForUpdate = false; // 2. Set target.[[updating]] to false. this.target._updating = false; // 3. The user agent should update the user interface based on any changed values in target. // The user agent should re-enable user interface elements that might have been disabled in the steps above if appropriate. noop(); } async updateWith( PaymentDetailsModifierOrPromise: | PaymentDetailsUpdate | (( PaymentDetailsModifier, PaymentAddress ) => Promise<PaymentDetailsUpdate>) ) { // 1. Let event be this PaymentRequestUpdateEvent instance. let event = this; // 2. Let target be the value of event's target attribute. let target = this.target; // 3. If target is not a PaymentRequest object, then throw a TypeError. if (!(target instanceof PaymentRequest)) { throw new Error('TypeError'); } // 5. If event.[[waitForUpdate]] is true, then throw an "InvalidStateError" DOMException. if (event._waitForUpdate === true) { throw new Error('InvalidStateError'); } // 6. If target.[[state]] is not " interactive", then throw an " InvalidStateError" DOMException. if (target._state !== 'interactive') { throw new Error('InvalidStateError'); } // 7. If target.[[updating]] is true, then throw an "InvalidStateError" DOMException. if (target._updating === true) { throw new Error('InvalidStateError'); } // 8. Set event's stop propagation flag and stop immediate propagation flag. noop(); // 9. Set event.[[waitForUpdate]] to true. event._waitForUpdate = true; // 10. Set target.[[updating]] to true. target._updating = true; // 11. The user agent should disable the user interface that allows the user to accept the payment request. // This is to ensure that the payment is not accepted until the web page has made changes required by the change. // The web page must settle the detailsPromise to indicate that the payment request is valid again. // The user agent should disable any part of the user interface that could cause another update event to be fired. // Only one update may be processed at a time. noop(); // NativePayments does this for us (iOS does at the time of this comment) // 12. Return from the method and perform the remaining steps in parallel. if (typeof PaymentDetailsModifierOrPromise === 'object') { const paymentDetailsModifier = PaymentDetailsModifierOrPromise; return this._handleDetailsChange(paymentDetailsModifier); } if (typeof PaymentDetailsModifierOrPromise === 'function') { const detailsFromPromise = await PaymentDetailsModifierOrPromise(); this._handleDetailsChange(detailsFromPromise); } // 13 & 14 happen in `this._handleDetailsChange`. } }