#paulc7053_handling-setup-intent-confirm-failures

1 messages ยท Page 1 of 1 (latest)

jagged snowBOT
#

๐Ÿ‘‹ Welcome to your new thread!

โฒ๏ธ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.

โฑ๏ธ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.

๐Ÿ”— This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1357108732714160148

๐Ÿ“ Have more to share? Add more details, code, screenshots, videos, etc. below.

solemn matrix
#

Hello!

north yew
#

Hi there!

#

Could you explain in a little more detail what you want to do? Is it that you want to know about when people enter wrong card details when signing up?

solemn matrix
#

Yep

#

On the frontend, I show the error message provided

#

But the above webhooks seem to behave as if the card was valid...

north yew
#

Do you have an example?

solemn matrix
#

Like an ID?

north yew
#

Yes please. Subscription or Customer would be fine

solemn matrix
#

sure, 1 sec please

#

This is a produciton subscription ID: sub_1R9UEMJ4ILijjURS12yDTdD6

#

Also shows up as paid (just like in the webhook)

north yew
#

An Invoice that doesn't require payment ($0 Invoice) will always show up as "paid"

#

ah, I see, when you tried to confirm the SetupIntent, it returned an error

#

or a decline, really

solemn matrix
#

Yes

#

How can I check for such cases using the webhook payload?

north yew
#

Just looking into the behavior around the SetupIntent's webhooks

#

I assume that you are looking to understand this soon after the decline happens, is that right?

solemn matrix
#

yes

#

it just seems odd to me that I can't see any obvious way to tell that the subscription doesnt have a valid card attached

north yew
#

Well payment methods on Subscriptions have some complexity, because you don't necessarily have to define the payment method on the Subscription object itself

#

If the Customer has a default_payment_method set, Stripe will use that

jagged snowBOT
north yew
#

But you should receive events to let you know the SetupIntent failed. I think in terms of a real decline, you would see a setup_intent.setup_failed event

#

And then at some point after that, around the 24 hour mark, you would see a setup_intent.cancelled event.

#

Unless you have a customer who is signing up and trying to pay with a Stripe test card, which would be interesting. ๐Ÿ˜†

solemn matrix
#

So you're saying the above problems mainly arise because I've tested with stripe cards in production, but otherwise the webhook payload/events are different?

#

Assuming the user uses a real card

#

And they introduce wrong details

north yew
#

I suspect that's the case. I would encourage you to test with Test Clocks and a decline-coded card in testmode

#

Test Clocks are a wonderful tool

zinc flint
#

Hi hi! Iโ€™m going to be taking over for my colleague here. Let me just read back.

solemn matrix
#

Hello, alright!

zinc flint
#

Can you share the Setup Intent ID which failed to emit that event?

solemn matrix
#

following up on your colleague's recommandation to listen to setup_intent.setup_failed, is there something in particular I should check in the payload?

zinc flint
#

What do you plan to do with the information?

solemn matrix
zinc flint
#

That Setup Intent is in a status: "requires_payment_method" state; it hasn't failed.

solemn matrix
zinc flint
solemn matrix
zinc flint
#

Ok let's take a step back. What are you trying to accomplish here exactly?

solemn matrix
#

differentiate between a user whose card has been declined at signup and a user whose card has been accepted

#

it's a free trial signup

zinc flint
#

So you're using Subscriptions?

solemn matrix
#

yes

zinc flint
#

You're usnig the SetupIntent from the Subscription here, right?

solemn matrix
#

I'm not listening for any setup intent events in my webhook

#

currently

zinc flint
#

How are you creating Subscriptions?

solemn matrix
#

Sorry, that was not the correct snippet

#

return await stripe.subscriptions.create({ customer: customerId, items: [{ price_data: { currency: currency.toLowerCase(), product: productId, unit_amount: Math.round(amount * 100), recurring: { interval: stripeInterval } } }], payment_behavior: 'default_incomplete', payment_settings: { save_default_payment_method: 'on_subscription' }, expand: ['latest_invoice.payment_intent', 'pending_setup_intent'], metadata, trial_period_days: trialDays });

zinc flint
#

expand doesn't belong in the same object as the params, it belongs in a separate, second argument object.

#

(So that shouldn't work, but maybe it does ยฏ_(ใƒ„)_/ยฏ)

solemn matrix
#

It does work actually haha

zinc flint
#

Huh.

#

Does it always have a trial?

solemn matrix
#

yep

zinc flint
#

Ok. I don't think you want payment_behavior=default_incomplete in there, because your subscription should be active (but trialing) immediately, ya?

solemn matrix
#

yes

zinc flint
#

So you likely don't want that in there.

#

How are you getting card details?

solemn matrix
#

ECE or Card element

zinc flint
#

Before you create the Subscription?

#

And it's attached to the Customer, ya?

solemn matrix
#

Yes

#

so I'm basically using await elements.submit(); then creating the subscription and customer, then returning the secret

zinc flint
#

The customer in that example Subscription didn't have a card.

#

Also you definitely need to be doing all this in test mode, as my colleague said.

solemn matrix
#

Yes, I know. I just realized this is a problem and was lazy to set up ngrok and all that.

#

Thought it would be an easy fix

#

Could you please check this subscription : sub_1R9ZkeJ4ILijjURS7aFknOGc. I didnt use a stripe card, but ensured it would be declined

zinc flint
#

What am I looking for?

solemn matrix
zinc flint
solemn matrix
#

great, so going back to my initial question: how do I differential between a successful and a declined card attachment? listed to setup_intent.payment_failed?

#

listen**

zinc flint
#

That only fires when the setup intent fails, which the first one hasn't done yet.

solemn matrix
#

setup_intent.setup_failed

#

I see, so how would you recommend I handle this case?

zinc flint
#

So you should check the status of that call. Otherwise, once the next Invoice is generated, it will fail to be paid, so you could deal with it then in the usual flow.

solemn matrix
zinc flint
#

Maybe?

solemn matrix
zinc flint
#

Which?

#

The second option there?

solemn matrix
#

yes

#

i.e. I don;t want to deal with a declined card after the trial ends

zinc flint
#

I guess. You can contrast that with the nontrivially large set of potential users that will exit your signup flow when you require a card to sign up.

solemn matrix
#

I mean yes... but Im trying to minimize both of these =))

zinc flint
#

No matter what you do here, you still will need to deal with declined cards after the trial ends. Any time Stripe attempts to pay an Invoice on one of your users' Subscriptions, it may fail, and you'll need to have a way to handle that.

#

I personally don't trial things that require a card; either your product speaks for itself and I will subsequently add my card so I can continue using it, or it doesn't and I don't. Saves me the hassle of figuring out how to remove my payment method from something I don't use.

solemn matrix
#

This seems very odd to me. The client knows its not valid- where can I get this info in the webhooks?

zinc flint
#

But I'm not all humans, so ... fair.

#

The setup_intent.created event also shows that.

#

Webhooks is the wrong place to handle this type of failure. In the client when you get the error is the right place.

#

This is also only related to initial signup; future errors wouldn't be like this.

solemn matrix
#

Okay. This is my handler `const handlePaymentButtonClick = async (e) => {
e.preventDefault();
setStripeError(null);

    if (!stripe || !elements) return;

    setIsLoading(true);
    const { error: submitError } = await elements.submit();
    if (submitError) {
        setStripeError(submitError.message);
        track('payment_error', {
            payment_method: 'card',
            error_type: 'submit'
        });
        return;
    };

    const formData = new FormData();

....

  ....

    const subscriptionRes = await fetch('/api/subscriptions/create', {
        method: 'POST',
        body: formData
    });

    const subscriptionData = await subscriptionRes.json();
    const clientSecret = subscriptionData.clientSecret;
    const stripeCustomerId = subscriptionData.stripeCustomerId;

   

......

    if (!clientSecret) {
        console.log("No immediate payment required due to trial period.");
        router.push(returnUrl);
        return;
    };

    // confirm the payment
    const cardElement = elements.getElement(CardElement);
    const { error } = await stripe.confirmCardSetup(clientSecret, {
        payment_method: {
            card: cardElement,
            billing_details: {
                email: billingEmail,
            },
        },
    });

    if (error) {
        setStripeError(error.message);
        setIsLoading(false);
        console.log({ error });
        track('payment_error', {
            payment_method: 'card',
            error_type: 'setup'
        });
    } else {
        router.push(returnUrl);
    };
};`
#

As you can see I make a request to /subscriptions/create and create the stripe customer & subscription before I get the error from confirmCardSetup

#

because it needs the clientSecret

zinc flint
#

await elements.submit(); will include the Payment Method ID if it succeeds, but you don't seem to be collecting that?

solemn matrix
#

yes, I was just following the guides

zinc flint
#

Which guide?

solemn matrix
#

I was just looking it up to link it here, but I can't see the Card element implementation anywhere on the site

zinc flint
#

That's an antique at this point so ... fair.

solemn matrix
#

I wrote this a few months ago

zinc flint
#

const { error } = await stripe.confirmCardSetup(clientSecret, {

That error is where you'd see that it failed and that you need to re-collect their payment details.

solemn matrix
#

yes, thats true

#

but it happens after I create the sub and customer on the backend

#

because I need the client secret

zinc flint
solemn matrix
#

But following this guide would yield the same problems -> create customer -> recieve customer created event ->create setup intent/subscription & get clientSecret -> receive event -> only then we see the error on the client ...

zinc flint
#

You don't need to wait for the event.

#

Do you mean the client side event in JavaScript? You can use await if you prefer; it doesn't really matter.

#

Point is, you knew that SetupIntent failed when const { error } = await stripe.confirmCardSetup() returned.

solemn matrix
#

my bad, I wrote that sequence badly

zinc flint
#

Or rather, that the SetupIntent didn't succeed to be properly set up.

#

It didn't fail, it just didn't succeed.

solemn matrix
#

events*

zinc flint
#

Yes, they are.

solemn matrix
#

Okay, let me put it differently: since I cannot prevent the invoice.payment_succeeded event from being sent - when the free subscription is created. How can I address on the backend the card failure to be validated

#

As you know, when you create a subscription and customer you handle events switch (event.type) { case 'customer.subscription.created': await handleSubscriptionCreated(event.data.object); break; case 'invoice.payment_succeeded': await handlePaymentSucceeded(event.data.object); ...... default: console.warn(Unhandled event type: ${event.type}); };. The problem is that I cannot differentiate between a valid and invalid card when signing up...

zinc flint
#

Based on your current setup and desire to not change it, there are three events your current system needs to see in order to confirm a "fully set up trialing subscription:"

[ customer.subscription.created, invoice.payment_succeeded, setup_intent.succeeded ]

If you haven't received all of those for a Subscription, it's not fully set up. More specifically, if you haven't received a setup_intent.succeeded event for a Subscription, the payment method part is still outstanding, so you would want to direct your user to try adding another payment method (which you'd then retry confirming with the Setup Intent client secret).

#

You could use setup_intent.succeeded to mark the Subsctription as payment_method_set=true (either in your app's data model, or in Stripe via metadata) and then if a Subscription doesn't have that, it isn't 'complete' from your perspective.

solemn matrix
#

this makes a lot of sense

#

will try implementing this right now

zinc flint
#

Awesome. ๐Ÿ™‚

solemn matrix
#

I know I stole lots of your time, sorry for that and thanks for your patience!

zinc flint
#

No worries at all - that's what we're here for! ๐Ÿ™‚

#

It was also a quiet afternoon so I was able to mostly focus on this.

solemn matrix
#

haha luckily for me

#

thanks again!

zinc flint
#

Of course! This thread will get closed after idling for a half hour or so, but you can always come back and access it and create another whenever you need. ๐Ÿ™‚

jagged snowBOT
#

paulc7053_handling-setup-intent-confirm-failures