Stripe Checkout Subscription Payment failure redirects to Expire Link Page

Viewed 879

I'm trying to integrate Stripe One time and Subscription Payment using their Checkout API.

I have also enabled 3D secure payments. In one time payment when payment is successful it redirects to our success page. And when a payment is failed, it shows error message in checkout form. Which is as expected.

In Checkout subscription, when payment is successfully it perfectly redirects to success page. For failed payment it also shows error messages after 3D authentication, but when I try to pay with another card or the same card after a failed attempt, it redirects me to Expired link page.

I checked in stripe demo checkout page (https://checkout.stripe.dev/preview) where it works fine but don't know what I'm missing.

What I understand from the stripe docs is that, for failed payment I should handle error and tell users/redirects to use a different payment method.

I have registered the following webhook events:

  1. invoice.payment_action_required
  2. charge.failed
  3. customer.subscription.deleted
  4. customer.subscription.created
  5. checkout.session.completed
  6. invoice.paid

When invoice.payment_action_required event is triggered, I have to manually confirm the payment for 3D secure authentication (it's required for subscription). I have written the following code to confirm the payment.

if (paymentIntent.status === 'requires_action') {
   await stripe.paymentIntents.confirm(payment_intent);
}

Here is the code snippet to create a subscription session:

const session = await stripePrivate.checkout.sessions.create({
        mode: 'subscription',
        payment_method_types: ['card'],
        line_items: [
          {
            price: planId,
            quantity: 1
          }
        ],
        metadata: {
          transactionId
        },
        subscription_data: {
          metadata: {
            transactionId
          }
        },
        success_url: `${merchantCallbackURL}?sessionId={CHECKOUT_SESSION_ID}&status=success&orderId=${orderId}`,
        cancel_url: `${merchantCallbackURL}?sessionId={CHECKOUT_SESSION_ID}&status=canceled&orderId=${orderId}`
      });

  const callbackUrl = session.url;

And below is the page, I'm redirected to if a subscription payment is failed (from 2nd attempt):

enter image description here

Here I'm adding some checkout URL for testing:

Test card 1: 4000008260003178 (insufficient balance)

Test card 2: 4000002500003155

  1. https://checkout.stripe.com/pay/cs_test_a1SAsf7YCjXOZPNKf0K9AXNDHSm8lMLFwD80VZEajKxEAgpeD9GiZBH2wr#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
  2. https://checkout.stripe.com/pay/cs_test_a1RTXGddpYeZy0zRfvuJrGWqtT3KiURrJFCjSDS9fK8OIhdmTPFBD0Mzx8#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
  3. https://checkout.stripe.com/pay/cs_test_a1ZMzrim1XQWNVgHCceiSw9mjrtMMdTricwdGhzf7wdHYcEsSabFTxRGcv#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
  4. https://checkout.stripe.com/pay/cs_test_a1iheiAZbEXl3hhuVPBSNARja4XkYL2su4bt0JRqlNQMaVnd4V2Hg5BEWD#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
  5. https://checkout.stripe.com/pay/cs_test_a1iheiAZbEXl3hhuVPBSNARja4XkYL2su4bt0JRqlNQMaVnd4V2Hg5BEWD#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl

Above URL will be expired within 24 hours. Please ask if you need another active url for testing.

How to test?

  1. Use the given test card 1 and complete 3D authentication
  2. Now use test card 1 or 2 and try to subscribe again, you'll be redirected to Expired link page

How do I fix this issue? What am I missing here?

1 Answers

Checkout pages are single use. If you use one to create a subscription, it is then "consumed" and can't be used again.

It sounds like you're trying to deal with the case where a subscription is created, but a future invoice's payment fails due to the payment being in the requires_action status. This will happen if the card in question requires 3DS and must be confirmed on the client by the user. See the PaymentIntents flow: https://stripe.com/docs/payments/intents

You wouldn't redirect to the same Checkout session to action a card in the requires_action status, instead you'd either build your own UI or use the Customer portal: https://stripe.com/docs/billing/subscriptions/customer-portal.

Related