react-nativeexpostripe-payments3d-secure

This payment type is not supported yet 3D Secure stripe payment gateway in react native


I'm trying to implement a 3D Secure payment method in the stripe payment gateway in react native.

The code is working fine for the normal 4242 xxxx series. But this is throwing an error when I put a 3D Secure card.

const PaymentCompleteScreen = () => {  
  const publishableKey ="";

  const handlePayment = async () => {
    setLoading(true);
    try {
      const response = await createPaymentMethod({
        type: "Card",
        card: cardDetails,
        cvc: cardDetails.cvc,
        billingDetails: billingDetails,
      }) //Creating a normal payment method to extract paymentMethodId
        .then(async (result) => {
          if (result.error) {
            setLoading(false);
            Alert.alert("Error", result.error.message);
          } else {
            const paymentMethodId = result.paymentMethod
              ? result.paymentMethod.id
              : null;

            const createSub = await createSubscription(
              paymentMethodId,
              user?.stripeId,
              selectedGroups[0].priceId
            ); //Passing the paymentMethodId to complete the payment.

            if (createSub.status) {
              await deviceStorage.saveItem("cart", JSON.stringify([]));
              Alert.alert(
                "Payment Successful",
                "Your payment was successful. Login again to access the premium group."
              ); //Success
              setLoading(false);
            } else if (createSub.requires_action) //3D Secure
             {
              const { error, paymentIntent } = await confirmPayment(
                createSub.payment_intent_client_secret, //I get this from my backend
                {
                  type: "card",
                }
              );

              console.log(error);

              if (error) {
                setLoading(false);
                Alert.alert("Error", error.code + " " + error.message);
              } else if (paymentIntent) {
                Alert.alert(
                  "Success",
                  "Your payment was successful. Login again to access the premium 
                  group."
                );
                setLoading(false);
              }
            }
          }
        })
        .catch((error) => {
          console.log(error);
        });

      setLoading(false);
    } catch (err) {
      console.log(err.message);
    }
  };

  return (
    <View style={{ flex: 1, padding: 20 }}>
      <Text
        style={{
          fontFamily: "Roboto-Regular",
          fontSize: themeStyles.FONT_SIZE_MEDIUM,
          marginTop: 5,
        }}
      >
        Enter your card details to finish the payment
      </Text>
      <StripeProvider publishableKey={publishableKey}>
        <CardField
          onCardChange={setCardDetails}
          postalCodeEnabled={false}
          autofocus={true}
          cardStyle={{
            backgroundColor: "#FFFFFF",
            textColor: "#000000",
          }}
          style={{
            width: "100%",
            height: 50,
            marginVertical: 30,
          }}
        />
        <StripeButton
          variant="primary"
          loading={loading}
          title="Pay"
          disabled={!cardDetails.complete}
          onPress={() => handlePayment()}
        />
      </StripeProvider>
    </View>
  );
};

In the handlePayment() method I'm doing two things.

  1. First creating a payment method and pass it to the createSubscription method which calls my backend for creating a subscription.
  2. If I get a status as a response, then the payment is successful else some action is required to be taken.
  3. With the require action key, I also pass an object like this ๐Ÿ‘‡
Object {
  "data": Object {
    "invoice_id": "in_zzz",
    "subscription_id": "sub_zzz",
  },
  "payment_intent_client_secret": "pi_zzz",
  "requires_action": true,
}

Any help to solve this will be a great improvement in this project ๐Ÿ™


Solution

  • The actual issue here is that type in the confirmPayment() method is case-sensitive. Instead of type: "card" you are required to use type: "Card". You can see this is case-specific in the SDK here.

    Edit: Below is the initial answer. urlScheme is not actually required to handle the 3DS redirect, however is is highly recommended otherwise the customer will not be returned to your App or a designated success page.

    The issue is that 3DS requires a return URL since the customer will be redirected away from your App to the issuerโ€™s authentication page to handle 3DS authentication and thus you must specify where they should be returned to after completing authentication. You set this return URL via urlScheme when you initialize StripeProvider โ€” you can see an example in the docs here.