diff --git a/CHANGELOG.md b/CHANGELOG.md index 711bb6c75..c20518c05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### New features +- Add `returnURL` as an optional parameter to `handleNextAction`. Use this so the Stripe SDK can redirect back to your app after authentication. [#1104](https://github.com/stripe/stripe-react-native/pull/1104) + ## Fixes - Fixed an issue where some promises on Android would never resolve when using React Native 0.65.x or under. [#1089](https://github.com/stripe/stripe-react-native/pull/1089). diff --git a/example/server/index.ts b/example/server/index.ts index 92bf422e0..4e3292e52 100644 --- a/example/server/index.ts +++ b/example/server/index.ts @@ -294,6 +294,7 @@ app.post( }, use_stripe_sdk: useStripeSdk, customer: customers.data[0].id, + return_url: 'stripe-example://stripe-redirect', }; const intent = await stripe.paymentIntents.create(params); return res.send(generateResponse(intent)); @@ -308,6 +309,7 @@ app.post( // If a mobile client passes `useStripeSdk`, set `use_stripe_sdk=true` // to take advantage of new authentication features in mobile SDKs. use_stripe_sdk: useStripeSdk, + return_url: 'stripe-example://stripe-redirect', }; const intent = await stripe.paymentIntents.create(params); // After create, if the PaymentIntent's status is succeeded, fulfill the order. diff --git a/example/src/screens/NoWebhookPaymentScreen.tsx b/example/src/screens/NoWebhookPaymentScreen.tsx index 603f7c00f..0d83895f1 100644 --- a/example/src/screens/NoWebhookPaymentScreen.tsx +++ b/example/src/screens/NoWebhookPaymentScreen.tsx @@ -104,7 +104,8 @@ export default function NoWebhookPaymentScreen() { if (clientSecret && requiresAction) { // 4. if payment requires action calling handleNextAction const { error: nextActionError, paymentIntent } = await handleNextAction( - clientSecret + clientSecret, + 'stripe-example://stripe-redirect' ); if (nextActionError) { diff --git a/ios/StripeSdk.m b/ios/StripeSdk.m index a8c2f16c5..a1676e9b5 100644 --- a/ios/StripeSdk.m +++ b/ios/StripeSdk.m @@ -71,6 +71,7 @@ @interface RCT_EXTERN_MODULE(StripeSdk, RCTEventEmitter) RCT_EXTERN_METHOD( handleNextAction:(NSString *)paymentIntentClientSecret + returnURL:(NSString *)returnURL resolver: (RCTPromiseResolveBlock)resolve rejecter: (RCTPromiseRejectBlock)reject ) diff --git a/ios/StripeSdk.swift b/ios/StripeSdk.swift index 41ddbcfcc..ad329d23e 100644 --- a/ios/StripeSdk.swift +++ b/ios/StripeSdk.swift @@ -684,14 +684,15 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi } } - @objc(handleNextAction:resolver:rejecter:) + @objc(handleNextAction:returnURL:resolver:rejecter:) func handleNextAction( paymentIntentClientSecret: String, + returnURL: String?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ){ let paymentHandler = STPPaymentHandler.shared() - paymentHandler.handleNextAction(forPayment: paymentIntentClientSecret, with: self, returnURL: nil) { status, paymentIntent, handleActionError in + paymentHandler.handleNextAction(forPayment: paymentIntentClientSecret, with: self, returnURL: returnURL) { status, paymentIntent, handleActionError in switch (status) { case .failed: resolve(Errors.createError(ErrorType.Failed, handleActionError)) diff --git a/src/NativeStripeSdk.tsx b/src/NativeStripeSdk.tsx index 2a3a1f562..5b5dbe468 100644 --- a/src/NativeStripeSdk.tsx +++ b/src/NativeStripeSdk.tsx @@ -38,7 +38,8 @@ type NativeStripeSdkType = { options: PaymentMethod.CreateOptions ): Promise; handleNextAction( - paymentIntentClientSecret: string + paymentIntentClientSecret: string, + returnURL?: string | null ): Promise; confirmPayment( paymentIntentClientSecret: string, diff --git a/src/functions.ts b/src/functions.ts index 7e31ce3b5..d18566f9d 100644 --- a/src/functions.ts +++ b/src/functions.ts @@ -37,6 +37,7 @@ import { CanAddCardToWalletResult, FinancialConnections, } from './types'; +import { Platform } from 'react-native'; const APPLE_PAY_NOT_SUPPORTED_MESSAGE = 'Apple pay is not supported on this device'; @@ -142,9 +143,9 @@ export const retrieveSetupIntent = async ( /** * Confirm and, if necessary, authenticate a PaymentIntent. * - * @param paymentIntentClientSecret The client_secret of the associated [PaymentIntent](https://stripe.com/docs/api/payment_intents). - * @param params An optional object that contains data related to the payment method used to confirm this payment. If no object is provided (undefined), then it is assumed that the payment method has already been [attached to the Payment Intent](https://stripe.com/docs/api/payment_intents/create#create_payment_intent-payment_method). - * @param options An optional object that contains options for this payment method. + * @param {string} paymentIntentClientSecret The client_secret of the associated [PaymentIntent](https://stripe.com/docs/api/payment_intents). + * @param {object=} params An optional object that contains data related to the payment method used to confirm this payment. If no object is provided (undefined), then it is assumed that the payment method has already been [attached to the Payment Intent](https://stripe.com/docs/api/payment_intents/create#create_payment_intent-payment_method). + * @param {object=} options An optional object that contains options for this payment method. * @returns A promise that resolves to an object containing either a `paymentIntent` field, or an `error` field. */ export const confirmPayment = async ( @@ -257,13 +258,24 @@ export const confirmApplePayPayment = async ( } }; +/** Handles any nextAction required to authenticate the PaymentIntent. + * Call this method if you are using manual confirmation. See https://stripe.com/docs/payments/accept-a-payment?platform=react-native&ui=custom + * + * @param {string} paymentIntentClientSecret The client secret associated with the PaymentIntent. + * @param {string=} returnURL An optional return URL so the Stripe SDK can redirect back to your app after authentication. This should match the `return_url` you specified during PaymentIntent confirmation. + * */ export const handleNextAction = async ( - paymentIntentClientSecret: string + paymentIntentClientSecret: string, + returnURL?: string ): Promise => { try { - const { paymentIntent, error } = await NativeStripeSdk.handleNextAction( - paymentIntentClientSecret - ); + const { paymentIntent, error } = + Platform.OS === 'ios' + ? await NativeStripeSdk.handleNextAction( + paymentIntentClientSecret, + returnURL ?? null + ) + : await NativeStripeSdk.handleNextAction(paymentIntentClientSecret); if (error) { return { error, @@ -579,7 +591,7 @@ export const collectBankAccountForSetup = async ( /** * Use collectBankAccountToken in the [Add a Financial Connections Account to a US Custom Connect](https://stripe.com/docs/financial-connections/connect-payouts) account flow. * When called, it will load the Authentication Flow, an on-page modal UI which allows your user to securely link their external financial account for payouts. - * @param clientSecret The client_secret of the [Financial Connections Session](https://stripe.com/docs/api/financial_connections/session). + * @param {string} clientSecret The client_secret of the [Financial Connections Session](https://stripe.com/docs/api/financial_connections/session). * @returns A promise that resolves to an object containing either `session` and `token` fields, or an error field. */ export const collectBankAccountToken = async ( @@ -608,7 +620,7 @@ export const collectBankAccountToken = async ( /** * Use collectFinancialConnectionsAccounts in the [Collect an account to build data-powered products](https://stripe.com/docs/financial-connections/other-data-powered-products) flow. * When called, it will load the Authentication Flow, an on-page modal UI which allows your user to securely link their external financial account. - * @param clientSecret The client_secret of the [Financial Connections Session](https://stripe.com/docs/api/financial_connections/session). + * @param {string} clientSecret The client_secret of the [Financial Connections Session](https://stripe.com/docs/api/financial_connections/session). * @returns A promise that resolves to an object containing either a `session` field, or an error field. */ export const collectFinancialConnectionsAccounts = async ( diff --git a/src/hooks/useStripe.tsx b/src/hooks/useStripe.tsx index 2c46f6979..23d72054e 100644 --- a/src/hooks/useStripe.tsx +++ b/src/hooks/useStripe.tsx @@ -157,9 +157,10 @@ export function useStripe() { const _handleNextAction = useCallback( async ( - paymentIntentClientSecret: string + paymentIntentClientSecret: string, + returnURL?: string ): Promise => { - return handleNextAction(paymentIntentClientSecret); + return handleNextAction(paymentIntentClientSecret, returnURL); }, [] );