#arya-subscription-trial
1 messages · Page 1 of 1 (latest)
arya-subscription-trial
Hey @tranquil flicker! If you have a Subscription with a trial period, the first payment Invoice will be for $0 since there's no upfront payment. You need to look at the pending_setup_intent instead to complete that
In the screenshot above, the clientSecret isn't being generated I believe. Is it because the payment is 0? If so, how can i modify the above code
you need to expand the pending_setup_intent the same way you are doing latest_invoice.payment_intent
let me know if you're stuck after that!
Returning something like
res.json({
subscriptionId: subscription.id,
clientSecret: givingTrial
? subscription.pending_setup_intent
: subscription.latest_invoice.payment_intent.client_secret,
});
Does not work
Sure but what doesn't work though? Like what isn't working, what error do you get, what exact values are you returning in clientSecret? As the developer you can add logs to your code to fully understand it and if you log this variable you should see the issue immediately
This is what my frontend has and im getting an error IntegrationError: You must pass in a clientSecret when calling stripe.confirmPayment()
Frontend code:
const clientSecret = await createSubscription({ planType, renewalPeriod });
if (!clientSecret) {
alert("Creation failed, check console");
return;
}
// Confirm the Subscription using the details collected by the Payment Element
const { error } = await stripe.confirmPayment({
elements: elements ? elements : undefined,
clientSecret,
confirmParams: {
return_url: `${FRONTEND_DOMAIN}${generalRoutes.success}`,
},
});
1/ confirmPayment is for payments, you are doing a SetupIntent
2/ You are not returning a client secret at all for SetupIntent, just a SetupIntent id
3/ You need properly logging in your code to track all your values and variable as you debug your code
The frontend code has no awareness of whether the user is eligible for a trial period or not. The backend server gets that information from the database. How can I setup the frontend code to handle trials?
you need to write custom code to handle this
your server can say "Hey this is a PaymentIntent and here is its secret" or "Hey this is a SetupIntent and here is its secret" and such. You fully control both sides here
By paymentIntent do you mean cllientSecret?
https://stripe.com/docs/payments/payment-intents that is what a PaymentIntent is: a state machien representing your attempt to accept a payment for a certain amount/currency
So my server should return a subscriptionId and clientSecret when there is no trial.
It should return a subscriptionId and setupintent when there is a trial period. Is this correct?
the client_secret property is just a really specific property (amongst dozens of others) that you can use to confirm it client-side
there's no reason to return the Subscription id client-side, it's not an id that can be used by front-end code.
What you need is
1/ The type of intent (SetupIntent or PaymentIntent) to know whether to call confirmSetup() or confirmPayment()
2/ The corresponding client_secret for that intent
Hmm and the client_secret for the case when the trial is happening is returned by subscription.pending_setup_intent, right
no
subscription.pending_setup_intent is the id of the SetupIntent, the same way invoice.payment_intent` is the id of the PaymentIntent.
it's pending_setup_intent: 'seti_12345'. You have to expand it and pass pending_setup_intent.client_secret instead
I dont think it can be expanded
Here is the backend code for reference:
const createSubscription = async (req, res) => {
try {
const { planType, renewalPeriod } = req.body;
// Create or retrieve customer
const user = await UserModel.findById(req.user._id);
let customer;
if (user.customerId === "") {
customer = await stripe.customers.create({
email: user.email,
description: user.fullName,
});
user.customerId = customer.id;
await user.save();
} else {
customer = await stripe.customers.retrieve(user.customerId);
}
if (!customer.id) {
return res.status(400).json({
message:
"Encountered error in createSubscription: no customerId found.",
});
}
let givingTrial = req.user.isEligibleForFreeTrial;
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [
{
price: priceIdMap[planType][renewalPeriod],
},
],
trial_period_days: req.user.isEligibleForFreeTrial ? 7 : 0,
payment_behavior: "default_incomplete",
payment_settings: { save_default_payment_method: "on_subscription" },
expand: ["latest_invoice.payment_intent"],
});
console.log(subscription, " is the subscription");
res.json({
// subscriptionId: subscription.id,
clientSecret: givingTrial
? subscription.pending_setup_intent.clientSecret
: subscription.latest_invoice.payment_intent.client_secret,
gaveFreeTrial: givingTrial,
});
} catch (err) {
return res.status(400).json({
message: `Encountered error in createSubscription: ${err.message}`,
});
}
};
I dont think it can be expanded
it can, I explained how earlier
Please read https://stripe.com/docs/expand carefully first to understand the feature
Right now your code passes expand: ["latest_invoice.payment_intent"], which means "Expand the Invoice and its PaymentIntent fully". And then later your code does
? subscription.pending_setup_intent.clientSecret
: subscription.latest_invoice.payment_intent.client_secret,```
This code has multiple separate bugs. You need to **cleanly log** all of those. I recommended this a few times earlier and if you take the time to add a clear log for each value it will jump out at you what doesn't work here
1/ You never expanded the SetupIntent so pending_setup_intent is a string so pending_setup_intent.xxxx would never work
2/ You wrote .clientSecret for the SetupIntent case instead of .client_secret like you did on the PaymentIntent case
Got it, giving it a shot
Im not sure why this is draft. (Im using test clock)
This happened when I moved forward my test clock to jun 18
đź‘‹ stepping in
If you move the clock to right near the time of renewal you will likely see a draft invoice like would happen for the hour when the Subscription renews. Try moving your test clock several hours past renewal time (or a day past) and you should see that it is finalized and paid
Thank you! That worked. I have another question
Sure
Currently, I have code in place that runs when the invoice.paid webhook is triggered. The code updates my database with the user's subscription tier information, records an end date, the subscriptionid:
case "invoice.paid": {
console.log("inside switch:", event.type);
const user = await UserModel.findOne({
customerId: data.customer,
});
const subscription = await stripe.subscriptions.retrieve(
data.subscription
);
const priceIdPurchased = subscription.items.data[0].plan.id;
if (
priceIdPurchased == priceIdMap["basic"]["monthly"] ||
priceIdPurchased == priceIdMap["basic"]["annual"]
) {
user.subscriptionPlan = "basic";
} else if (
priceIdPurchased == priceIdMap["pro"]["monthly"] ||
priceIdPurchased == priceIdMap["pro"]["annual"]
) {
user.subscriptionPlan = "pro";
}
user.endDate = new Date(subscription.current_period_end * 1000);
user.subscriptionId = data.subscription;
user.cancelAtPeriodEnd = false;
user.isEligibleForFreeTrial = false;
await user.save();
break;
}
When the trial ends, i saw that an invoice.createdwebhook is triggered. What should I have in my code for this webhook
So overall my recommendation would be to not use invoice.paid here if you are trying to track the status of the Subscription.
Instead I'd recommend using customer.subscription.updated.
That will indicate for instance when a Subscription moves from Trial --> Active
Or from Active --> past_due (if a payment fails)
Here is my goal:
A new user is eligible for a free trial of 7 days (I have a bool value called isEligibleForFreeTrial in my database which is true for a new user and I want to set it to false whenever a user starts a free trial).
When the user starts the free trial, I intend to collect their payment information and their subscription information needs to be updated in the database.
When the free trial ends, I want to charge the user and start a new billing cycle at this time.
If the user cancels before the free trial ends, I dont want to charge them (but they shouldnt be eligible for another free trial)
I was using invoice.paid because I thought I should update the information in my database only when the payment goes through. What is the webhook design should i follow to achieve my goal above
I would use customer.subscription.updated as I mentioned above.
And you can combine that with customer.subscription.created if you want to ingest the data when the Subscription first starts.
What if customer.subscription.updated fires when the payment does not go through?
Yeah you want to examine the status of the Subscription each time that fires
As well as the previous_attributes
So for instance if you see status: trialing in previous_attributes and status: active as the current status of the Subscription object you know that it just transitioned from trialing --> active successfully (meaning the new Invoice was successfully paid)
Where can i find the different values of the status?
And what happens if a user cancels the trial before the trial ends?
Then the Sub would move to a status of canceled
And you likely do want to just use customer.subscription.deleted for this
So customer.subscription.updated should be used for trial -> active and no plan -> trial, right?
You likely want customer.subscription.created for "no plan --> trial" -- it would depend on your integration for whether customer.subscription.updated would be fired here so using customer.subscription.created is the safe bet for new Subs.
Okay, so is the following fine:
“No plan —> trial” use customer subscription created
“If trial is cancelled” use customer subscription deleted
“If trial —> active” use customer subscription updated
“If active plan is cancelled” use customer subscription deleted
Yep that should all work well
Specifically, how do I handle payment failures (sorry I’m new to all this)
All good
For payment failures the status of the Subscription will move from active --> past_due
So you can use customer.subscription.updated for that as well.
Yes you control that from your Dashboard settings at https://dashboard.stripe.com/settings/billing/automatic
Thanks!
Is there a way that a customer can change their card on file for subscriptions?
Since my database doesn’t store card details at all
You likely want to look at using Customer Portal if you don't want to build that yourself: https://stripe.com/docs/customer-management
Got it, checking it out
Im getting status: incomplete here
Im expecting a status: active
The approach in webhook design that im using is this:
“No plan —> trial” use customer subscription created
“If trial is cancelled” use customer subscription deleted
“If trial —> active” use customer subscription updated
“If active plan is cancelled” use customer subscription deleted
Hey there, do you have an example subscription we can look at?
A subscription with a trial should go to active right away
On the dashboard, i see that the subscription is active
This is what the logs show:
event.type is: payment_intent.created
event.type is: invoice.created
event.type is: invoice.finalized
event.type is: customer.subscription.created
Now, inside customer.subscription.created, im seeing that the status is incomplete.
Idk if this has something to do with test clocks
I'm not sure either. What's the subscription ID?
sub_1NFk2AIJY9GdUtEDMwJjft9c
When i create a subscription, this is the code (it passes in default incomplete):
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [
{
price: priceIdMap[planType][renewalPeriod],
},
],
trial_period_days: req.user.isEligibleForFreeTrial ? 7 : 0,
payment_behavior: "default_incomplete",
payment_settings: { save_default_payment_method: "on_subscription" },
expand: ["latest_invoice.payment_intent", "pending_setup_intent"],
});
Idk if that's correct
Hi are you there
yep still looking at this
you're sending trial days =0 (no trial)
you should check your logic for isEligibleForFreeTrial or just override with 7 for testing these webhook events
The customer im testing for isn't eligible for a free trial, so I passed in trial_period_days as 0
Is that incorrect?
I removed that line and tried without a trial period. Got the same status: incomplete. Here's the subscription id for this one: sub_1NFkSDIJY9GdUtEDRQwNQs3b
Should I just provision access to my product when invoide.paid is triggered lol instead of using the following design:
“No plan —> trial” use customer subscription created
“If trial is cancelled” use customer subscription deleted
“If trial —> active” use customer subscription updated
“If active plan is cancelled” use customer subscription deleted
Sorry, i thought you were testing for trial periods, which go directly to active
If you intend to examine the no trial case (immediate first payment required) then yes incomplete is expected on creation until you complete payment for the first invoice
Im very confused lol. Could you tell me which webhooks i should listen for when tracking subscription changes? Trial to actual plan, no trial to actual plan, plan cancel, trial cancel
Im not sure what's the best way to go about this
đź‘‹ hopping in here since synthrider has to head out - give me a minute to catch up
Earlier i was told to not use invoice.paid, but idk why
What's the design of webhooks to follow and which ones shoujld i listen to when
Generally, for tracking subscription changes you'd listen to customer.subscription.updated - but it looks like your real question was about a specific subscription not being in the state you expected, right?
I dont have any questions per say. Im just confused on which webhooks should i listen to in each of the following cases
My goal is:
A new user is eligible for a free trial of 7 days (I have a bool value called isEligibleForFreeTrial in my database which is true for a new user and I want to set it to false whenever a user starts a free trial).
When the user starts the free trial, I intend to collect their payment information and their subscription information needs to be updated in the database.
When the free trial ends, I want to charge the user and start a new billing cycle at this time.
If the user cancels before the free trial ends, I dont want to charge them (but they shouldnt be eligible for another free trial)
I was using invoice.paid because I thought I should update the information in my database only when the payment goes through. What is the webhook design should i follow to achieve my goal above
Yes, you can use invoice.paid - but you'll need to check that it's for the renewal invoice and not the trial one (we also create $0 invoices for trials that are immediately paid).
It really depends on what you're trying to do and I would strongly suggest reading through all the events we talk about here (https://stripe.com/docs/billing/subscriptions/webhooks). Some integrations find that it's enough to just listen for invoice.paid , whereas others need to more closely track the subscription state and may also listen for customer.subscription.* events.
Just based on what you said, it sounds like you ONLY need invoice.paid, but as you build more things out you may need to listen for more events
Got it, i'll probably just try with invoice.paid for now
👍
I wanted to create an endpoint to get a user's transaction history i.e. how many subscriptions, what price, from what dates, etc. Which api can i use for that
It depends on everything you'd like to display
You'd likely have to use multiple APIs
You'd need v1/subscriptions for showing how many subscriptions and what they're subscribed to
you'd want /v1/invoices if you want to show a payment history of all the past invoices
Got it
When I get the invoice object
It has an array called data which contains objects. Each of these objects has an object called lines which also has an array called data which also contains objects
Which one should I read??
What's the distinction between the outer data object and the data inside lines
data is something that you get back from List API requests (https://stripe.com/docs/api/pagination)
Each of the objects in data should be an Invoice (https://stripe.com/docs/api/invoices)
I don't know what specifically you need to read since I don't know what you're building/what data you need, but you can look at the API reference for Invoices and see all the diffrent params that are available
Trying to create something that that goes like, “hey user, here’s a list of your previous transactions with us along with the subscription plan, time period, and amount”
What’s the best approach for that
Have you looked through the Invoices API ref I sent over? https://stripe.com/docs/api/invoices
Everything you need is on there
Same with the Subscriptions API ref (https://stripe.com/docs/api/subscriptions)
Yeah i think that's what im using:
const invoices = await stripe.invoices.list({
customer: req.user.customerId,
limit: 3,
});
console.log(invoices)
đź‘‹ you are listing Invoices, that's great! That's step 1. The next step is to extract from each Invoice what you care about. This is what @stoic grove told you multiple times above.
Now you need to look at the API Reference for the Invoice object: https://stripe.com/docs/api/invoices/object and extract all the information you care about. It's documented cleanly there with all the details you need.
We can't list everything you are after or tell you abour every property as there are dozens of developers who need help every day in this Discord server. But our docs should give you all you need here and if you have a specific question about your code then we can look at it to guide you
What's the distinction between the outer data object and the data inside lines
Thank you for your help though!
I already explained that as well - data is somethign you get back from all List requests (https://stripe.com/docs/api/pagination) and each object in data is an Invoice. The Invoice object itself has it's own format, which you can look at in the api reference (https://stripe.com/docs/api/invoices/object#invoice_object-lines-data)
Got it, thank you so much for your help!