#paulc7053_api

1 messages ยท Page 1 of 1 (latest)

hollow kelpBOT
#

๐Ÿ‘‹ 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/1279134440748220550

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

Below are links to other discussions we've had with you in the past week in case you want to review that information. If your question is related to one of these previous discussions, please provide a comprehensive summary of the current state and what you need help with now. We help many users simultaneously, so a summary allows us to resolve your issue as soon as possible.

fervent pebble
#

Hi!

#

This is how I handle the payment on the client

#

`const handlePaymentButtonClick = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
return;
};

    const { error: submitError } = await elements.submit();
    if (submitError) {
        setStripeError(submitError.message);
        return;
    };

    // create the customer
    const stripeCustomerIdRes = await fetch('/api/stripe/findOrCreateCustomer', {
        method: 'post',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            billingEmail,

...
}),
});
const stripeCustomerId = (await stripeCustomerIdRes.json()).stripeCustomerId;

    // create the subscription
    const subscriptionRes = await fetch('/api/stripe/createSubscription', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            stripeCustomerId,
            ...
        }),
    });
    const clientSecret = (await subscriptionRes.json()).clientSecret;

    const queryParams = { ...router.query };
    const queryString = getPaymentSuccessfulQueryString(queryParams);

    const host = process.env.NODE_ENV === 'development' ? a : b;

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

    if (error) {
        console.log({ error });
    } else {
        router.push(returnUrl);
        console.log(returnUrl);
    };
};`
#

And this is how I create the subscription `// /api/stripe/createSubscription
import Stripe from "stripe";

const stripe = new Stripe(
process.env.NODE_ENV === 'development'
? process.env.STRIPE_TEST_API_KEY
: process.env.STRIPE_PRODUCTION_API_KEY
);

export default async function createSubscription(req, res) {
const {
stripeCustomerId,

    billingEmail,

  ..
} = req.body;

const metadata = {
    stripeCustomerId,

    billingEmail,

  ...
};

//create a SetupIntent for collecting payment method
const setupIntent = await stripe.setupIntents.create({
  customer: stripeCustomerId,
});
console.log("Stripe setup intent created.");

//create a dynamic product
const product = await stripe.products.create({
    name: `${currency.toUpperCase()}${totalPrice} Subscription`,
});
console.log("Stripe product has been created.");

//create the subscription
const subscription = await stripe.subscriptions.create({
    customer: stripeCustomerId,
    items: [{
        price_data: {
            currency: currency.toLowerCase(),
            product: product.id,
            unit_amount: Math.round(totalPrice * 100),
            recurring: { interval: 'year' },
        }
    }],
    payment_behavior: 'default_incomplete',
    expand: ['latest_invoice.payment_intent'],
    metadata: metadata,
    trial_period_days: 3
});
console.log("Stripe subscription has been created.");

const clientSecret = setupIntent.client_secret || null;

res.send({
    subscriptionId: subscription.id,
    clientSecret: clientSecret,
});

};`

#

But the problem is that when the trial ends, the invoice is always getting payment failed/past due

#

And I don't know what I'm doing wrong

regal depot
#

Sorry this is a wall of code, can we step back for a bit?

Can you explain in words, using a bulleted list, the steps you are taking to create the subscription?

fervent pebble
#

yes

#
  1. create a customer 2.create a setupIntent with the customer id 3. create the subscription 4. return the client secret from the setup intent to the client and confirm it with stripe.confirmCardSetip
regal depot
#

Sorry, now I'm a little confused, you create the subscription after you create the Setup Intent but before you confirm it?

fervent pebble
#

yes

regal depot
#

Okay so the customer doesn't have a saved payment method when the Subscription is created.

fervent pebble
#

yes, that seems to be the problem

regal depot
#

How long is the free trial period?

fervent pebble
#

3 days

regal depot
#

Are you currently listening to webhooks?

fervent pebble
#

yes

regal depot
#

When you confirm the SetupIntent you should get a setup_intent.succeeded event. This will include the Setup Intent which has the Customer and Payment Method IDs in it.

fervent pebble
#

Is there any other way to do this?

#

is seems kinda of hacky, no?

regal depot
#

This is pretty normal

fervent pebble
#

But then how come I can easily collect the default payment method without a webhook when I have no trial period?

regal depot
#

Well I would change your integration entirely

#

I would not separate the payment method collections from the Subscription flow

fervent pebble
#

Yeah

#

that's what I'd like

#

how would you do that?

regal depot
#

Yes you can configure your subscriptions to do that

fervent pebble
#

Okay, I'm confused

#

so you're saying that when I create a subscription, I can do like this const setupIntent = await stripe.setupIntents.create({ customer: stripeCustomerId, }); const subscription = await stripe.subscriptions.create({ customer: stripeCustomerId, items: [{ price_data: { currency: currency.toLowerCase(), product: product.id, unit_amount: Math.round(totalPrice * 100), recurring: { interval: 'year' }, } }], payment_behavior: 'default_incomplete', pending_setup_intent: setup_intent, expand: ['latest_invoice.payment_intent'], metadata: metadata, trial_period_days: 3 });

regal depot
#

No

#

Actually, since you are setting trial_period_days it doesn't make sense to expand the latest_invoice.payment_intent

#

There isn't a payment intent

#

I am saying that the sub object you get back from your request will include a property called pending_setup_intent and you can use this instead of creating your own Setup Intent to collect the payment method information

fervent pebble
#

You mean like this const subscription = await stripe.subscriptions.create({ customer: stripeCustomerId, items: [{ }], payment_behavior: 'default_incomplete', metadata: metadata, trial_period_days: 3 }); const setupIntent = subscription.pending_setup_intent;?

regal depot
#

Yes

#

And you use the client Secret when you confirmCardSetup

fervent pebble
#

indeed that makes more sense

regal depot
#

Okay yeah, it's a bit more streamlined

fervent pebble
#

Basically, according to this example I ought to use 'on_subscription', return the client secret that now comes from the subscription itself & confirm the payment on the client

#

1 sec I'll try right now

#

In the docs, they do `const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{
price: priceId,
}],
payment_behavior: 'default_incomplete',
payment_settings: { save_default_payment_method: 'on_subscription' },
expand: ['latest_invoice.payment_intent'],
});

res.send({
  subscriptionId: subscription.id,
  clientSecret: subscription.latest_invoice.payment_intent.client_secret,
});`. I do exactly like that but add the trial period param & I get no payment_intent .....
regal depot
#

Yes, because you have a free trial period

#

So there is nothing to pay for

fervent pebble
#

okay, so what do I do?

regal depot
#

You need to use the Setup Intent

fervent pebble
#

okay so I use subscription.setup_intent

regal depot
#

pending_setup_intent

fervent pebble
#

alright, 1 sec please

#

Okay, it also fails to collect it after the trial period

#

this is what I have rn `const subscription = await stripe.subscriptions.create({
customer: stripeCustomerId,
items: [{
price_data: {

    }],
    payment_behavior: 'default_incomplete',
    payment_settings: {
        save_default_payment_method : 'on_subscription'
    },  
    expand: ['latest_invoice.payment_intent'],
    metadata: metadata,
    trial_period_days: 3
});
const setupIntent = subscription.pending_setup_intent;
const clientSecret = subscription.client_secret || null;

res.send({
    subscriptionId: subscription.id,
    clientSecret: clientSecret,
});`
#

and on the client const { error } = await stripe.confirmPayment(clientSecret, { payment_method: { card: cardElement, billing_details: { email: billingEmail, }, }, });

regal depot
#

Can you share a request ID for the Subscription creation?

fervent pebble
#

sure

#

evt_1PtZhCJ4ILijjURSL7OSiQJu

hollow kelpBOT
fervent pebble
#

hello

cinder rover
#

Hi, stepping in and catching up.

fervent pebble
#

basically you can just read my last code blocks comments

#

the payment fails after the trial period

cinder rover
fervent pebble
#

because the exact same setup succeeds on a regular sub with no trial

#

okay, and so that implies something like await stripe.customers.update(customerId, { invoice_settings: { default_payment_method: paymentMethodId, }, });. but where do I get the paymentMrthodId?

cinder rover
#

On your code, you would need to add another logic to handle subscription on trial

#

these subscriptions return a pending setup intent, and you would need to use that to collect the payment method on the client side

fervent pebble
#

so const setupIntent = subscription.pending_setup_intent; and const clientSecret = subscription.client_secret; . now what exactly do I need to do on the client?

#

This whole flow is just so damn blurry to me. I get that I need to associate a default payment with the customer, but not how

#

can you tell me step by step what I have to do: ex. 1. create customer 2.create subscription 3.get the pending payment intent from the subscription etc. ?

cinder rover
#

After you create the subscription with trials, if you want to collect payment methods then, you can use pending setup intent: https://dashboard.stripe.com/test/logs/req_xlP0bGzgTalMMv and use that client secret to collect the payment details on the client side. That piece would work similarly to how you collect the payment method details that you do not have trials.

fervent pebble
#

but I am already doing that and it doesnt work

cinder rover
#

And the client secret would be under pending setup intent

fervent pebble
#

and then I do const { error } = await stripe.confirmPayment(clientSecret, { payment_method: { card: cardElement, billing_details: { email: billingEmail, }, }, }); on the client

cinder rover
#

yes

fervent pebble
#

I have tried this many many times

fervent pebble
cinder rover
fervent pebble
#

what event to look out for?

cinder rover
#

I need the request id for when you create teh subscription that attempted this function confirm on the subscription that was on trial and you tried using the client secret from the pending intent.

fervent pebble
#

or what endpoint

#

just so you know, this is what I see

#

How am I supposed to know which one is the confirmPayment

cinder rover
#

Can you share a subscription id where you attempted to use the pending intent's client secret?

fervent pebble
#

it's sub_1PtaBuJ4ILijjURSIxu3vYGm

cinder rover
#

Looking ..

fervent pebble
#

Alright, thanks

#

any updates? @cinder rover

#

Look, I know you're busy an have other chats too, but I've been here for 2 HOURS, running in circles

cinder rover
#

I'm still looking, and trying to reproduce the issue on my end.

It looks like you're using Test Clocks here as well, can you share the exact steps you're taking here so I can test fully?

fervent pebble
#

I just set the test clock 3 hours after the subscription trial end date, as your colleague suggested...

cinder rover
#

After you rencer the Payment Element to collect the payment details, are you passing test card details and clicking on 'Pay'? Are you saying that after you add those details and click on 'Pay' nothing happens?

fervent pebble
#

after that I am being redirected to the stated redirect URl

#

Url

#

that works as expected

cinder rover
#

What does that mean? You're rending the UI successfully using the pending setup intent's client secret?

hollow isle
#

Hey again ๐Ÿ‘‹ catching up on what you've been chatting with my teammates, just a bit more for me to review.

fervent pebble
#

Yes....

hollow isle
#

Alright, let me try to lay this out.

The flow:

  • Create a Customer
  • Create a Subscription (with a trial period)
  • Use the client secret from pending_setup_intent that creates to render your payment form and confirm the Setup Intent
hollow isle
#

The key things that need to change based on what I'm seeing are:

  • adjust the expand line when creating the Subscription so that you also expand pending_setup_intent (where you're currently already expanding latest_invoice.payment_intent)
#

That way you'll get the Setup Intent's client secret directly.

#

Now let me check your frontend code again.

#

Looks like you already adjusted the frontend code to use confirmCardSetup

fervent pebble
#

I just keep changing these, right now I have confirmPayment

fervent pebble
hollow isle
hollow isle
#

One clarifying question, are you trying to build a flow that only handles Subscriptions with trials, or that can handle Subscriptions that don't start with a trial period as well?

fervent pebble
fervent pebble
fervent pebble
fervent pebble
hollow isle
hollow isle
fervent pebble
#

Alright, great

#

testing now

#

With the ECE, I get :{ "type": "invalid_request_error", "message": "Payment details were collected through Stripe Elements using automatic payment methods and cannot be confirmed with a Setup Intent configured with payment_method_types.", "request_log_url": "https://dashboard.stripe.com/test/logs/req_3X3GUjAPhHjaIu?t=1725048505", "setup_intent": { "id": "seti_1Ptb7MJ4ILijjURSICZzJt9l", "object": "setup_intent", "automatic_payment_methods": null, "cancellation_reason": null, "client_secret": "seti_1Ptb7MJ4ILijjURSICZzJt9l_secret_Ql7MJDCvPsvkiFE6uaKmn5tedHiXS25", "created": 1725048504, "description": null, "last_setup_error": null, "livemode": false, "next_action": null, "payment_method": null, "payment_method_configuration_details": null, "payment_method_types": [ "card", "link", "paypal" ], "status": "requires_payment_method", "usage": "off_session" }, "shouldRetry": false }

hollow isle
#

That error indicates that you used conflicting parameters when creating the Setup Intent (so in this case when creating the Subscription) versus how Elements was initialized.

#

Can you share your ECE related frontend code again? I know it's in here somewhere, but the thread is getting pretty lengthy.

fervent pebble
#

Yes

#

And `export function InlineExecutiveCheckout({
props
}) {
const options = {
mode: 'setup', // Use setup mode to indicate no immediate charge
amount: 0, // Set the initial amount to 0 for the free trial
currency,
appearance: {
variables: {
borderRadius: '36px',
fontSizeBase: '16px', // Adjusted appearance to make trial info clearer
}
},
setupFutureUsage: "off_session", // Prepare for future payment
};

return (
<Elements stripe={stripePromise} options={options}>
<ElementsConsumer>
{({ stripe }) => {
if (!stripe) {
return <Spinner />;
};
return (
<>
<ExpressCheckout
{...props}
/>
</>
);
}}
</ElementsConsumer>
</Elements>
);
};`

hollow isle
#

Huh, weird, you aren't suppressing automatic payment methods though

fervent pebble
#

yeah

hollow isle
#

Going to do some testing on my end

fervent pebble
#

Alright, thanks!

#

I'll be afk for 5 mins, but will see the replies asap!

hollow isle
fervent pebble
#

sure

#

Im trying right now

#

I thought setup is meant for free trials

hollow isle
#

It's for if you were using Setup Intents directly, but here you're relying on the Setup Intent being created by a Subscription instead. (Which is the better path so the Setup Intent pulls in the payment method types for your Subscriptions, rather than running the risk of pulling in types that you don't have enabled for Subscription payments)

fervent pebble
#

OMG!

#

You're the MVP Toby

#

I would have been here for God knows how many hours staring at this chat

#

if it werent for you

#

finally works!

hollow isle
#

Woohoo, so glad to hear that did the trick!

fervent pebble
#

Thanks a lot!

hollow isle
#

Any time!

fervent pebble
#

Thanks again!! you're the MVP

#

Have a great day!

hollow isle
#

Thank you, hope you do the same!