stripe-react-native icon indicating copy to clipboard operation
stripe-react-native copied to clipboard

Free trials are active after payment sheet is open before collecting card details.

Open YoucefBen47 opened this issue 1 year ago • 5 comments

Describe the bug We use Stripe for monthly and yearly subcriptions. We have a free trial and once the payment sheet is open the subscription is set to active before even collecting the card details or clicking on the Set-Up button on the payment sheet. This is the front-end code: ` const response = await expressApi.post( '/mobile-subs/create-subscription', { customerId: resultGetUser?.data?.stripe_customer_id, priceId: priceId, isTrial: isTrial, }, { headers: { authtoken: token, }, }, ); const {data} = response; if (isTrial) { if (response.status !== 200) return Alert.alert(data.message); const clientSecret = data.clientSecret; const initSheet = await stripe.initPaymentSheet({ setupIntentClientSecret: clientSecret, merchantDisplayName: 'Company name', }); if (initSheet.error) return Alert.alert(initSheet.error.message); const presentSheet = await stripe.presentPaymentSheet({ clientSecret, });

    if (presentSheet.error)
     //handle error

    const result = await expressApi.post(
      'subs/subscription-status',
      {
        stripeId: user.user.stripe_customer_id,
        userId: user?.user?._id,
      },
      {
        headers: {
          authtoken: token,
        },
      },
    );
  }

`

This is the backend code: ` if (isTrial === true) { try { const subscription = await stripe.subscriptions.create({ customer: customerId, items: [ { price: priceId, }, ], payment_behavior: "default_incomplete", payment_settings: { save_default_payment_method: "on_subscription" }, expand: ["pending_setup_intent"], trial_period_days: 7, });

  res.send({
    subscriptionId: subscription.id,
    clientSecret: subscription?.pending_setup_intent?.client_secret,
  });
} catch (error) {
  return res.status(400).send({ error: { message: error.message } });
}

} `

Expected behavior The subscription should be created as a setup intent but not be active until the user fills the card details and clicks on the Set up button.

YoucefBen47 avatar Sep 17 '23 06:09 YoucefBen47

+1 it's not clear by the docs how we should be handling subscription intent, we've almost exactly copied this code.

However if a user goes to our subscription page await stripe.subscriptions.create gets called (as it's prefetching before presentPaymentSheet is called) and a new subscription object gets generated every time.

cstoneham avatar Sep 20 '23 13:09 cstoneham

Did some of you manage to fix this pls?

samvoults avatar Sep 27 '23 11:09 samvoults

@samvoults unfortunately not yet.

YoucefBen47 avatar Sep 28 '23 10:09 YoucefBen47

@charliecruzan-stripe is this an error or just the way stripe works ? Can we have an answer please ? We keep getting many duplicates of active free trials.

YoucefBen47 avatar Oct 09 '23 06:10 YoucefBen47

@samvoults @cstoneham here's how I solved it.

  1. I setup an intent to collect the payment method: This function in the backend receives the customer id and creates a setup intent.
export const setUpUserIntent = async (req, res) => {
const customerId = req.body.customerId;
try {
const ephemeralKey = await stripe.ephemeralKeys.create(
{ customer: customerId },
{ apiVersion: "2020-08-27" }
);
const setupIntent = await stripe.setupIntents.create({
customer: customerId,
});
return res.json({
setupIntent: setupIntent.client_secret,
ephemeralKey: ephemeralKey.secret,
customer: customerId,
setupIntentId: setupIntent.id,
});
} catch (err) {
console.log(err);
}
};
  1. I init the payment sheet and then present it using the returned intent data.
const {setupIntent, ephemeralKey, customer, setupIntentId} =
await setUpSubscriptionIntent(
customer_id,
);
const initSheet = await stripe.initPaymentSheet({
customerId: customer,
customerEphemeralKeySecret: ephemeralKey,
setupIntentClientSecret: setupIntent,
merchantDisplayName: 'company inc',
allowsDelayedPaymentMethods: true,
});
if (initSheet?.error) {
...
}
const presentIntentSheet = await stripe.presentPaymentSheet();
  1. I pass the returned created intent id and use it to retrieve the setup intent object which contains the id of the card and then I use that card id as default_payment_method for the subscription.
const setupIntent = await stripe.setupIntents.retrieve(setupIntentId);
const payment_method_id = setupIntent.payment_method;
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [
{
price: priceId,
},
],
payment_behavior: "default_incomplete",
payment_settings: { save_default_payment_method: "on_subscription" },
expand: ["pending_setup_intent"],
default_payment_method: payment_method_id,
trial_period_days: 7,
});

This way the free trial gets created only after the user sets up an intent with his card.

YoucefBen47 avatar Oct 13 '23 15:10 YoucefBen47