From 769cb42fc9b75f85adbed124756ffd922436ecdd Mon Sep 17 00:00:00 2001 From: Arkadiusz Kubaczkowski Date: Tue, 20 Jul 2021 19:26:13 +0200 Subject: [PATCH 01/38] chore: google pay new approach implementation --- android/build.gradle | 2 +- .../com/reactnativestripesdk/Constants.kt | 5 + .../java/com/reactnativestripesdk/Errors.kt | 4 + .../reactnativestripesdk/GooglePayFragment.kt | 106 ++++++++++++++ .../PaymentMethodCreateParamsFactory.kt | 19 +-- .../reactnativestripesdk/StripeSdkModule.kt | 130 +++++++++++++++++- example/server/index.ts | 2 + example/src/App.tsx | 2 + example/src/screens/GooglePayScreen.tsx | 114 +++++++++++++++ example/src/screens/HomeScreen.tsx | 11 ++ src/NativeStripeSdk.tsx | 9 ++ src/functions.ts | 72 +++++++++- src/hooks/useGooglePay.tsx | 55 ++++++++ src/hooks/useStripe.tsx | 40 +++++- src/index.tsx | 1 + src/types/Errors.ts | 6 + src/types/GooglePay.ts | 25 ++++ src/types/index.ts | 28 ++++ 18 files changed, 596 insertions(+), 35 deletions(-) create mode 100644 android/src/main/java/com/reactnativestripesdk/GooglePayFragment.kt create mode 100644 example/src/screens/GooglePayScreen.tsx create mode 100644 src/hooks/useGooglePay.tsx create mode 100644 src/types/GooglePay.ts diff --git a/android/build.gradle b/android/build.gradle index b1d451bbd..e1871297b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -131,7 +131,7 @@ dependencies { // noinspection GradleDynamicVersion api 'com.facebook.react:react-native:+' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'com.stripe:stripe-android:16.10.2' + implementation 'com.stripe:stripe-android:17.0.0' implementation 'com.google.android.material:material:1.3.0' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' diff --git a/android/src/main/java/com/reactnativestripesdk/Constants.kt b/android/src/main/java/com/reactnativestripesdk/Constants.kt index 94791591c..f615e5f59 100644 --- a/android/src/main/java/com/reactnativestripesdk/Constants.kt +++ b/android/src/main/java/com/reactnativestripesdk/Constants.kt @@ -5,3 +5,8 @@ var ON_PAYMENT_OPTION_ACTION = "com.reactnativestripesdk.PAYMENT_OPTION_ACTION" var ON_CONFIGURE_FLOW_CONTROLLER = "com.reactnativestripesdk.CONFIGURE_FLOW_CONTROLLER_ACTION" var ON_INIT_PAYMENT_SHEET = "com.reactnativestripesdk.INIT_PAYMENT_SHEET" var ON_FRAGMENT_CREATED = "com.reactnativestripesdk.FRAGMENT_CREATED_ACTION" + +var ON_GOOGLE_PAY_FRAGMENT_CREATED = "com.reactnativestripesdk.ON_GOOGLE_PAY_FRAGMENT_CREATED" +var ON_INIT_GOOGLE_PAY = "com.reactnativestripesdk.ON_INIT_GOOGLE_PAY" +var ON_GOOGLE_PAY_RESULT = "com.reactnativestripesdk.ON_GOOGLE_PAY_RESULT" +var ON_GOOGLE_PAYMENT_METHOD_RESULT = "com.reactnativestripesdk.ON_GOOGLE_PAYMENT_METHOD_RESULT" diff --git a/android/src/main/java/com/reactnativestripesdk/Errors.kt b/android/src/main/java/com/reactnativestripesdk/Errors.kt index eea67eb43..8b8229356 100644 --- a/android/src/main/java/com/reactnativestripesdk/Errors.kt +++ b/android/src/main/java/com/reactnativestripesdk/Errors.kt @@ -37,6 +37,10 @@ enum class PaymentSheetErrorType { Failed, Canceled } +enum class GooglePayErrorType { + Failed, Canceled, Unknown +} + internal fun mapError(code: String, message: String?, localizedMessage: String?, declineCode: String?, type: String?, stripeErrorCode: String?): WritableMap { val map: WritableMap = WritableNativeMap() val details: WritableMap = WritableNativeMap() diff --git a/android/src/main/java/com/reactnativestripesdk/GooglePayFragment.kt b/android/src/main/java/com/reactnativestripesdk/GooglePayFragment.kt new file mode 100644 index 000000000..7a1d36199 --- /dev/null +++ b/android/src/main/java/com/reactnativestripesdk/GooglePayFragment.kt @@ -0,0 +1,106 @@ +package com.reactnativestripesdk + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.fragment.app.Fragment +import com.facebook.react.bridge.ReadableMap +import com.stripe.android.googlepaylauncher.GooglePayEnvironment +import com.stripe.android.googlepaylauncher.GooglePayLauncher +import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher + +class GooglePayFragment : Fragment() { + private var googlePayLauncher: GooglePayLauncher? = null + private var googlePayMethodLauncher: GooglePayPaymentMethodLauncher? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return FrameLayout(requireActivity()).also { + it.visibility = View.GONE + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val testEnv = arguments?.getBoolean("testEnv") + val createPaymentMethod = arguments?.getBoolean("createPaymentMethod") + val merchantName = arguments?.getString("merchantName").orEmpty() + val countryCode = arguments?.getString("countryCode").orEmpty() + + if (createPaymentMethod == true) { + googlePayMethodLauncher = GooglePayPaymentMethodLauncher( + fragment = this, + config = GooglePayPaymentMethodLauncher.Config( + environment = if (testEnv == true) GooglePayEnvironment.Test else GooglePayEnvironment.Production, + merchantCountryCode = "FR", + merchantName = "Widget Store" + ), + readyCallback = ::onGooglePayReady, + resultCallback = ::onGooglePayResult + ) + } else { + googlePayLauncher = GooglePayLauncher( + fragment = this, + config = GooglePayLauncher.Config( + environment = if (testEnv == true) GooglePayEnvironment.Test else GooglePayEnvironment.Production, + merchantCountryCode = countryCode, + merchantName = merchantName + ), + readyCallback = ::onGooglePayReady, + resultCallback = ::onGooglePayResult + ) + } + + val intent = Intent(ON_GOOGLE_PAY_FRAGMENT_CREATED) + activity?.sendBroadcast(intent) + } + + fun payWithGoogle(clientSecret: String) { + if (googlePayLauncher == null) { + val intent = Intent(ON_GOOGLE_PAY_RESULT) + intent.putExtra("error", "GooglePayLauncher is not initialized. Please make sure that createPaymentMethod option is set to false") + activity?.sendBroadcast(intent) + return + } + googlePayLauncher?.presentForPaymentIntent(clientSecret) + } + + fun createPaymentMethod(currencyCode: String, amount: Int) { + if (googlePayMethodLauncher == null) { + val intent = Intent(ON_GOOGLE_PAYMENT_METHOD_RESULT) + intent.putExtra("error", "GooglePayPaymentMethodLauncher is not initialized. Please make sure that createPaymentMethod option is set to true") + activity?.sendBroadcast(intent) + return + } + googlePayMethodLauncher?.present( + currencyCode = currencyCode, + amount = amount + ) + } + + private fun onGooglePayReady(isReady: Boolean) { + val intent = Intent(ON_INIT_GOOGLE_PAY) + intent.putExtra("isReady", isReady) + activity?.sendBroadcast(intent) + } + + private fun onGooglePayResult(result: GooglePayLauncher.Result) { + val intent = Intent(ON_GOOGLE_PAY_RESULT) + + intent.putExtra("paymentResult", result) + activity?.sendBroadcast(intent) + } + + private fun onGooglePayResult(result: GooglePayPaymentMethodLauncher.Result) { + val intent = Intent(ON_GOOGLE_PAYMENT_METHOD_RESULT) + + intent.putExtra("paymentResult", result) + activity?.sendBroadcast(intent) + } +} diff --git a/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt b/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt index 917320c08..0e4877b55 100644 --- a/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt +++ b/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt @@ -3,7 +3,7 @@ package com.reactnativestripesdk import com.facebook.react.bridge.ReadableMap import com.stripe.android.model.* -class PaymentMethodCreateParamsFactory(private val clientSecret: String, private val params: ReadableMap, private val urlScheme: String?, cardParams: PaymentMethodCreateParams.Card?) { +class PaymentMethodCreateParamsFactory(private val clientSecret: String, private val params: ReadableMap, cardParams: PaymentMethodCreateParams.Card?) { private val billingDetailsParams = mapToBillingDetails(getMapOrNull(params, "billingDetails")) private val cardParams = cardParams @@ -66,7 +66,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private paymentMethodCreateParams = createParams, clientSecret = clientSecret, setupFutureUsage = setupFutureUsage, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -82,7 +81,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private .createWithPaymentMethodCreateParams( paymentMethodCreateParams = params, clientSecret = clientSecret, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -106,7 +104,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private paymentMethodOptions = paymentMethodOptionParams, clientSecret = clientSecret, setupFutureUsage = setupFutureUsage, - returnUrl = mapToReturnURL(urlScheme) ) } else { var card = cardParams @@ -119,7 +116,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private paymentMethodCreateParams = paymentMethodCreateParams, clientSecret = clientSecret, setupFutureUsage = setupFutureUsage, - returnUrl = mapToReturnURL(urlScheme) ) } } @@ -134,7 +130,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private return ConfirmSetupIntentParams.create( paymentMethodCreateParams = createParams, clientSecret = clientSecret, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -185,7 +180,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private paymentMethodCreateParams = params, clientSecret = clientSecret, setupFutureUsage = setupFutureUsage, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -202,7 +196,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private return ConfirmSetupIntentParams.create( paymentMethodCreateParams = params, clientSecret = clientSecret, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -215,7 +208,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private .createWithPaymentMethodCreateParams( paymentMethodCreateParams = params, clientSecret = clientSecret, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -233,7 +225,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private paymentMethodCreateParams = params, clientSecret = clientSecret, setupFutureUsage = setupFutureUsage, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -248,7 +239,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private .create( paymentMethodCreateParams = params, clientSecret = clientSecret, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -264,7 +254,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private .createWithPaymentMethodCreateParams( paymentMethodCreateParams = params, clientSecret = clientSecret, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -280,7 +269,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private .createWithPaymentMethodCreateParams( paymentMethodCreateParams = params, clientSecret = clientSecret, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -296,7 +284,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private .createWithPaymentMethodCreateParams( paymentMethodCreateParams = params, clientSecret = clientSecret, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -333,7 +320,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private .createWithPaymentMethodCreateParams( paymentMethodCreateParams = params, clientSecret = clientSecret, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -349,7 +335,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private .createWithPaymentMethodCreateParams( paymentMethodCreateParams = params, clientSecret = clientSecret, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -378,7 +363,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private .createWithPaymentMethodCreateParams( paymentMethodCreateParams = params, clientSecret = clientSecret, - returnUrl = mapToReturnURL(urlScheme) ) } @@ -407,7 +391,6 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private .create( paymentMethodCreateParams = params, clientSecret = clientSecret, - returnUrl = mapToReturnURL(urlScheme) ) } } diff --git a/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt b/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt index aa2e30e8b..50ca2631c 100644 --- a/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +++ b/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt @@ -9,9 +9,12 @@ import android.os.AsyncTask import android.os.Bundle import android.os.Parcelable import android.util.Log +import androidx.activity.ComponentActivity import androidx.appcompat.app.AppCompatActivity import com.facebook.react.bridge.* import com.stripe.android.* +import com.stripe.android.googlepaylauncher.GooglePayLauncher +import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher import com.stripe.android.model.* import com.stripe.android.paymentsheet.PaymentSheetResult import com.stripe.android.view.AddPaymentMethodActivityStarter @@ -38,6 +41,10 @@ class StripeSdkModule(reactContext: ReactApplicationContext, cardFieldManager: S private var initPaymentSheetPromise: Promise? = null private var confirmPaymentClientSecret: String? = null + private var googlePayFragment: GooglePayFragment? = null + private var initGooglePayPromise: Promise? = null + private var presentGooglePayPromise: Promise? = null + private val mActivityEventListener = object : BaseActivityEventListener() { override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) { if (::stripe.isInitialized) { @@ -122,6 +129,7 @@ class StripeSdkModule(reactContext: ReactApplicationContext, cardFieldManager: S }) paymentSheetFragment?.activity?.activityResultRegistry?.dispatchResult(requestCode, resultCode, data) + googlePayFragment?.activity?.activityResultRegistry?.dispatchResult(requestCode, resultCode, data) try { val result = AddPaymentMethodActivityStarter.Result.fromIntent(data) @@ -155,6 +163,51 @@ class StripeSdkModule(reactContext: ReactApplicationContext, cardFieldManager: S ) } + private val googlePayReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent) { + if (intent.action == ON_GOOGLE_PAY_FRAGMENT_CREATED) { + googlePayFragment = (currentActivity as AppCompatActivity).supportFragmentManager.findFragmentByTag("google_pay_launch_fragment") as GooglePayFragment + } + if (intent.action == ON_INIT_GOOGLE_PAY) { + initGooglePayPromise?.resolve(WritableNativeMap()) + } + if (intent.action == ON_GOOGLE_PAYMENT_METHOD_RESULT) { + intent.extras?.getString("error")?.let { + presentGooglePayPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), it)) + return + } + when (val result = intent.extras?.getParcelable("paymentResult")) { + is GooglePayPaymentMethodLauncher.Result.Completed -> { + presentGooglePayPromise?.resolve(createResult("paymentMethod", mapFromPaymentMethod(result.paymentMethod))) + } + GooglePayPaymentMethodLauncher.Result.Canceled -> { + presentGooglePayPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), "Google Pay has been canceled")) + } + is GooglePayPaymentMethodLauncher.Result.Failed -> { + presentGooglePayPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), result.error)) + } + } + } + if (intent.action == ON_GOOGLE_PAY_RESULT) { + intent.extras?.getString("error")?.let { + presentGooglePayPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), it)) + return + } + when (val result = intent.extras?.getParcelable("paymentResult")) { + GooglePayLauncher.Result.Completed -> { + presentGooglePayPromise?.resolve(WritableNativeMap()) + } + GooglePayLauncher.Result.Canceled -> { + presentGooglePayPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), "Google Pay has been canceled")) + } + is GooglePayLauncher.Result.Failed -> { + presentGooglePayPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), result.error)) + } + } + } + } + } + private val mPaymentSheetReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent) { if (intent.action == ON_FRAGMENT_CREATED) { @@ -238,6 +291,11 @@ class StripeSdkModule(reactContext: ReactApplicationContext, cardFieldManager: S this.currentActivity?.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_FRAGMENT_CREATED)); this.currentActivity?.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_INIT_PAYMENT_SHEET)); + this.currentActivity?.registerReceiver(googlePayReceiver, IntentFilter(ON_GOOGLE_PAY_FRAGMENT_CREATED)) + this.currentActivity?.registerReceiver(googlePayReceiver, IntentFilter(ON_INIT_GOOGLE_PAY)) + this.currentActivity?.registerReceiver(googlePayReceiver, IntentFilter(ON_GOOGLE_PAY_RESULT)) + this.currentActivity?.registerReceiver(googlePayReceiver, IntentFilter(ON_GOOGLE_PAYMENT_METHOD_RESULT)) + promise.resolve(null) } @@ -308,11 +366,12 @@ class StripeSdkModule(reactContext: ReactApplicationContext, cardFieldManager: S private fun onFpxPaymentMethodResult(result: AddPaymentMethodActivityStarter.Result) { when (result) { is AddPaymentMethodActivityStarter.Result.Success -> { - stripe.confirmPayment(currentActivity!!, + val activity = currentActivity as ComponentActivity + + stripe.confirmPayment(activity, ConfirmPaymentIntentParams.createWithPaymentMethodId( result.paymentMethod.id!!, confirmPaymentClientSecret!!, - returnUrl = mapToReturnURL(urlScheme) )); } is AddPaymentMethodActivityStarter.Result.Failure -> { @@ -405,7 +464,7 @@ class StripeSdkModule(reactContext: ReactApplicationContext, cardFieldManager: S @ReactMethod fun handleCardAction(paymentIntentClientSecret: String, promise: Promise) { - val activity = currentActivity + val activity = currentActivity as ComponentActivity if (activity != null) { handleCardActionPromise = promise stripe.handleNextActionForPayment(activity, paymentIntentClientSecret) @@ -432,12 +491,13 @@ class StripeSdkModule(reactContext: ReactApplicationContext, cardFieldManager: S return } - val factory = PaymentMethodCreateParamsFactory(paymentIntentClientSecret, params, urlScheme, cardParams) + val factory = PaymentMethodCreateParamsFactory(paymentIntentClientSecret, params, cardParams) try { + val activity = currentActivity as ComponentActivity val confirmParams = factory.createConfirmParams(paymentMethodType) confirmParams.shipping = mapToShippingDetails(getMapOrNull(params, "shippingDetails")) - stripe.confirmPayment(currentActivity!!, confirmParams) + stripe.confirmPayment(activity, confirmParams) } catch (error: PaymentMethodCreateParamsException) { promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), error)) } @@ -479,16 +539,72 @@ class StripeSdkModule(reactContext: ReactApplicationContext, cardFieldManager: S return } - val factory = PaymentMethodCreateParamsFactory(setupIntentClientSecret, params, urlScheme, cardParams) + val factory = PaymentMethodCreateParamsFactory(setupIntentClientSecret, params, cardParams) try { + val activity = currentActivity as ComponentActivity val confirmParams = factory.createSetupParams(paymentMethodType) - stripe.confirmSetupIntent(currentActivity!!, confirmParams) + stripe.confirmSetupIntent(activity, confirmParams) } catch (error: PaymentMethodCreateParamsException) { promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), error)) } } + @ReactMethod + fun initGooglePay(params: ReadableMap, promise: Promise) { + val activity = currentActivity as AppCompatActivity? + + if (activity == null) { + promise.resolve(createError(GooglePayErrorType.Failed.toString(), "Activity doesn't exist")) + return + } + val testEnv = getBooleanOrNull(params, "testEnv") ?: false + val countryCode = getValOr(params, "countryCode", null) + val merchantName = getValOr(params, "merchantName", null) + val createPaymentMethod = getBooleanOrFalse(params, "createPaymentMethod") + + val fragment = GooglePayFragment().also { + val bundle = Bundle() + bundle.putBoolean("testEnv", testEnv) + bundle.putBoolean("createPaymentMethod", createPaymentMethod) + bundle.putString("countryCode", countryCode) + bundle.putString("merchantName", merchantName) + + it.arguments = bundle + } + + initGooglePayPromise = promise + + activity.supportFragmentManager.beginTransaction() + .add(fragment, "google_pay_launch_fragment") + .commit() + } + + @ReactMethod + fun payWithGoogle(params: ReadableMap, promise: Promise) { + val clientSecret = getValOr(params, "clientSecret") ?: run { + promise.resolve(createError(GooglePayErrorType.Failed.toString(), "you must provide clientSecret")) + return + } + presentGooglePayPromise = promise + googlePayFragment?.payWithGoogle(clientSecret) + } + + @ReactMethod + fun createGooglePayPaymentMethod(params: ReadableMap, promise: Promise) { + val currencyCode = getValOr(params, "currencyCode", null) ?: run { + promise.resolve(createError(GooglePayErrorType.Failed.toString(), "you must provide currencyCode")) + return + } + val amount = getIntOrNull(params, "amount") ?: run { + promise.resolve(createError(GooglePayErrorType.Failed.toString(), "you must provide amount")) + return + } + presentGooglePayPromise = promise + googlePayFragment?.createPaymentMethod(currencyCode, amount) + } + + /// Check paymentIntent.nextAction is voucher-based payment method. /// If it's voucher-based, the paymentIntent status stays in requiresAction until the voucher is paid or expired. /// Currently only OXXO payment is voucher-based. diff --git a/example/server/index.ts b/example/server/index.ts index 07779aaeb..32be5ed5a 100644 --- a/example/server/index.ts +++ b/example/server/index.ts @@ -25,6 +25,7 @@ app.use( if (req.originalUrl === '/webhook') { next(); } else { + /* @ts-ignore */ bodyParser.json()(req, res, next); } } @@ -331,6 +332,7 @@ app.post('/create-setup-intent', async (req, res) => { app.post( '/webhook', // Use body-parser to retrieve the raw body as a buffer. + /* @ts-ignore */ bodyParser.raw({ type: 'application/json' }), async (req: express.Request, res: express.Response): Promise => { // Retrieve the event by verifying the signature using the raw body and secret. diff --git a/example/src/App.tsx b/example/src/App.tsx index 6bcdddbe2..7871c8c1f 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -31,6 +31,7 @@ import P24PaymentScreen from './screens/P24PaymentScreen'; import AuBECSDebitPaymentScreen from './screens/AuBECSDebitPaymentScreen'; import AfterpayClearpayPaymentScreen from './screens/AfterpayClearpayPaymentScreen'; import AuBECSDebitSetupPaymentScreen from './screens/AuBECSDebitSetupPaymentScreen'; +import GooglePayScreen from './screens/GooglePayScreen'; const Stack = createStackNavigator(); @@ -151,6 +152,7 @@ export default function App() { name="AfterpayClearpayPaymentScreen" component={AfterpayClearpayPaymentScreen} /> + diff --git a/example/src/screens/GooglePayScreen.tsx b/example/src/screens/GooglePayScreen.tsx new file mode 100644 index 000000000..44075b759 --- /dev/null +++ b/example/src/screens/GooglePayScreen.tsx @@ -0,0 +1,114 @@ +import React, { useEffect, useState } from 'react'; +import { useGooglePay } from '@stripe/stripe-react-native'; +import PaymentScreen from '../components/PaymentScreen'; +import { API_URL } from '../Config'; +import Button from '../components/Button'; +import { Alert, StyleSheet, View } from 'react-native'; + +export default function GooglePayScreen() { + const { + initGooglePay, + payWithGoogle, + loading, + createGooglePayPaymentMethod, + } = useGooglePay(); + const [initialized, setInitialized] = useState(false); + + const fetchPaymentIntentClientSecret = async () => { + const response = await fetch(`${API_URL}/create-payment-intent`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + currency: 'usd', + items: [{ id: 'id' }], + force3dSecure: true, + }), + }); + const { clientSecret } = await response.json(); + + return clientSecret; + }; + + const pay = async () => { + // 2. Fetch payment intent client secret + const clientSecret = await fetchPaymentIntentClientSecret(); + + // 3. Open Google Pay sheet and proceed a payment + const { error } = await payWithGoogle({ + clientSecret, + }); + + if (error) { + Alert.alert(error.code, error.message); + return; + } + Alert.alert('Success', 'The payment was confirmed successfully.'); + setInitialized(false); + }; + + const createPaymentMethod = async () => { + const { error, paymentMethod } = await createGooglePayPaymentMethod({ + amount: 12, + currencyCode: 'USD', + }); + + if (error) { + Alert.alert(error.code, error.message); + return; + } else if (paymentMethod) { + Alert.alert( + 'Success', + `The payment method was created successfully. paymentMethodId: ${paymentMethod.id}` + ); + } + setInitialized(false); + }; + + useEffect(() => { + // 1. Initialize Google Pay + async function initialize() { + const { error } = await initGooglePay({ + testEnv: true, + merchantName: 'Test', + countryCode: 'US', + createPaymentMethod: true, + }); + + if (error) { + Alert.alert(error.code, error.message); + return; + } + setInitialized(true); + } + initialize(); + }, [initGooglePay]); + + return ( + +