#arya-subscription-trial

1 messages · Page 1 of 1 (latest)

digital houndBOT
balmy crater
#

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

tranquil flicker
#

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

balmy crater
#

you need to expand the pending_setup_intent the same way you are doing latest_invoice.payment_intent

tranquil flicker
#

Got it, thanks!

#

Lemme try that

balmy crater
#

let me know if you're stuck after that!

tranquil flicker
#

Returning something like

    res.json({
      subscriptionId: subscription.id,
      clientSecret: givingTrial
        ? subscription.pending_setup_intent
        : subscription.latest_invoice.payment_intent.client_secret,
    });

Does not work

balmy crater
#

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

tranquil flicker
#

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}`,
      },
    });
balmy crater
#

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

tranquil flicker
#

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?

balmy crater
#

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

tranquil flicker
#

By paymentIntent do you mean cllientSecret?

balmy crater
tranquil flicker
#

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?

balmy crater
#

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

tranquil flicker
#

Hmm and the client_secret for the case when the trial is happening is returned by subscription.pending_setup_intent, right

balmy crater
#

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

tranquil flicker
#

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}`,
    });
  }
};
balmy crater
#

I dont think it can be expanded
it can, I explained how earlier

digital houndBOT
balmy crater
#

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

tranquil flicker
#

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

mint temple
#

đź‘‹ 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

tranquil flicker
#

Thank you! That worked. I have another question

mint temple
#

Sure

tranquil flicker
#

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

mint temple
#

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)

tranquil flicker
#

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

mint temple
#

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.

tranquil flicker
#

What if customer.subscription.updated fires when the payment does not go through?

mint temple
#

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)

tranquil flicker
#

Where can i find the different values of the status?

tranquil flicker
#

And what happens if a user cancels the trial before the trial ends?

mint temple
#

Then the Sub would move to a status of canceled

#

And you likely do want to just use customer.subscription.deleted for this

tranquil flicker
#

So customer.subscription.updated should be used for trial -> active and no plan -> trial, right?

mint temple
#

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.

tranquil flicker
#

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

mint temple
#

Yep that should all work well

tranquil flicker
#

Specifically, how do I handle payment failures (sorry I’m new to all this)

mint temple
#

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.

tranquil flicker
#

Can I also provision emails for when a paymenr fails?

#

Payment*

mint temple
tranquil flicker
#

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

mint temple
digital houndBOT
tranquil flicker
#

Got it, checking it out

tranquil flicker
#

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

twilit oriole
#

A subscription with a trial should go to active right away

tranquil flicker
#

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

twilit oriole
#

I'm not sure either. What's the subscription ID?

tranquil flicker
#

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

twilit oriole
#

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

tranquil flicker
#

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

digital houndBOT
twilit oriole
#

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

tranquil flicker
#

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

stoic grove
#

đź‘‹ hopping in here since synthrider has to head out - give me a minute to catch up

tranquil flicker
#

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

stoic grove
#

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?

tranquil flicker
#

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

stoic grove
#

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

tranquil flicker
#

Got it, i'll probably just try with invoice.paid for now

stoic grove
#

👍

tranquil flicker
#

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

stoic grove
#

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

tranquil flicker
#

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

stoic grove
#

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

tranquil flicker
#

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

stoic grove
#

Everything you need is on there

tranquil flicker
#

Yeah i think that's what im using:

    const invoices = await stripe.invoices.list({
      customer: req.user.customerId,
      limit: 3,
    });
    console.log(invoices)
balmy crater
#

đź‘‹ 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

tranquil flicker
#

What's the distinction between the outer data object and the data inside lines

#

Thank you for your help though!

stoic grove
tranquil flicker
#

Got it, thank you so much for your help!