stripe-react-native: [ios] `presentPaymentSheet` will crash the app once is being called.

Describe the bug Have been following this tutorial and even though most of the things went pretty well, the app would crash when I am calling the presentPaymentSheet with the clientSecret returned from my backend. There isn’t any log I could find that would help me debug the issue.

Here is my component, I hope pasting the code helps a little bit more. I am using firebase functions as my backend.

import React, {useState, useEffect} from 'react';
import {StyleSheet, Alert} from 'react-native';
import {Text, useTheme} from 'react-native-paper';
import {
  useStripe,
} from '@stripe/stripe-react-native';
import {useProfile} from './ProfileProvider';
import {SafeAreaView} from 'react-native-safe-area-context';
import {FormButton} from '../../components/FormButton';

export default function CardDetailsScreen({navigation}) {
  const {styles} = useStyles();
  const {
    profile: {stripeId},
  } = useProfile();
  const {initPaymentSheet, presentPaymentSheet} = useStripe();
  const [paymentSheetEnabled, setPaymentSheetEnabled] = useState(false);
  const [clientSecret, setClientSecret] = useState();

  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const initialisePaymentSheet = async () => {
      const fetchPaymentSheetParams = async () => {
        try {
          const response = await fetch(
            'https://MY_BACKEND_ENDPOINT.cloudfunctions.net/completePaymentWithStripe',
            {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
              },
              body: JSON.stringify({
                amount: 55 * 100,
                customerStripeId: stripeId,
              }),
            },
          );

          const {paymentIntent, ephemeralKey} = await response.json();
          setClientSecret(paymentIntent);

          return {paymentIntent, ephemeralKey};
        } catch (e) {
          console.log(e);
          debugger;
        }
      };

      const {paymentIntent, ephemeralKey} = await fetchPaymentSheetParams();
      const {error} = await initPaymentSheet({
        customerId: stripeId,
        customerEphemeralKeySecret: ephemeralKey,
        paymentIntentClientSecret: paymentIntent,
      });

      if (!error) {
        setPaymentSheetEnabled(true);
      }
    };

    stripeId && initialisePaymentSheet();
  }, [initPaymentSheet, stripeId]);

  const openPaymentSheet = async () => {
    if (!clientSecret) {
      return;
    }
    setLoading(true);
    try {
      debugger;
      const {error} = await presentPaymentSheet({clientSecret});

      if (error) {
        Alert.alert(`Error code: ${error.code}`, error.message);
      } else {
        Alert.alert('Success', 'Your order is confirmed!');
      }
    } catch (e) {
      debugger;
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <Text>Hello</Text>
      <FormButton
        title="Show Payment Sheet"
        modeValue="contained"
        onPress={openPaymentSheet}
        loading={loading}
        disabled={!paymentSheetEnabled}
      />
    </SafeAreaView>
  );
}

Expected behavior Expected would be to open the sheet like the example image

Desktop (please complete the following information):

  • OS: iOS
  • Version 1.15.7

Smartphone (please complete the following information):

  • Device: iPhone 12
  • OS: 14.4

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 15 (3 by maintainers)

Most upvoted comments

Hey sorry for the super late response @thorsten-stripe but havent had much time lately. I am still facing the same situation. Can you please see if I am missing something or if we can reopen the issue?

PS: you can see my code on the react-native side in the issue description, in case you need it

https://user-images.githubusercontent.com/4181674/121083041-37a31080-c7df-11eb-9a15-ea56e230cb0a.mp4

So I solved it! In my case I was using wix react-native-navigation. And the app crashed when calling the function presentPaymentSheet on a modal. I had to setRoot from the modal to go to the checkout process. That closes all modals which solved the issue.

I’m still getting that error. i tried everything but it doesn’t work. it’s working fine on Android but app freezes on IOS during sheet opening. Screen Shot 2021-07-29 at 3 08 41 PM

Can you provide a log of the values of stripeId, ephemeralKey, paymentIntent, and clientSecret just before you call presentPaymentSheet please?

So I solved it! In my case I was using wix react-native-navigation. And the app crashed when calling the function presentPaymentSheet on a modal. I had to setRoot from the modal to go to the checkout process. That closes all modals which solved the issue.

This tripped me up for awhile too, I think that it would be a good caveat to inform people of that you cannot call presentPaymentSheet from a modal

@thorsten-stripe just for the record your answer was indeed the solution to my issue (using id instead of secret / client_secret), works now thanks

Hi @thorsten-stripe I’m also getting this issue with a slightly varied version of the official example

Here’s my code:

export default function PaymentsUICompleteScreen() {
  const { initPaymentSheet, presentPaymentSheet } = useStripe();
  const [email, setEmail] = useState('');
  const [amount, setAmount] = useState('');

  const fetchPaymentSheetParams = async () => {
    const apiClient = new APIClient(PAYMENT_URL);

    try {
      const response = await apiClient.request(PROCESS_CHARGE, { // Just an API call to my backend endpoint to fetch data returned below
        email,
        amount: Number(amount),
      });

      return {
        paymentIntent: response.intent.id,
        ephemeralKey: response.ephemeralKey.id,
        customer: response.customer.id,
      };
    } catch (err) {
      console.log('fetchPaymentSheetParams err is: ', err);
    }
  };

  const openPaymentSheet = async (paymentIntent: string) => {
    console.log('Inside openPaymentSheet: paymentIntent is: ', paymentIntent);
    if (!paymentIntent) {
      return;
    }
    console.log('Come this far 1');
    try {
      console.log('Come this far 2');
      await presentPaymentSheet({ clientSecret: paymentIntent }); // Fails here...
      console.log('Come this far 3');
    } catch (err) {
      console.log('openPaymentSheet err is: ', err);
    }

    console.log('Come this far 4');
  };

  const initialisePaymentSheet = async () => {
    const { paymentIntent, ephemeralKey, customer } = await fetchPaymentSheetParams();

    console.log('paymentIntent is: ', paymentIntent);
    console.log('ephemeralKey is: ', ephemeralKey);
    console.log('customer is: ', customer);

    try {
      await initPaymentSheet({
        customerId: customer,
        customerEphemeralKeySecret: ephemeralKey,
        paymentIntentClientSecret: paymentIntent,
        customFlow: false,
        merchantDisplayName: 'Example Inc.',
        style: 'alwaysDark',
      });
      openPaymentSheet(paymentIntent);
    } catch (err) {
      console.log('initialisePaymentSheet err is: ', err);
    }
  };

  return (
    <PaymentScreen>
      <Input value={email} onChangeText={setEmail} label="Email" />
      <Input value={amount} onChangeText={setAmount} label="Amount" />
      <Button text="Charge" onPress={initialisePaymentSheet} />
    </PaymentScreen>
  );
}

Here’s the log output I get (in order):

LOG      paymentIntent is:  pi_1IxhTODchL2cGl0JcKZO5U4c
LOG      ephemeralKey is:  ephkey_1IxhTODchL2cGl0JmXxpYtGC
LOG      customer is:  cus_JatFNcTg1T6Dk3
LOG      Inside openPaymentSheet: paymentIntent is:  pi_1IxhTODchL2cGl0JcKZO5U4c
LOG      Come this far 1
LOG      Come this far 2

After the “Come this far 2” log it exits and the app closes (crashes?) with no error or further log/info, so similarly to @paschalidi you can tell it’s crashing because of presentPaymentSheet.

Please let me know if you can see anything wrong whether it be setup or the values being passed in