stripe / stripe-react-native

React Native library for Stripe.
https://stripe.dev/stripe-react-native
MIT License
1.28k stars 264 forks source link

confirmSetupIntent with PayPal paymentMethodType #1562

Closed omaSpencer closed 4 months ago

omaSpencer commented 11 months ago

Describe the bug I created a SetupIntent on the server side for off-session usage with the PayPal payment method and encountered an error when attempting to confirm the intent. My goal is to collect customer details from PayPal (payer_id, payer_email) similar to the confirmPlatformPaySetupIntent.

To Reproduce server side:

const stripe = new Stripe(
  'sk_test_....',
  {
    apiVersion: '2023-10-16',
    typescript: true,
  }
);
app.post('/create-setup-intent', async (req, res) => {
  let setupIntent = null;

  try {
    const { offSession, type, userAgent, ipAddress } = req.body;

    const customers = await stripe.customers.list();
    const customer = customers.data.find(
      (customer) => customer.id === 'cus_Opt76NQecXwbL1' //? just mock
    );

    if (!customer) {
      throw new Error('Customer not found');
    }

    if (type === 'paypal') {
      setupIntent = await stripe.setupIntents.create({
        usage: offSession ? 'off_session' : 'on_session',
        payment_method_types: ['paypal'],
        payment_method_data: {
          type: 'paypal',
        },
        customer: customer.id,
        confirm: true,
        return_url: 'example://stripe/paypal',
        mandate_data: {
          customer_acceptance: {
            type: 'online',
            online: {
              ip_address: ipAddress,
              user_agent: userAgent,
            },
          },
        },
      });
    } else {
      setupIntent = await stripe.setupIntents.create({
        usage: offSession ? 'off_session' : 'on_session',
      });
    }

    res.status(200).json({
      paymentIntent: setupIntent.client_secret,
      paymentIntentId: setupIntent.id,
      type,
    });
  } catch (error: any) {
    console.error(
      error?.raw?.message ?? error?.message ?? error.toString().toUpperCase()
    );
    res.json({
      error: 'Create intent for off-session Platform payment failed.',
    });
  }
});

client side:

const createSetupIntent = async () => {
    try {
      const res = await fetch('http://localhost:8000/create-setup-intent', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          offSession: true,
          type: 'paypal',
          userAgent,
          ipAddress: netInfo.ipAddress,
        }),
      });
      const { paymentIntent, paymentIntentId, type } = await res.json();

      if (!paymentIntentId || !paymentIntent) {
        throw new Error('Invalid payment intent');
      }

      setOffSessionPayment((prev) => ({
        ...prev,
        started: true,
        completed: false,
        type,
        paymentIntent: {
          ...prev.paymentIntent,
          status: 'created',
          id: paymentIntentId,
          secret: paymentIntent,
        },
      }));
    } catch (err: any) {
      console.error(err);

      const error =
        err?.raw?.message ?? err?.message ?? err ?? 'Something went wrong';

      setOffSessionPayment((prev) => ({
        ...prev,
        error,
        started: false,
        completed: false,
      }));
    }
  };

const handleConfirmSetupIntent = async () => {
    const { paymentIntent, type } = offSessionPayment;

    if (!paymentIntent || !paymentIntent?.secret) return;

    if (type === 'platform_pay') {
      try {
        const { error, setupIntent } = await confirmPlatformPaySetupIntent(
          paymentIntent.secret,
          {
            applePay: {
              currencyCode: 'usd',
              merchantCountryCode: 'DE',
              cartItems: [
                {
                  label: 'Test',
                  amount: '10.00',
                  paymentType: 'Immediate' as any,
                },
              ],
              requiredBillingContactFields: [
                PlatformPay.ContactField.Name,
                PlatformPay.ContactField.PostalAddress,
                PlatformPay.ContactField.EmailAddress,
                PlatformPay.ContactField.PhoneNumber,
              ],
              requiredShippingAddressFields: [
                PlatformPay.ContactField.Name,
                PlatformPay.ContactField.PostalAddress,
                PlatformPay.ContactField.EmailAddress,
                PlatformPay.ContactField.PhoneNumber,
              ],
            },
            googlePay: {
              testEnv: true,
              currencyCode: 'usd',
              merchantCountryCode: 'DE',
              amount: 10,
              label: 'Test',
              allowCreditCards: true,
              billingAddressConfig: {
                format: PlatformPay.BillingAddressFormat.Full,
                isRequired: true,
              },
            },
          }
        );

        if (error) {
          console.error(error);
          Alert.alert('Error', error.message);
        } else {
          console.log(setupIntent);

          const { paymentMethodId, paymentMethodTypes, usage, status } =
            setupIntent ?? {};

          if (!paymentMethodId) throw new Error('Invalid payment method id');

          setOffSessionPayment((prev) => ({
            ...prev,
            paymentMethod: {
              id: paymentMethodId,
              types: paymentMethodTypes,
              status,
              usage,
            },
          }));

          createPaymentMethod(paymentMethodId);
        }
      } catch (error) {
        console.error(error);
      }
    } else {
      const { error, setupIntent } = await confirmSetupIntent(
        paymentIntent.secret,
        {
          paymentMethodType: 'PayPal',
          paymentMethodData: {
            mandateData: {
              customerAcceptance: {
                online: {
                  ipAddress: netInfo.ipAddress,
                  userAgent: userAgent!,
                },
              },
            },
          },
        },
        {
          setupFutureUsage: 'OffSession',
        }
      );

      if (error) {
        console.error(error);
      } else {
        console.log(setupIntent);
      }
    }
  };

I encountered an error while attempting to confirm the SetupIntent: {"code": "Failed", "declineCode": null, "localizedMessage": "There was an unexpected error -- try again in a few seconds", "message": "To confirm the SetupIntent with the PayPal payment method type, you need to provide a 'return_url' address.", "stripeErrorCode": "", "type": "invalid_request_error"}

Expected behavior I want to confirm the SetupIntent and retrieve customer PayPal details for future use as a PaymentMethod.

Smartphone (please complete the following information):

Additional context This is simply a mock app for a proof of concept, hence the use of 'any' in various places.

hansemannn commented 11 months ago

+1, this is only happening with Paypal right now.

iranaahsanali commented 5 months ago

I am facing this issue for iDeal & Paypal. On test environments, payments were working fine. On production environment I am facing this issue.

dtn1999 commented 5 months ago

I have the same issue. In development mode, the integration works fine. However, when switching to the live mode, I face the issue.

dtn1999 commented 4 months ago

@iranaahsanali I was able to get it working. After connecting PayPal as a payment method to your Stripe account, it is mandatory to configure recurring payments for it. The following link explains how to do that. You typically get feedback within 5 days. I got feedback, and everything is working fine now.

iranaahsanali commented 4 months ago

@dtn1999 Thanks for detailed steps, I did the same thing and everything worked for me as well.

YannisHofmann commented 2 months ago

I am facing the same issue when trying to confirm a SetupIntent with the PayPal payment method. The problem occurs in both the test and production environments, even though I have enabled PayPal recurring payments.

Here's the code snippets I am using:

server side:

const paymentIntent = await stripe.setupIntents.create({
  usage: "off_session",
  payment_method_types: ["paypal"],
  payment_method_data: {
    type: "paypal",
  },
  customer: "cus_P8h2aYiovSpx4Y",
  confirm: true,
  return_url: "https://yourdomain.com/return-url",
  mandate_data: {
    customer_acceptance: {
      type: "online",
      online: {
        ip_address: ipAddress,
        user_agent: userAgent,
      },
    },
  },
});

client side:

await confirmSetupIntent(clientSecret,
  {
    paymentMethodType: "PayPal",
  },
  {
    setupFutureUsage: "OffSession",
  }
);

I am consistently getting the following error message: {"code": "Failed", "declineCode": null, "localizedMessage": "There was an unexpected error -- try again in a few seconds", "message": "To confirm the SetupIntent with the PayPal payment method type, you need to provide a 'return_url' address.", "stripeErrorCode": "", "type": "invalid_request_error"}

Additional Information Stripe API Version: 16.6.0 React Native SDK Version: ^0.38.3 Environment: Both Test and Production