UNPKG

@rnw-community/react-native-payments

Version:

Accept Payments with Apple Pay and Android Pay using the Payment Request API.

710 lines (587 loc) 31.4 kB
/* eslint-disable max-lines */ import { Platform } from 'react-native'; import uuidModule from 'react-native-uuid'; import { emptyFn, isDefined, isEmptyArray, isError, isNotEmptyArray, isNotEmptyString } from '@rnw-community/shared'; import { AndroidPaymentMethodTokenizationType } from '../../@standard/android/enum/android-payment-method-tokenization-type.enum.js'; import { defaultAndroidPaymentDataRequest } from '../../@standard/android/request/android-payment-data-request.js'; import { defaultAndroidPaymentMethod } from '../../@standard/android/request/android-payment-method.js'; import { defaultAndroidTransactionInfo } from '../../@standard/android/request/android-transaction-info.js'; import { IOSPKContactField } from '../../@standard/ios/enum/ios-pk-contact-field.enum.js'; import { IosPKMerchantCapability } from '../../@standard/ios/enum/ios-pk-merchant-capability.enum.js'; import { IosPKPaymentNetworksEnum } from '../../@standard/ios/enum/ios-pk-payment-networks.enum.js'; import { PaymentMethodNameEnum } from '../../enum/payment-method-name.enum.js'; import { PaymentsErrorEnum } from '../../enum/payments-error.enum.js'; import { SupportedNetworkEnum } from '../../enum/supported-networks.enum.js'; import { ConstructorError } from '../../error/constructor.error.js'; import { DOMException } from '../../error/dom.exception.js'; import { PaymentsError } from '../../error/payments.error.js'; import { getNativePaymentsEventEmitter } from '../../util/get-native-payments-event-emitter/get-native-payments-event-emitter.util.js'; import { isNativeUserCancellation } from '../../util/is-native-user-cancellation.util.js'; import { resolvePaymentDetailsModifier } from '../../util/resolve-payment-details-modifier.util.js'; import { validateAndroidTransactionInfo } from '../../util/validate-android-transaction-info.util.js'; import { validateDetailsUpdate } from '../../util/validate-details-update.util.js'; import { validateDisplayItems } from '../../util/validate-display-items.util.js'; import { validateModifiers } from '../../util/validate-modifiers.util.js'; import { validatePaymentMethods } from '../../util/validate-payment-methods.util.js'; import { validateShippingOptions } from '../../util/validate-shipping-options.util.js'; import { validateShippingType } from '../../util/validate-shipping-type.util.js'; import { validateTotal } from '../../util/validate-total.util.js'; import { warnChangeEventError } from '../../util/warn-change-event-error.util.js'; import { ChangeEventDispatcher } from '../change-event-dispatcher/change-event-dispatcher.js'; import { NativePayments } from '../native-payments/native-payments.js'; import { AndroidPaymentResponse } from '../payment-response/android-payment-response.js'; import { IosPaymentResponse } from '../payment-response/ios-payment-response.js'; import type { AndroidAllowedCardNetworksEnum } from '../../@standard/android/enum/android-allowed-card-networks.enum.js'; import type { AndroidPaymentMethodDataDataInterface } from '../../@standard/android/mapping/android-payment-method-data-data.interface.js'; import type { AndroidPaymentDataRequest } from '../../@standard/android/request/android-payment-data-request.js'; import type { IosPaymentMethodDataDataInterface } from '../../@standard/ios/mapping/ios-payment-method-data-data.interface.js'; import type { IosPaymentDataRequest } from '../../@standard/ios/request/ios-payment-data-request.js'; import type { PaymentDetailsInit } from '../../@standard/w3c/payment-details-init.js'; import type { PaymentDetailsUpdate } from '../../@standard/w3c/payment-details-update.js'; import type { PaymentItem } from '../../@standard/w3c/payment-item.js'; import type { PaymentMethodData } from '../../@standard/w3c/payment-method-data.js'; import type { NativePaymentDetailsUpdateInterface } from '../../interface/native-payment-details-update.interface.js'; import type { PaymentRequestEventPayloadInterface } from '../../interface/payment-request-event-payload.interface.js'; import type { PaymentRequestEventRegistrationInterface } from '../../interface/payment-request-event-registration.interface.js'; import type { PaymentResponseAddressInterface } from '../../interface/payment-response-address.interface.js'; import type { ResolvedPaymentDetailsInterface } from '../../interface/resolved-payment-details.interface.js'; import type { PaymentMethodChangeEventListener } from '../../type/payment-method-change-event-listener.type.js'; import type { PaymentRequestEventListener } from '../../type/payment-request-event-listener.type.js'; import type { PaymentRequestEventType } from '../../type/payment-request-event.type.js'; import type { PaymentRequestUpdateEvent } from '../payment-request-update-event/payment-request-update-event.js'; import type { Maybe } from '@rnw-community/shared'; import type { EmitterSubscription } from 'react-native'; const uuid = uuidModule as unknown as { v4: () => string }; /* * HINT: Troubleshooting: https://developers.google.com/pay/api/android/support/troubleshooting * HINT: Google Pay API Errors: https://developers.google.com/pay/api/web/reference/error-objects */ export class PaymentRequest { // https://www.w3.org/TR/payment-request/#id-attribute readonly id: string; updating = false; state: 'closed' | 'created' | 'interactive' = 'created'; couponCode: Maybe<string> = null; shippingAddress: Maybe<PaymentResponseAddressInterface> = null; shippingOption: Maybe<string> = null; // Internal Slots https://www.w3.org/TR/payment-request/#internal-slots private readonly serializedMethodData: string; private readonly platformMethodData: AndroidPaymentMethodDataDataInterface | IosPaymentMethodDataDataInterface; private readonly eventRegistrations = new Map<PaymentRequestEventType, PaymentRequestEventRegistrationInterface>(); private readonly pendingDispatchers = new Set<ChangeEventDispatcher>(); private readonly attributeHandlers = new Map< PaymentRequestEventType, PaymentMethodChangeEventListener | PaymentRequestEventListener >(); private readonly attributeHandlerWrappers = new Map<PaymentRequestEventType, PaymentRequestEventListener>(); private eventGeneration = 0; private isNativeEventsSynced = false; private acceptPromiseRejecter: (reason: unknown) => void = emptyFn; constructor( readonly methodData: PaymentMethodData[], public details: PaymentDetailsInit ) { // 3. Establish the request's id: if (!isNotEmptyString(details.id)) { // TODO: Can we avoid using external lib? Use Math.random? details.id = uuid.v4(); } this.id = details.id; this.validateConstructorInputs(methodData, details); // 17. Set request.[[serializedMethodData]] to serializedMethodData. */ this.platformMethodData = this.findPlatformPaymentMethodData(); const nativePlatformMethodData = Platform.OS === 'android' ? this.getAndroidPaymentMethodData( this.platformMethodData as AndroidPaymentMethodDataDataInterface, this.resolveEffectiveDetails(details).total ) : this.getIosPaymentMethodData(this.platformMethodData as IosPaymentMethodDataDataInterface); this.serializedMethodData = JSON.stringify(nativePlatformMethodData); } // https://www.w3.org/TR/payment-request/#dom-paymentrequest-onshippingaddresschange get onshippingaddresschange(): Maybe<PaymentRequestEventListener> { return this.getAttributeHandler('shippingaddresschange') as Maybe<PaymentRequestEventListener>; } // https://www.w3.org/TR/payment-request/#dom-paymentrequest-onshippingoptionchange get onshippingoptionchange(): Maybe<PaymentRequestEventListener> { return this.getAttributeHandler('shippingoptionchange') as Maybe<PaymentRequestEventListener>; } // https://www.w3.org/TR/payment-request/#dom-paymentrequest-onpaymentmethodchange get onpaymentmethodchange(): Maybe<PaymentMethodChangeEventListener> { return this.getAttributeHandler('paymentmethodchange'); } // couponcodechange is a PassKit extension: https://developer.apple.com/documentation/passkit/pkpaymentrequest/3801275-couponcode?language=objc get oncouponcodechange(): Maybe<PaymentRequestEventListener> { return this.getAttributeHandler('couponcodechange') as Maybe<PaymentRequestEventListener>; } set onshippingaddresschange(listener: Maybe<PaymentRequestEventListener>) { this.setAttributeHandler('shippingaddresschange', listener); } set onshippingoptionchange(listener: Maybe<PaymentRequestEventListener>) { this.setAttributeHandler('shippingoptionchange', listener); } set onpaymentmethodchange(listener: Maybe<PaymentMethodChangeEventListener>) { this.setAttributeHandler('paymentmethodchange', listener); } set oncouponcodechange(listener: Maybe<PaymentRequestEventListener>) { this.setAttributeHandler('couponcodechange', listener); } // https://www.w3.org/TR/payment-request/#canmakepayment-method async canMakePayment(): Promise<boolean> { if (this.state !== 'created') { throw new DOMException(PaymentsErrorEnum.InvalidStateError); } return NativePayments.canMakePayments(this.serializedMethodData); } // https://www.w3.org/TR/payment-request/#hasenrolledinstrument-method async hasEnrolledInstrument(): Promise<boolean> { if (this.state !== 'created') { throw new DOMException(PaymentsErrorEnum.InvalidStateError); } return NativePayments.hasEnrolledInstrument(this.serializedMethodData); } // https://www.w3.org/TR/payment-request/#show-method show(): Promise<AndroidPaymentResponse | IosPaymentResponse> { if (this.state !== 'created') { return Promise.reject(new DOMException(PaymentsErrorEnum.InvalidStateError)); } this.state = 'interactive'; this.syncActiveEvents(); const resolvedDetails = this.resolveEffectiveDetails(this.details); // HINT: We need to pass Android environment configuration to native module via details const details = Platform.OS === 'android' ? { ...this.details, environment: (this.platformMethodData as AndroidPaymentMethodDataDataInterface).environment, ...resolvedDetails, } : { ...this.details, ...resolvedDetails }; return new Promise<AndroidPaymentResponse | IosPaymentResponse>((resolve, reject) => { this.acceptPromiseRejecter = reject; NativePayments.show(this.id, this.serializedMethodData, details) .then(jsonDetails => { const paymentResponse = this.handleAccept(jsonDetails); this.closeRequest(); resolve(paymentResponse); return void 0; }) .catch((error: unknown) => { this.closeRequest(); if (isNativeUserCancellation(error)) { reject(new DOMException(PaymentsErrorEnum.AbortError)); return; } reject(isError(error) ? error : new PaymentsError(`Failed showing PaymentRequest`)); }); }); } // https://www.w3.org/TR/payment-request/#abort-method async abort(): Promise<void> { if (this.state !== 'interactive') { throw new DOMException(PaymentsErrorEnum.InvalidStateError); } await NativePayments.abort().catch(() => { throw new PaymentsError(`Failed aborting PaymentRequest`); }); this.closeRequest(); this.acceptPromiseRejecter(new DOMException(PaymentsErrorEnum.AbortError)); } addEventListener(type: 'paymentmethodchange', listener: PaymentMethodChangeEventListener): void; addEventListener(type: PaymentRequestEventType, listener: PaymentRequestEventListener): void; addEventListener( type: PaymentRequestEventType, eventListener: PaymentMethodChangeEventListener | PaymentRequestEventListener ): void { if (this.state === 'closed') { return; } const listener = eventListener as PaymentRequestEventListener; const registration = this.eventRegistrations.get(type); if (isDefined(registration)) { if (!registration.listeners.includes(listener)) { registration.listeners.push(listener); } return; } const listeners = [listener]; this.eventRegistrations.set(type, { listeners, subscription: this.subscribeToNativeEvent(type, listeners) }); this.syncActiveEvents(); } removeEventListener(type: 'paymentmethodchange', listener: PaymentMethodChangeEventListener): void; removeEventListener(type: PaymentRequestEventType, listener: PaymentRequestEventListener): void; removeEventListener( type: PaymentRequestEventType, eventListener: PaymentMethodChangeEventListener | PaymentRequestEventListener ): void { const registration = this.eventRegistrations.get(type); if (!isDefined(registration)) { return; } this.forgetListener(registration, eventListener as PaymentRequestEventListener); if (isNotEmptyArray(registration.listeners)) { return; } this.dropRegistration(type, registration); } private closeRequest(): void { this.state = 'closed'; this.clearEventRegistrations(); } private validateConstructorInputs(methodData: PaymentMethodData[], details: PaymentDetailsInit): void { // 4. Process payment methods validatePaymentMethods(methodData); validateAndroidTransactionInfo(methodData, ConstructorError); validateShippingType(methodData, ConstructorError); // 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(ConstructorError, details.displayItems); validateShippingOptions(ConstructorError, details.shippingOptions); validateModifiers(ConstructorError, details.modifiers); } private getAttributeHandler( type: PaymentRequestEventType ): Maybe<PaymentMethodChangeEventListener | PaymentRequestEventListener> { return this.attributeHandlers.get(type) ?? null; } private setAttributeHandler( type: PaymentRequestEventType, listener: Maybe<PaymentMethodChangeEventListener | PaymentRequestEventListener> ): void { if (!isDefined(listener)) { this.attributeHandlers.delete(type); this.removeAttributeHandlerWrapper(type); return; } this.attributeHandlers.set(type, listener); this.ensureAttributeHandlerWrapper(type); } private ensureAttributeHandlerWrapper(type: PaymentRequestEventType): void { if (this.attributeHandlerWrappers.has(type)) { return; } const wrapper: PaymentRequestEventListener = event => this.dispatchToAttributeHandler(type, event); this.attributeHandlerWrappers.set(type, wrapper); this.addEventListener(type, wrapper); } private removeAttributeHandlerWrapper(type: PaymentRequestEventType): void { const wrapper = this.attributeHandlerWrappers.get(type); if (!isDefined(wrapper)) { return; } this.attributeHandlerWrappers.delete(type); this.removeEventListener(type, wrapper); } private dispatchToAttributeHandler( type: PaymentRequestEventType, event: PaymentRequestUpdateEvent ): Promise<void> | void { return (this.attributeHandlers.get(type) as PaymentRequestEventListener)(event); } private handleAccept(details: string): AndroidPaymentResponse | IosPaymentResponse { return Platform.OS === 'android' ? new AndroidPaymentResponse(this.id, PaymentMethodNameEnum.AndroidPay, details, this.shippingOption) : new IosPaymentResponse(this.id, PaymentMethodNameEnum.ApplePay, details, this.shippingOption); } private subscribeToNativeEvent( type: PaymentRequestEventType, listeners: PaymentRequestEventListener[] ): Maybe<EmitterSubscription> { const eventEmitter = getNativePaymentsEventEmitter(); if (!isDefined(eventEmitter)) { return null; } return eventEmitter.addListener(type, (payload: PaymentRequestEventPayloadInterface) => { this.handleChangeEvent(type, listeners, payload).catch(warnChangeEventError); }); } private dropRegistration(type: PaymentRequestEventType, registration: PaymentRequestEventRegistrationInterface): void { if (isDefined(registration.subscription)) { registration.subscription.remove(); } this.eventRegistrations.delete(type); this.syncActiveEvents(); } private syncActiveEvents(): void { const eventNames = [...this.eventRegistrations.keys()]; const { setActiveEvents } = NativePayments; if (!isDefined(setActiveEvents) || (isEmptyArray(eventNames) && !this.isNativeEventsSynced)) { return; } this.isNativeEventsSynced = isNotEmptyArray(eventNames); setActiveEvents(this.id, eventNames).catch(emptyFn); } private clearEventRegistrations(): void { this.eventGeneration += 1; this.pendingDispatchers.forEach(dispatcher => { dispatcher.abandon(); }); this.pendingDispatchers.clear(); this.eventRegistrations.forEach(registration => { if (isDefined(registration.subscription)) { registration.subscription.remove(); } }); this.eventRegistrations.clear(); this.syncActiveEvents(); } private forgetListener( registration: PaymentRequestEventRegistrationInterface, listener: PaymentRequestEventListener ): void { const listenerIndex = registration.listeners.indexOf(listener); if (listenerIndex >= 0) { registration.listeners.splice(listenerIndex, 1); } } private async handleChangeEvent( type: PaymentRequestEventType, listeners: PaymentRequestEventListener[], payload: PaymentRequestEventPayloadInterface ): Promise<void> { const generation = this.eventGeneration; if (payload.requestId !== this.id || !this.isDispatchActive(generation)) { return; } this.applyEventPayload(payload); if (this.updating) { await this.sendDetailsUpdate(type, payload.eventId, null, generation); return; } this.updating = true; try { await this.dispatchChangeEvent(type, listeners, payload, generation); } finally { this.updating = false; } } private isDispatchActive(generation: number): boolean { return this.state === 'interactive' && this.eventGeneration === generation; } private async dispatchChangeEvent( type: PaymentRequestEventType, listeners: PaymentRequestEventListener[], payload: PaymentRequestEventPayloadInterface, generation: number ): Promise<void> { const dispatcher = new ChangeEventDispatcher(type, payload, () => this.isDispatchActive(generation)); this.pendingDispatchers.add(dispatcher); try { const detailsUpdate = await this.resolveDetailsUpdate(dispatcher, listeners); await this.sendDetailsUpdate(type, payload.eventId, detailsUpdate, generation); } finally { this.pendingDispatchers.delete(dispatcher); } } private applyEventPayload(payload: PaymentRequestEventPayloadInterface): void { if (isDefined(payload.shippingAddress)) { this.shippingAddress = payload.shippingAddress; } if (isDefined(payload.shippingOption)) { this.shippingOption = payload.shippingOption; } if (isDefined(payload.couponCode)) { this.couponCode = payload.couponCode; } } private async resolveDetailsUpdate( dispatcher: ChangeEventDispatcher, listeners: PaymentRequestEventListener[] ): Promise<Maybe<PaymentDetailsUpdate>> { try { const detailsUpdate = await dispatcher.dispatch(listeners); if (isDefined(detailsUpdate)) { validateDetailsUpdate(detailsUpdate); } return detailsUpdate; } catch (error) { warnChangeEventError(error); return null; } } private async sendDetailsUpdate( type: PaymentRequestEventType, eventId: number | undefined, detailsUpdate: Maybe<PaymentDetailsUpdate>, generation: number ): Promise<void> { const { updatePaymentDetails } = NativePayments; if (!this.isDispatchActive(generation) || !isDefined(updatePaymentDetails)) { return; } const updatedDetails = this.getUpdatedDetails(detailsUpdate); const resolvedDetails = this.resolveEffectiveDetails(updatedDetails); const update: NativePaymentDetailsUpdateInterface = { error: detailsUpdate?.error ?? '', eventName: type, requestId: this.id, total: resolvedDetails.total, ...(isDefined(eventId) && { eventId }), }; await updatePaymentDetails(update, resolvedDetails.displayItems, updatedDetails.shippingOptions ?? []); this.details = updatedDetails; } private getUpdatedDetails(detailsUpdate: Maybe<PaymentDetailsUpdate>): PaymentDetailsInit { if (!isDefined(detailsUpdate)) { return this.details; } return { ...this.details, ...(isDefined(detailsUpdate.total) && { total: detailsUpdate.total }), ...(isDefined(detailsUpdate.displayItems) && { displayItems: detailsUpdate.displayItems }), ...(isDefined(detailsUpdate.shippingOptions) && { shippingOptions: detailsUpdate.shippingOptions }), ...(isDefined(detailsUpdate.modifiers) && { modifiers: detailsUpdate.modifiers }), }; } private resolveEffectiveDetails(details: PaymentDetailsInit): ResolvedPaymentDetailsInterface { return resolvePaymentDetailsModifier( this.getPlatformSupportedMethod(), details.total, details.displayItems, details.modifiers ); } private getPlatformSupportedMethod(): PaymentMethodNameEnum { return Platform.OS === 'ios' ? PaymentMethodNameEnum.ApplePay : PaymentMethodNameEnum.AndroidPay; } private findPlatformPaymentMethodData(): AndroidPaymentMethodDataDataInterface | IosPaymentMethodDataDataInterface { const platformSupportedMethod = this.getPlatformSupportedMethod(); const platformMethod = this.methodData.find( paymentMethodData => paymentMethodData.supportedMethods === platformSupportedMethod ); if (!isDefined(platformMethod)) { throw new DOMException(PaymentsErrorEnum.NotSupportedError); } return platformMethod.data; } private getAndroidPaymentMethodData( methodData: AndroidPaymentMethodDataDataInterface, total: PaymentItem ): AndroidPaymentDataRequest { const isBillingRequired = methodData.requestBillingAddress === true || methodData.requestPayerName === true || methodData.requestPayerPhone === true; const totalPriceStatus = methodData.totalPriceStatus ?? defaultAndroidTransactionInfo.totalPriceStatus; return { ...defaultAndroidPaymentDataRequest, merchantInfo: { merchantName: total.label, }, transactionInfo: { ...defaultAndroidTransactionInfo, currencyCode: methodData.currencyCode, totalPrice: total.amount.value, totalPriceLabel: total.label, countryCode: methodData.countryCode, totalPriceStatus, ...(isDefined(methodData.checkoutOption) && { checkoutOption: methodData.checkoutOption }), ...(isDefined(methodData.transactionId) && { transactionId: methodData.transactionId }), }, allowedPaymentMethods: [ { ...defaultAndroidPaymentMethod, parameters: { ...defaultAndroidPaymentMethod.parameters, allowedCardNetworks: methodData.supportedNetworks.map( network => network.toUpperCase() as AndroidAllowedCardNetworksEnum ), allowedAuthMethods: methodData.allowedAuthMethods ?? defaultAndroidPaymentMethod.parameters.allowedAuthMethods, ...(isBillingRequired && { billingAddressRequired: true, billingAddressParameters: { format: methodData.requestBillingAddress === true ? 'FULL' : 'MIN', phoneNumberRequired: methodData.requestPayerPhone === true, }, }), }, ...(isDefined(methodData.gatewayConfig) && { tokenizationSpecification: { parameters: methodData.gatewayConfig, type: AndroidPaymentMethodTokenizationType.PAYMENT_GATEWAY, }, }), ...(isDefined(methodData.directConfig) && { tokenizationSpecification: { parameters: methodData.directConfig, type: AndroidPaymentMethodTokenizationType.DIRECT, }, }), }, ], ...(methodData.requestPayerEmail === true && { emailRequired: true }), ...(methodData.requestShipping === true && { shippingAddressRequired: true, shippingAddressParameters: { phoneNumberRequired: methodData.requestPayerPhone === true, }, }), }; } private getIosPaymentMethodData(methodData: IosPaymentMethodDataDataInterface): IosPaymentDataRequest { // TODO: Add mappings for other systems if needed const supportedNetworkMap: Record<SupportedNetworkEnum, IosPKPaymentNetworksEnum> = { [SupportedNetworkEnum.Amex]: IosPKPaymentNetworksEnum.PKPaymentNetworkAmex, [SupportedNetworkEnum.Mastercard]: IosPKPaymentNetworksEnum.PKPaymentNetworkMasterCard, [SupportedNetworkEnum.Visa]: IosPKPaymentNetworksEnum.PKPaymentNetworkVisa, [SupportedNetworkEnum.Discover]: IosPKPaymentNetworksEnum.PKPaymentNetworkDiscover, [SupportedNetworkEnum.Bancontact]: IosPKPaymentNetworksEnum.PKPaymentNetworkBancontact, [SupportedNetworkEnum.CartesBancaires]: IosPKPaymentNetworksEnum.PKPaymentNetworkCartesBancaires, [SupportedNetworkEnum.ChinaUnionPay]: IosPKPaymentNetworksEnum.PKPaymentNetworkChinaUnionPay, [SupportedNetworkEnum.Dankort]: IosPKPaymentNetworksEnum.PKPaymentNetworkDankort, [SupportedNetworkEnum.Eftpos]: IosPKPaymentNetworksEnum.PKPaymentNetworkEftpos, [SupportedNetworkEnum.Electron]: IosPKPaymentNetworksEnum.PKPaymentNetworkElectron, [SupportedNetworkEnum.Elo]: IosPKPaymentNetworksEnum.PKPaymentNetworkElo, [SupportedNetworkEnum.Girocard]: IosPKPaymentNetworksEnum.PKPaymentNetworkGirocard, [SupportedNetworkEnum.Interac]: IosPKPaymentNetworksEnum.PKPaymentNetworkInterac, [SupportedNetworkEnum.Jcb]: IosPKPaymentNetworksEnum.PKPaymentNetworkJCB, [SupportedNetworkEnum.Mada]: IosPKPaymentNetworksEnum.PKPaymentNetworkMada, [SupportedNetworkEnum.Maestro]: IosPKPaymentNetworksEnum.PKPaymentNetworkMaestro, [SupportedNetworkEnum.Mir]: IosPKPaymentNetworksEnum.PKPaymentNetworkMir, [SupportedNetworkEnum.PrivateLabel]: IosPKPaymentNetworksEnum.PKPaymentNetworkPrivateLabel, [SupportedNetworkEnum.Vpay]: IosPKPaymentNetworksEnum.PKPaymentNetworkVPay, }; const defaultMerchantCapabilities = [ IosPKMerchantCapability.PKMerchantCapability3DS, IosPKMerchantCapability.PKMerchantCapabilityDebit, IosPKMerchantCapability.PKMerchantCapabilityCredit, ]; const requestedShippingFields = this.getRequestedShippingFields(methodData); const isShippingRequested = requestedShippingFields.length > 0; return { countryCode: methodData.countryCode, currencyCode: methodData.currencyCode, merchantIdentifier: methodData.merchantIdentifier, supportedNetworks: methodData.supportedNetworks.map(network => supportedNetworkMap[network]), merchantCapabilities: isNotEmptyArray(methodData.merchantCapabilities) ? methodData.merchantCapabilities : defaultMerchantCapabilities, ...(methodData.requestBillingAddress === true && { requiredBillingContactFields: [IOSPKContactField.PKContactFieldPostalAddress], }), ...(isShippingRequested && { requiredShippingContactFields: requestedShippingFields }), ...(isDefined(methodData.applicationData) && { applicationData: methodData.applicationData }), ...(isNotEmptyString(methodData.couponCode) && { couponCode: methodData.couponCode }), ...(isDefined(methodData.shippingType) && { shippingType: methodData.shippingType }), }; } private getRequestedShippingFields(methodData: IosPaymentMethodDataDataInterface): IOSPKContactField[] { const requiredShippingFields: IOSPKContactField[] = []; if (methodData.requestPayerEmail ?? false) { requiredShippingFields.push(IOSPKContactField.PKContactFieldEmailAddress); } if (methodData.requestPayerName ?? false) { requiredShippingFields.push(IOSPKContactField.PKContactFieldName); } if (methodData.requestPayerPhone ?? false) { requiredShippingFields.push(IOSPKContactField.PKContactFieldPhoneNumber); } if (methodData.requestShipping ?? false) { requiredShippingFields.push(IOSPKContactField.PKContactFieldPostalAddress); } return requiredShippingFields; } }