diff --git a/android/build.gradle b/android/build.gradle
index 6b42efd43..72e28df49 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -128,7 +128,7 @@ dependencies {
// noinspection GradleDynamicVersion
api 'com.facebook.react:react-native:+'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
- implementation 'com.github.stripe:stripe-android:96fd89ead5'
+ implementation 'com.github.stripe:stripe-android:cc1dc067e9'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
diff --git a/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt b/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt
index 5d4115be8..bd36bf6a7 100644
--- a/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt
+++ b/android/src/main/java/com/reactnativestripesdk/PaymentMethodCreateParamsFactory.kt
@@ -15,6 +15,7 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private
PaymentMethod.Type.Alipay -> createAlipayPaymentConfirmParams()
PaymentMethod.Type.Sofort -> createSofortPaymentConfirmParams()
PaymentMethod.Type.Bancontact -> createBancontactPaymentConfirmParams()
+ PaymentMethod.Type.Oxxo -> createOXXOPaymentConfirmParams()
PaymentMethod.Type.Giropay -> createGiropayPaymentConfirmParams()
PaymentMethod.Type.Eps -> createEPSPaymentConfirmParams()
PaymentMethod.Type.GrabPay -> createGrabPayPaymentConfirmParams()
@@ -253,6 +254,24 @@ class PaymentMethodCreateParamsFactory(private val clientSecret: String, private
)
}
+ @Throws(PaymentMethodCreateParamsException::class)
+ private fun createOXXOPaymentConfirmParams(): ConfirmPaymentIntentParams {
+ val billingDetails = billingDetailsParams?.let { it } ?: run {
+ throw PaymentMethodCreateParamsException("You must provide billing details")
+ }
+ if (urlScheme == null) {
+ throw PaymentMethodCreateParamsException("You must provide urlScheme")
+ }
+ val params = PaymentMethodCreateParams.createOxxo(billingDetails)
+
+ return ConfirmPaymentIntentParams
+ .createWithPaymentMethodCreateParams(
+ paymentMethodCreateParams = params,
+ clientSecret = clientSecret,
+ returnUrl = mapToReturnURL(urlScheme)
+ )
+ }
+
@Throws(PaymentMethodCreateParamsException::class)
private fun createEPSPaymentConfirmParams(): ConfirmPaymentIntentParams {
val billingDetails = billingDetailsParams?.let { it } ?: run {
diff --git a/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt b/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt
index a71110e4e..23bd36a85 100644
--- a/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt
+++ b/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt
@@ -61,6 +61,16 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
confirmPromise?.resolve(mapFromPaymentIntentResult(paymentIntent))
handleCardActionPromise?.resolve(mapFromPaymentIntentResult(paymentIntent))
}
+ StripeIntent.Status.RequiresAction -> {
+ if (isPaymentIntentNextActionVoucherBased(paymentIntent.nextActionType)) {
+ confirmPromise?.resolve(mapFromPaymentIntentResult(paymentIntent))
+ handleCardActionPromise?.resolve(mapFromPaymentIntentResult(paymentIntent))
+ } else {
+ val errorMessage = paymentIntent.lastPaymentError?.message.orEmpty()
+ confirmPromise?.reject(ConfirmPaymentErrorType.Canceled.toString(), errorMessage)
+ handleCardActionPromise?.reject(NextPaymentActionErrorType.Canceled.toString(), errorMessage)
+ }
+ }
StripeIntent.Status.RequiresPaymentMethod -> {
val errorMessage = paymentIntent.lastPaymentError?.message.orEmpty()
confirmPromise?.reject(ConfirmPaymentErrorType.Failed.toString(), errorMessage)
@@ -123,6 +133,16 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
)
}
+ /// 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.
+ private fun isPaymentIntentNextActionVoucherBased(nextAction: StripeIntent.NextActionType?): Boolean {
+ nextAction?.let {
+ return it == StripeIntent.NextActionType.DisplayOxxoDetails
+ }
+ return false
+ }
+
@ReactMethod
fun initialise(params: ReadableMap) {
val publishableKey = getValOr(params,"publishableKey", null) as String
diff --git a/docs/oxxo/accept-a-payment.md b/docs/oxxo/accept-a-payment.md
new file mode 100644
index 000000000..11fdac22e
--- /dev/null
+++ b/docs/oxxo/accept-a-payment.md
@@ -0,0 +1,191 @@
+# OXXO payments
+
+Use the Payment Intents and Payment Methods APIs to accept OXXO, a common payment method in Mexico.
+
+## 1. Setup Stripe
+
+The React Native SDK is open source and fully documented. Under the hood it uses native Android and iOS SDKs.
+
+To install the SDK run the following command in your terminal:
+
+```sh
+yarn add stripe-react-native
+or
+npm install stripe-react-native
+```
+
+For iOS you will have to run `pod install` inside `ios` directory in order to install needed native dependencies. Android won't require any additional steps.
+
+Configure the SDK with your Stripe [publishable key](https://dashboard.stripe.com/account/apikeys) so that it can make requests to the Stripe API. In order to do that use `StripeProvider` component in the root component of your application.
+
+```tsx
+import { StripeProvider } from 'stripe-react-native';
+
+function App() {
+ return (
+
+ // Your app code here
+
+ );
+}
+```
+
+## 2. Create a PaymentIntent
+
+### Server side
+
+Stripe uses a PaymentIntent object to represent your intent to collect payment from a customer, tracking your charge attempts and payment state changes throughout the process.
+
+### Client side
+
+On the client, request a PaymentIntent from your server and store its client secret.
+
+```tsx
+const fetchPaymentIntentClientSecret = async () => {
+ const response = await fetch(`${API_URL}/create-payment-intent`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ email,
+ currency: 'mxn',
+ items: [{ id: 'id' }],
+ payment_method_types: ['oxxo'],
+ }),
+ });
+ const { clientSecret, error } = await response.json();
+
+ return { clientSecret, error };
+};
+```
+
+## 3. Collect payment method details
+
+In your app, collect your customer’s full name and email address.
+
+```tsx
+export default function OxxoPaymentScreen() {
+ const [name, setName] = useState();
+ const [email, setEmail] = useState();
+
+ const handlePayPress = async () => {
+ // ...
+ };
+
+ return (
+
+ setName(value.nativeEvent.text)}
+ />
+ setEmail(value.nativeEvent.text)}
+ />
+
+ );
+}
+```
+
+## 4. Submit the payment to Stripe
+
+Retrieve the client secret from the PaymentIntent you created in step 2 and call `confirmPayment` method. This presents a webview where the customer can complete the payment on their bank’s website or app. Afterwards, the promise will be resolved with the result of the payment.
+
+The Stripe React Native SDK specifies `safepay/` as the host for the return URL for bank redirect methods. After the customer completes their payment with Bancontact, your app will be opened with `myapp://safepay/` where `myapp` is your custom URL scheme.
+
+```tsx
+export default function OxxoPaymentScreen() {
+ const [name, setName] = useState();
+ const [email, setEmail] = useState();
+
+ const handlePayPress = async () => {
+ const billingDetails: PaymentMethodCreateParams.BillingDetails = {
+ name,
+ email,
+ };
+ };
+
+ const { error, paymentIntent } = await confirmPayment(clientSecret, {
+ type: 'Oxxo',
+ billingDetails,
+ });
+
+ if (error) {
+ Alert.alert(`Error code: ${error.code}`, error.message);
+ console.log('Payment confirmation error', error.message);
+ } else if (paymentIntent) {
+ if (paymentIntent.status === PaymentIntents.Status.RequiresAction) {
+ Alert.alert(
+ 'Success',
+ `The OXXO voucher was created successfully. Awaiting payment from customer.`
+ );
+ } else {
+ Alert.alert('Payment intent status:', paymentIntent.status);
+ }
+ }
+};
+
+ return (
+
+ setName(value.nativeEvent.text)}
+ />
+ setEmail(value.nativeEvent.text)}
+ />
+
+ );
+}
+```
+
+## 5. Handle deep linking
+
+To handle deep linking for bank redirect and wallet payment methods, your app will need to register a custom url scheme. If you're using Expo, [set your scheme](https://docs.expo.io/guides/linking/#in-a-standalone-app) in the `app.json` file.
+
+Otherwise, follow the React Native Linking module [docs](https://reactnative.dev/docs/linking) to configure deep linking. For more information on native URL schemes, refer to the native [Android](https://developer.android.com/training/app-links/deep-linking) and [iOS](https://developer.apple.com/documentation/xcode/allowing_apps_and_websites_to_link_to_your_content/defining_a_custom_url_scheme_for_your_app) docs.
+
+Once your scheme is configured, you can specify a callback to handle the URLs:
+
+```tsx
+import React, { useEffect } from 'react';
+import { useNavigation } from '@react-navigation/native';
+import { useStripe } from 'stripe-react-native';
+
+import { Linking } from 'react-native';
+// For Expo use this import instead:
+// import * as Linking from 'expo-linking';
+
+export default function HomeScreen() {
+ const navigation = useNavigation();
+ const { handleURLCallback } = useStripe();
+
+ const handleDeepLink = async () => {
+ if (url && url.includes(`safepay`)) {
+ await handleURLCallback(url);
+ navigation.navigate('PaymentResultScreen', { url });
+ }
+ };
+
+ useEffect(() => {
+ const getUrlAsync = async () => {
+ const initialUrl = await Linking.getInitialURL();
+ handleDeepLink(initialUrl);
+ };
+ getUrlAsync();
+
+ const urlCallback = (event) => {
+ handleDeepLink(event.url);
+ };
+
+ Linking.addEventListener('url', urlCallback);
+ return () => Linking.removeEventListener('url', urlCallback);
+ }, []);
+
+ return {/* ... */};
+}
+```
+
+## 6. Handle post-payment events
diff --git a/example/ios/Podfile b/example/ios/Podfile
index 6538d2791..b485404d7 100644
--- a/example/ios/Podfile
+++ b/example/ios/Podfile
@@ -8,13 +8,13 @@ target 'StripeSdkExample' do
use_react_native!(:path => config["reactNativePath"])
pod 'stripe-react-native', :path => '../..'
- pod 'Stripe', :git => 'https://github.com/thorsten-stripe/stripe-ios', :branch => 'fix/ideal-mandate-data'
+ pod 'Stripe', :git => 'https://github.com/thorsten-stripe/stripe-ios', :branch => 'fix/voucher-intent-status'
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable these next few lines.
-
+
# Workaround for https://github.com/facebook/react-native/issues/30836
use_flipper!({ 'Flipper-Folly' => '2.3.0' })
post_install do |installer|
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index 0e54ee658..3e9b75978 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -371,7 +371,7 @@ DEPENDENCIES:
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- - Stripe (from `https://github.com/thorsten-stripe/stripe-ios`, branch `fix/ideal-mandate-data`)
+ - Stripe (from `https://github.com/thorsten-stripe/stripe-ios`, branch `fix/voucher-intent-status`)
- stripe-react-native (from `../..`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@@ -454,7 +454,7 @@ EXTERNAL SOURCES:
RNScreens:
:path: "../node_modules/react-native-screens"
Stripe:
- :branch: fix/ideal-mandate-data
+ :branch: fix/voucher-intent-status
:git: https://github.com/thorsten-stripe/stripe-ios
stripe-react-native:
:path: "../.."
@@ -463,7 +463,7 @@ EXTERNAL SOURCES:
CHECKOUT OPTIONS:
Stripe:
- :commit: a1b2e5b483b4f4ddbff21746afc0eec8a44891dd
+ :commit: 3f3be7230fb95002851a124dd3e978494450da30
:git: https://github.com/thorsten-stripe/stripe-ios
SPEC CHECKSUMS:
@@ -514,6 +514,6 @@ SPEC CHECKSUMS:
Yoga: 4bd86afe9883422a7c4028c00e34790f560923d6
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
-PODFILE CHECKSUM: 4b2c8c8456cd09b6f08da3604c6082e9a4e4ee81
+PODFILE CHECKSUM: 0d9b057f12ef1795e2beda2c4c7812e048942979
COCOAPODS: 1.10.1
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 28e8c6289..fff14a0b2 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -21,6 +21,7 @@ import SofortSetupFuturePaymentScreen from './screens/SofortSetupFuturePaymentSc
import FPXPaymentScreen from './screens/FPXPaymentScreen';
import BancontactPaymentScreen from './screens/BancontactPaymentScreen';
import BancontactSetupFuturePaymentScreen from './screens/BancontactSetupFuturePaymentScreen';
+import OxxoPaymentScreen from './screens/OxxoPaymentScreen';
import GiropayPaymentScreen from './screens/GiropayPaymentScreen';
import EPSPaymentScreen from './screens/EPSPaymentScreen';
import GrabPayPaymentScreen from './screens/GrabPayPaymentScreen';
@@ -144,6 +145,10 @@ export default function App() {
name="BancontactSetupFuturePaymentScreen"
component={BancontactSetupFuturePaymentScreen}
/>
+
+
+
+