UNPKG

react-native-moyasar-sdk

Version:

Official React Native Moyasar SDK - Integrate Credit Cards, Apple Pay, Samsung Pay, and STC Pay with simple interfaces for a seamless payment experience in your React Native app

347 lines (294 loc) 13.8 kB
'use strict'; // Types // Modules import { DeviceEventEmitter, Platform } from 'react-native'; import NativePayments from "../NativeBridge/index.js"; import PaymentResponse from "./PaymentResponse.js"; import PaymentRequestUpdateEvent from "./PaymentRequestUpdateEvent.js"; import { errorLog } from "../../helpers/debug_log.js"; // Helpers import { isValidDecimalMonetaryValue, isNegative, convertDetailAmountsToString, getPlatformMethodData, validateTotal, validatePaymentMethods, validateDisplayItems, validateShippingOptions, getSelectedShippingOption, hasGatewayConfig, getGatewayName, validateGateway } from "./helpers/index.js"; import { ConstructorError, GatewayError } from "./errors/index.js"; // Constants 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, GATEWAY_ERROR_EVENT, SUPPORTED_METHOD_NAME } from "./constants.js"; const noop = () => {}; const IS_ANDROID = Platform.OS === 'android'; const IS_IOS = Platform.OS === 'ios'; // function processPaymentDetailsModifiers(details, serializedModifierData) { // let modifiers = []; // if (details.modifiers) { // modifiers = details.modifiers; // modifiers.forEach((modifier) => { // if (modifier.total && modifier.total.amount && modifier.total.amount.value) { // // TODO: refactor validateTotal so that we can display proper error messages (should remove construct 'PaymentRequest') // validateTotal(modifier.total); // } // if (modifier.additionalDisplayItems) { // modifier.additionalDisplayItems.forEach((displayItem) => { // let value = displayItem && displayItem.amount.value && displayItem.amount.value; // isValidDecimalMonetaryValue(value); // }); // } // let serializedData = modifier.data // ? JSON.stringify(modifier.data) // : null; // serializedModifierData.push(serializedData); // if (modifier.data) { // delete modifier.data; // } // }); // } // details.modifiers = modifiers; // } export default class PaymentRequest { // Internal Slots // TODO: - add proper type annotation // TODO: - add proper type annotation // TODO: - add proper type annotation // TODO: - add proper type annotation // TODO: - add proper type annotation // function provided by user // function provided by user constructor(methodData = [], details = [], options = {}) { // 1. If the current settings object's responsible document is not allowed to use the feature indicated by attribute name allowpaymentrequest, then throw a " SecurityError" DOMException. noop(); // 2. Let serializedMethodData be an empty list. // happens in `processPaymentMethods` // 3. Establish the request's id: // Removed usage of id since it's not used in our implementation // 4. Process payment methods const serializedMethodData = validatePaymentMethods(methodData); // 5. Process the total validateTotal(details.total, ConstructorError); // 6. If the displayItems member of details is present, then for each item in details.displayItems: validateDisplayItems(details.displayItems, ConstructorError); // 7. Let selectedShippingOption be null. let selectedShippingOption = null; // 8. Process shipping options validateShippingOptions(details, ConstructorError); if (IS_IOS) { selectedShippingOption = getSelectedShippingOption(details.shippingOptions); } // 9. Let serializedModifierData be an empty list. let serializedModifierData = []; // 10. Process payment details modifiers: // TODO // - Look into how payment details modifiers are used. // processPaymentDetailsModifiers(details, serializedModifierData) // 11. Let request be a new PaymentRequest. // 12. Set request.[[options]] to options. this._options = options; // 13. Set request.[[state]] to "created". this._state = 'created'; // 14. Set request.[[updating]] to false. this._updating = false; // 15. Set request.[[details]] to details. this._details = details; // 16. Set request.[[serializedModifierData]] to serializedModifierData. this._serializedModifierData = serializedModifierData; // 17. Set request.[[serializedMethodData]] to serializedMethodData. this._serializedMethodData = JSON.stringify(methodData); // 18. Set the value of request's shippingOption attribute to selectedShippingOption. this._shippingOption = selectedShippingOption; // 19. Set the value of the shippingAddress attribute on request to null. this._shippingAddress = null; // 20. If options.requestShipping is set to true, then set the value of the shippingType attribute on request to options.shippingType. Otherwise, set it to null. this._shippingType = IS_IOS && options.requestShipping === true ? options.shippingType : null; // React Native Payments specific 👇 // --------------------------------- // Setup event listeners this._setupEventListeners(); // Set the amount of times `_handleShippingAddressChange` has been called. // This is used on iOS to noop the first call. this._shippingAddressChangesCount = 0; const platformMethodData = getPlatformMethodData(methodData, Platform.OS); const normalizedDetails = convertDetailAmountsToString(details); // Validate gateway config if present if (hasGatewayConfig(platformMethodData)) { validateGateway(getGatewayName(platformMethodData), NativePayments.supportedGateways); } NativePayments.createPaymentRequest(platformMethodData, normalizedDetails, options); } // initialize acceptPromiseResolver/Rejecter // mainly for unit tests to work without going through the complete flow. _acceptPromiseResolver = () => {}; _acceptPromiseRejecter = () => {}; _setupEventListeners() { // Internal Events this._userDismissSubscription = DeviceEventEmitter.addListener(USER_DISMISS_EVENT, this._closePaymentRequest.bind(this)); this._userAcceptSubscription = DeviceEventEmitter.addListener(USER_ACCEPT_EVENT, this._handleUserAccept.bind(this)); if (IS_IOS) { this._gatewayErrorSubscription = DeviceEventEmitter.addListener(GATEWAY_ERROR_EVENT, this._handleGatewayError.bind(this)); // https://www.w3.org/TR/payment-request/#onshippingoptionchange-attribute this._shippingOptionChangeSubscription = DeviceEventEmitter.addListener(INTERNAL_SHIPPING_OPTION_CHANGE_EVENT, this._handleShippingOptionChange.bind(this)); // https://www.w3.org/TR/payment-request/#onshippingaddresschange-attribute this._shippingAddressChangeSubscription = DeviceEventEmitter.addListener(INTERNAL_SHIPPING_ADDRESS_CHANGE_EVENT, this._handleShippingAddressChange.bind(this)); } } _handleShippingAddressChange(postalAddress) { this._shippingAddress = postalAddress; const event = new PaymentRequestUpdateEvent(SHIPPING_ADDRESS_CHANGE_EVENT, this); this._shippingAddressChangesCount++; // On iOS, this event fires when the PKPaymentRequest is initialized. // So on iOS, we track the amount of times `_handleShippingAddressChange` gets called // and noop the first call. if (IS_IOS && this._shippingAddressChangesCount === 1) { return event.updateWith(this._details); } // Eventually calls `PaymentRequestUpdateEvent._handleDetailsUpdate` when // after a details are returned this._shippingAddressChangeFn && this._shippingAddressChangeFn(event); } _handleShippingOptionChange({ selectedShippingOptionId }) { // Update the `shippingOption` this._shippingOption = selectedShippingOptionId; const event = new PaymentRequestUpdateEvent(SHIPPING_OPTION_CHANGE_EVENT, this); this._shippingOptionChangeFn && this._shippingOptionChangeFn(event); } _getPlatformDetails(details) { return IS_IOS ? this._getPlatformDetailsIOS(details) : this._getPlatformDetailsAndroid(details); } _getPlatformDetailsIOS(details) { const { paymentData: serializedPaymentData, billingContact: serializedBillingContact, shippingContact: serializedShippingContact, paymentToken, transactionIdentifier, paymentMethod } = details; const isSimulator = transactionIdentifier === 'Simulated Identifier'; let billingContact = null; let shippingContact = null; if (serializedBillingContact && serializedBillingContact !== "") { try { billingContact = JSON.parse(serializedBillingContact); } catch (e) {} } if (serializedShippingContact && serializedShippingContact !== "") { try { shippingContact = JSON.parse(serializedShippingContact); } catch (e) {} } return { paymentData: isSimulator ? null : JSON.parse(serializedPaymentData), billingContact, shippingContact, paymentToken, transactionIdentifier, paymentMethod }; } _getPlatformDetailsAndroid(details) { const { googleTransactionId, paymentDescription } = details; return { googleTransactionId, paymentDescription, // On Android, the recommended flow is to have user's confirm prior to // retrieving the full wallet. getPaymentToken: () => NativePayments.getFullWalletAndroid(googleTransactionId, getPlatformMethodData(JSON.parse(this._serializedMethodData, Platform.OS)), convertDetailAmountsToString(this._details)) }; } _handleUserAccept(details) { // On Android, we don't have `onShippingAddressChange` events, so we // set the shipping address when the user accepts. // // Developers will only have access to it in the `PaymentResponse`. if (IS_ANDROID) { const { shippingAddress } = details; this._shippingAddress = shippingAddress; } const paymentResponse = new PaymentResponse({ methodName: IS_IOS ? 'apple-pay' : 'android-pay', shippingAddress: this._options.requestShipping ? this._shippingAddress : null, details: this._getPlatformDetails(details), shippingOption: IS_IOS ? this._shippingOption : null, payerName: this._options.requestPayerName ? this._shippingAddress.recipient : null, payerPhone: this._options.requestPayerPhone ? this._shippingAddress.phone : null, payerEmail: IS_ANDROID && this._options.requestPayerEmail ? details.payerEmail : null }); return this._acceptPromiseResolver(paymentResponse); } _handleGatewayError(details) { return this._acceptPromiseRejecter(new GatewayError(details.error)); } _closePaymentRequest() { this._state = 'closed'; this._acceptPromiseRejecter(new Error('AbortError')); // Remove event listeners before aborting. this._removeEventListeners(); } _removeEventListeners() { // Internal Events this._userDismissSubscription.remove(); this._userAcceptSubscription.remove(); if (IS_IOS) { this._shippingAddressChangeSubscription.remove(); this._shippingOptionChangeSubscription.remove(); } } // https://www.w3.org/TR/payment-request/#onshippingaddresschange-attribute // https://www.w3.org/TR/payment-request/#onshippingoptionchange-attribute addEventListener(eventName, fn) { if (eventName === SHIPPING_ADDRESS_CHANGE_EVENT) { return this._shippingAddressChangeFn = fn.bind(this); } if (eventName === SHIPPING_OPTION_CHANGE_EVENT) { return this._shippingOptionChangeFn = fn.bind(this); } } // https://www.w3.org/TR/payment-request/#shippingaddress-attribute get shippingAddress() { return this._shippingAddress; } // https://www.w3.org/TR/payment-request/#shippingoption-attribute get shippingOption() { return this._shippingOption; } // https://www.w3.org/TR/payment-request/#show-method show() { this._acceptPromise = new Promise((resolve, reject) => { this._acceptPromiseResolver = resolve; this._acceptPromiseRejecter = reject; if (this._state !== 'created') { return reject(new Error('InvalidStateError')); } this._state = 'interactive'; // These arguments are passed because on Android we don't call createPaymentRequest. const platformMethodData = getPlatformMethodData(JSON.parse(this._serializedMethodData), Platform.OS); const normalizedDetails = convertDetailAmountsToString(this._details); const options = this._options; // Note: resolve will be triggered via _acceptPromiseResolver() from somwhere else return NativePayments.show(platformMethodData, normalizedDetails, options).catch(reject); }); return this._acceptPromise; } // https://www.w3.org/TR/payment-request/#abort-method abort() { return new Promise((resolve, reject) => { // We can't abort if the PaymentRequest isn't shown or already closed if (this._state !== 'interactive') { return reject(new Error('InvalidStateError')); } // Try to dismiss the UI NativePayments.abort().then(_bool => { this._closePaymentRequest(); // Return `undefined` as proposed in the spec. return resolve(undefined); }).catch(_err => reject(new Error('InvalidStateError'))); }); } // https://www.w3.org/TR/payment-request/#canmakepayment-method canMakePayments() { return NativePayments.canMakePayments(getPlatformMethodData(JSON.parse(this._serializedMethodData), Platform.OS)); } static canMakePaymentsUsingNetworks = NativePayments.canMakePaymentsUsingNetworks; } //# sourceMappingURL=index.js.map