diff --git a/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt b/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt index c49eb519e..e0633c0a7 100644 --- a/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +++ b/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt @@ -886,6 +886,73 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ return null } + /** + * Custom + */ + + private fun extractPaymentMethodCreateParams(options: ReadableMap, token: String?): PaymentMethodCreateParams { + val cardParams = getMapOrNull(options, "card") + val billingDetailsParams = getMapOrNull(options, "billingDetails") + val addressParams = getMapOrNull(billingDetailsParams, "address") + val address = mapToAddress(addressParams, null) + val billingDetails = PaymentMethod.BillingDetails.Builder() + .setAddress(address) + .setEmail(getValOr(billingDetailsParams, "email")) + .setName(getValOr(billingDetailsParams, "name")) + .setPhone(getValOr(billingDetailsParams, "phone")) + .build() + val card = if (token != null) { + PaymentMethodCreateParams.Card.create(token) + } else { + PaymentMethodCreateParams.Card.Builder() + .setCvc(cardParams?.getString("cvc")) + .setExpiryMonth(cardParams?.getInt("expMonth")) + .setExpiryYear(cardParams?.getInt("expYear")) + .setNumber(cardParams?.getString("number")) + .build() + } + return PaymentMethodCreateParams.create( + card, + billingDetails, + ) + } + + @ReactMethod + fun createPaymentMethodCustomNative(params: ReadableMap, promise: Promise) { + val billingDetailsParams = getMapOrNull(params, "billingDetails") + val addressParams = getMapOrNull(billingDetailsParams, "address") + val cardParamsMap = getMapOrNull(params, "card") + val cardParams = CardParams( + number = getValOr(cardParamsMap, "number") as String, + expMonth = cardParamsMap?.getInt("expMonth") ?: 0, + expYear = cardParamsMap?.getInt("expYear") ?: 0, + cvc = getValOr(cardParamsMap, "cvc", null) as String, + address = mapToAddress(addressParams, null), + name = getValOr(billingDetailsParams, "name"), + ) + + CoroutineScope(Dispatchers.IO).launch { + runCatching { + val token = stripe.createCardToken( + cardParams = cardParams, + stripeAccountId = stripeAccountId + ) + CoroutineScope(Dispatchers.IO).launch { + val pmcp = extractPaymentMethodCreateParams(params, token.id) + runCatching { + val paymentMethod = stripe.createPaymentMethod(pmcp) + val paymentMethodMap: WritableMap = mapFromPaymentMethod(paymentMethod) + promise.resolve(createResult("paymentMethod", paymentMethodMap)) + }.onFailure { + promise.resolve(createError("Failed", it)) + } + } + }.onFailure { + promise.resolve(createError(CreateTokenErrorType.Failed.toString(), it)) + } + } + } + companion object { const val NAME = "StripeSdk" } diff --git a/ios/ApplePayViewController.swift b/ios/ApplePayViewController.swift index d80ade757..4fb112b18 100644 --- a/ios/ApplePayViewController.swift +++ b/ios/ApplePayViewController.swift @@ -14,6 +14,13 @@ extension StripeSdk : PKPaymentAuthorizationViewControllerDelegate, STPApplePayC didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Void ) { + if (self.hasLegacyApplePayListeners) { + // Legacy, remove when useApplePay hook is removed + let contact = payment.shippingContact + if ((contact) != nil) { + sendEvent(withName: "onDidSetShippingContact", body: ["shippingContact": Mappers.mapFromShippingContact(shippingContact: contact!)]) + } + } applePaymentMethodFlowCanBeCanceled = false if (platformPayUsesDeprecatedTokenFlow) { @@ -165,6 +172,13 @@ extension StripeSdk : PKPaymentAuthorizationViewControllerDelegate, STPApplePayC paymentInformation: PKPayment, completion: @escaping STPIntentClientSecretCompletionBlock ) { + if (self.hasLegacyApplePayListeners) { + // Legacy, remove when useApplePay hook is removed + let contact = paymentInformation.shippingContact + if ((contact) != nil) { + sendEvent(withName: "onDidSetShippingContact", body: ["shippingContact": Mappers.mapFromShippingContact(shippingContact: contact!)]) + } + } if let clientSecret = self.confirmApplePayPaymentClientSecret { completion(clientSecret, nil) } else if let clientSecret = self.confirmApplePaySetupClientSecret { diff --git a/ios/StripeSdk.m b/ios/StripeSdk.m index 0bf02417c..1b5b9e786 100644 --- a/ios/StripeSdk.m +++ b/ios/StripeSdk.m @@ -193,4 +193,9 @@ @interface RCT_EXTERN_MODULE(StripeSdk, RCTEventEmitter) resolver: (RCTPromiseResolveBlock)resolve rejecter: (RCTPromiseRejectBlock)reject ) +RCT_EXTERN_METHOD( + createPaymentMethodCustomNative:(NSDictionary *)params + resolver: (RCTPromiseResolveBlock)resolve + rejecter: (RCTPromiseRejectBlock)reject + ) @end diff --git a/ios/StripeSdk.swift b/ios/StripeSdk.swift index 90cd5c130..166ed5844 100644 --- a/ios/StripeSdk.swift +++ b/ios/StripeSdk.swift @@ -1171,6 +1171,66 @@ class StripeSdk: RCTEventEmitter, STPBankSelectionViewControllerDelegate, UIAdap break } } + + // Custom + func extractPaymentMethodCreateParams( + options: NSDictionary, + token: String? + ) -> STPPaymentMethodParams { + let cardParams = options["card"] as? NSDictionary + let billingDetailsParams = options["billingDetails"] as? NSDictionary + let addressParams = billingDetailsParams?["address"] as? NSDictionary + let card = STPPaymentMethodCardParams() + let billingDetails = STPPaymentMethodBillingDetails() + let address = STPPaymentMethodAddress(address: Mappers.mapToAddress(address: addressParams)) + billingDetails.address = address + billingDetails.email = billingDetailsParams?["email"] as? String + billingDetails.name = billingDetailsParams?["name"] as? String + billingDetails.phone = billingDetailsParams?["phone"] as? String + if let token = token { + card.token = token + } else { + card.number = cardParams?["number"] as? String + card.expMonth = cardParams?["expMonth"] as? NSNumber + card.expYear = cardParams?["expYear"] as? NSNumber + card.cvc = cardParams?["cvc"] as? String + } + return STPPaymentMethodParams(card: card, billingDetails: billingDetails, metadata: nil) + } + + @objc(createPaymentMethodCustomNative:resolver:rejecter:) + func createPaymentMethodCustomNative( + params: NSDictionary, + resolver resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock + ) -> Void { + let billingDetailsParams = params["billingDetails"] as? NSDictionary + let addressParams = billingDetailsParams?["address"] as? NSDictionary + let cardParamsMap = params["card"] as? NSDictionary + let cardSourceParams = STPCardParams() + cardSourceParams.number = cardParamsMap?["number"] as? String + cardSourceParams.expMonth = UInt(truncating: cardParamsMap?["expMonth"] as? NSNumber ?? 0) + cardSourceParams.expYear = UInt(truncating: cardParamsMap?["expYear"] as? NSNumber ?? 0) + cardSourceParams.cvc = cardParamsMap?["cvc"] as? String + cardSourceParams.address = Mappers.mapToAddress(address: addressParams) + cardSourceParams.name = billingDetailsParams?["name"] as? String + STPAPIClient.shared.createToken(withCard: cardSourceParams) { token, error in + if let token = token { + let pmcp = self.extractPaymentMethodCreateParams(options: params, token: token.tokenId) + STPAPIClient.shared.createPaymentMethod(with: pmcp) { paymentMethod, error in + if let error = error { + resolve(Errors.createError(ErrorType.Failed, error as NSError)) + return + } + resolve( + Mappers.createResult("paymentMethod", Mappers.mapFromPaymentMethod(paymentMethod)) + ) + } + } else { + resolve(Errors.createError(ErrorType.Failed, error as NSError?)) + } + } + } } func findViewControllerPresenter(from uiViewController: UIViewController) -> UIViewController { diff --git a/jest/mock.js b/jest/mock.js index 5ba27496a..1738c43de 100644 --- a/jest/mock.js +++ b/jest/mock.js @@ -226,4 +226,17 @@ module.exports = { AddToWalletButton: () => 'AddToWalletButton', PlatformPayButton: () => 'PlatformPayButton', useStripe: jest.fn(() => mockHooks), + PlatformPay: { + ContactField: { + EmailAddress: "emailAddress", + Name: "name", + PhoneNumber: "phoneNumber", + PhoneticName: "phoneticName", + PostalAddress: "postalAddress", + }, + BillingAddressFormat: { + Full: "FULL", + Min: "MIN", + }, + }, }; diff --git a/package.json b/package.json index a62db7a82..4809eba42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@stripe/stripe-react-native", - "version": "0.26.0", + "version": "0.26.1", "author": "Stripe", "description": "Stripe SDK for React Native", "main": "lib/commonjs/index", diff --git a/src/NativeStripeSdk.tsx b/src/NativeStripeSdk.tsx index c1b1ac1d8..1659c11a0 100644 --- a/src/NativeStripeSdk.tsx +++ b/src/NativeStripeSdk.tsx @@ -139,6 +139,9 @@ type NativeStripeSdkType = { webServiceUrl: string, authenticationToken: string ): Promise; + createPaymentMethodCustomNative( + params: PaymentMethod.CreateParams + ): Promise; }; const { StripeSdk } = NativeModules; diff --git a/src/functions.ts b/src/functions.ts index a1ecb1602..a32191b94 100644 --- a/src/functions.ts +++ b/src/functions.ts @@ -927,3 +927,13 @@ export const openPlatformPaySetup = async (): Promise => { await NativeStripeSdk.openApplePaySetup(); } }; + +/** + * Use this method to create a payment method from a custom form. + * This include the token creation and payment method creation performed natively by stripe. + */ +export const createPaymentMethodCustom = async ( + params: PaymentMethod.CreateParams +): Promise => { + return await NativeStripeSdk.createPaymentMethodCustomNative(params); +}; diff --git a/src/types/PaymentMethod.ts b/src/types/PaymentMethod.ts index 625d866b7..cfaa151a4 100644 --- a/src/types/PaymentMethod.ts +++ b/src/types/PaymentMethod.ts @@ -71,6 +71,17 @@ export type CardParams = cvc?: string; billingDetails?: BillingDetails; }; + } + | { + paymentMethodType: 'Card'; + billingDetails?: BillingDetails; + card: { + number: string; + cvc: string; + expMonth: number; + expYear: number; + name: string; + }; }; export interface IdealParams { diff --git a/tsconfig.json b/tsconfig.json index 227a64b13..44222af66 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -28,5 +28,8 @@ "node", "jest" ] - } + }, + "exclude": [ + "example" + ] }