Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extension requires deprecated feature of Stripe to support free trials for subscriptions #609

Open
frankAOD opened this issue Feb 12, 2024 · 9 comments

Comments

@frankAOD
Copy link

Bug report

  • Extension name: Run Payments with Stripe

Describe the bug

Documentation of this extension states the following:

Handling trials
By default, the trial period days that you’ve specified on the pricing plan will be applied to the checkout session.

However Stripe no longer supports adding trial periods to pricing plans on products.
Currently, a trial period can be added to a pricing plan if it is added via Stripe's Billing > Quick Actions, but it is marked as 'legacy' in this workflow and cannot then be added if later pricing plans are needed on the product.
This workaround is currently effective in that the subscription is indeed created with the trial period on the pricing plan being honoured, however if it relies on a deprecated feature of Stripe then it will not be sustainable long term.

Expected behavior

If the addition of a trial period to a pricing plan is deprecated in Stripe, another method is needed to be able to bring this option from a pricing plan into the subscription that is created by this extension. Perhaps a required entry into the pricing plan's metadata, that can be picked up by the extension?

Screenshot of the documentation on the extension in Firebase

Screenshot 2024-02-12 221055

A free trial can be added to a pricing plan when creating it via Billing > Quick Actions, but it is marked as 'legacy'

free trial

If adding new pricing to the product created above, free trial options cannot be added in this workflow, meaning that for a product to be supported by this extension, it cannot ever have a price change after it's created.

cannot add

@maertu
Copy link

maertu commented Feb 25, 2024

You may explain how to create a price with a trial period over the quick actions (billing)? Cause for me this options not seem to be visible.
I think this really needs attention as if I see this right, it is not possible to define trials at all atm.

Edit:
I was able to find. One need to create a whole new product over the billing page. But yea, issue persists about modifying an existing product and once legacy method gets removed it will not even be possible for new products.

@frankAOD
Copy link
Author

Edit: I was able to find. One need to create a whole new product over the billing page. But yea, issue persists about modifying an existing product and once legacy method gets removed it will not even be possible for new products.

I've created a temporary workaround by editing the source code of the cloud functions created by this extension so that it will create a 30 day trial for customers who have not have a subscription before. This works for my particular circumstance because all of my subscription products will offer a 30 day trial. It won't work for anyone with some products that do not have a trial, or have varying periods - these things could be accommodated with further edits to the functions, picking up metadata or the like.

@maertu these are the changes I've made to function 'ext-firestore-stripe-payments-createCheckoutSession' make this work while waiting for a resolution from Invertase. Hopefully it can help you:

    if (mode === 'subscription') {

      //Prevent customer from having free trial twice
      const previousSubs = await admin
        .firestore()
        .collection(config.customersCollectionPath + '/' + context.params.uid + '/subscriptions')
        .get();

      const subDocs = previousSubs.docs.map(doc => doc.data());
      const hadProducts = subDocs.map(doc => doc.items.map(i => i.plan.id)[0]);
      const subTo = sessionCreateParams.line_items.map(m => m.price);
      const foundPrice = subTo.some(r => hadProducts.includes(r));

      sessionCreateParams.payment_method_collection =
        payment_method_collection;

      if (foundPrice == false) {
        sessionCreateParams.subscription_data = {
          trial_period_days: 30,
          metadata,
        };
      } else {
        sessionCreateParams.subscription_data = {
          metadata,
        };
      }

      if (!automatic_tax) {
        sessionCreateParams.subscription_data.default_tax_rates = tax_rates;
      }
    }

@crisblunt
Copy link

Edit: I was able to find. One need to create a whole new product over the billing page. But yea, issue persists about modifying an existing product and once legacy method gets removed it will not even be possible for new products.

I've created a temporary workaround by editing the source code of the cloud functions created by this extension so that it will create a 30 day trial for customers who have not have a subscription before. This works for my particular circumstance because all of my subscription products will offer a 30 day trial. It won't work for anyone with some products that do not have a trial, or have varying periods - these things could be accommodated with further edits to the functions, picking up metadata or the like.

@maertu these are the changes I've made to function 'ext-firestore-stripe-payments-createCheckoutSession' make this work while waiting for a resolution from Invertase. Hopefully it can help you:

    if (mode === 'subscription') {

      //Prevent customer from having free trial twice
      const previousSubs = await admin
        .firestore()
        .collection(config.customersCollectionPath + '/' + context.params.uid + '/subscriptions')
        .get();

      const subDocs = previousSubs.docs.map(doc => doc.data());
      const hadProducts = subDocs.map(doc => doc.items.map(i => i.plan.id)[0]);
      const subTo = sessionCreateParams.line_items.map(m => m.price);
      const foundPrice = subTo.some(r => hadProducts.includes(r));

      sessionCreateParams.payment_method_collection =
        payment_method_collection;

      if (foundPrice == false) {
        sessionCreateParams.subscription_data = {
          trial_period_days: 30,
          metadata,
        };
      } else {
        sessionCreateParams.subscription_data = {
          metadata,
        };
      }

      if (!automatic_tax) {
        sessionCreateParams.subscription_data.default_tax_rates = tax_rates;
      }
    }

Would you mind posting a repo for this? I'm still pretty new to coding and I'm not sure what files i'd need to mod to get this working.

@lanthias
Copy link

lanthias commented Apr 8, 2024

It looks like this has been fixed in next but not yet deployed to npm (

)

Would it be possible to get an ETA on this landing?

@marcosluizfp
Copy link

marcosluizfp commented Jun 29, 2024

The fix was applied, however, the way it works is different from the official docs:

  const checkoutSession: CustomerCheckout = {
    billing_address_collection: 'auto',
    line_items: [selectedPrice],
    allow_promotion_codes: true,
    mode: 'subscription',
    // THIS IS HOW IT WORKS:
    trial_period_days: trialPeriod,
    subscription_data: {
      // OFFICIAL DOCS - NOT WORKING:
      trial_period_days: trialPeriod,
    },
    success_url: successUrl,
    cancel_url: cancelUrl,
  };

@nicolasheady
Copy link

The fix was applied, however, the way it works is different from the official docs:

  const checkoutSession: CustomerCheckout = {
    billing_address_collection: 'auto',
    line_items: [selectedPrice],
    allow_promotion_codes: true,
    mode: 'subscription',
    // THIS IS HOW IT WORKS:
    trial_period_days: trialPeriod,
    subscription_data: {
      // OFFICIAL DOCS - NOT WORKING:
      trial_period_days: trialPeriod,
    },
    success_url: successUrl,
    cancel_url: cancelUrl,
  };

So when creating a new checkoutSession, you're saying add the trial_period_days attribute, NOT the subscription_data attribute?

@nicolasheady
Copy link

The fix was applied, however, the way it works is different from the official docs:

  const checkoutSession: CustomerCheckout = {
    billing_address_collection: 'auto',
    line_items: [selectedPrice],
    allow_promotion_codes: true,
    mode: 'subscription',
    // THIS IS HOW IT WORKS:
    trial_period_days: trialPeriod,
    subscription_data: {
      // OFFICIAL DOCS - NOT WORKING:
      trial_period_days: trialPeriod,
    },
    success_url: successUrl,
    cancel_url: cancelUrl,
  };

So when creating a new checkoutSession, you're saying add the trial_period_days attribute, NOT the subscription_data attribute?

I can confirm this is working. I haven't placed an actual order yet using the free trial. But when I get to the checkout page after creating the checkout session object with the "trial_period_days" property, it shows on the checkout page a free trial for X days. Card will not be charged immediately but the card info is collected and charged after the free trial.

@ebenbruyns
Copy link

The way I'm currently using this extension is by calling the functions from the client side. I can see an issue where users can just add their own trial days. Since this is just a field that's added to the checkout session and the users are creating this themselves essentially.

Is this intended behaviour or am I doing it wrong? I get the impression that this API is not supposed to be used directly by client code on firebase?

How do I mitigate this and what else is laying in wait for me to discover? Maybe I'm just misunderstanding how this is supposed to be used.

@ebenbruyns
Copy link

ebenbruyns commented Jan 18, 2025

I've managed to put a workable solution in place, this really should be documented because if I didn't go looking I would not have discovered this unless I had done a pen test on the system.

Add a rule to firestore to clamp the value. This is sub-optimal since it clamps it for ALL subscriptions so you cannot vary it by subscription type.


match /checkout_sessions/{id} {
        allow read: if request.auth.uid == uid;
        allow write: if request.auth.uid == uid && !(request.resource.data.trial_period_days != null && request.resource.data.trial_period_days != 7);
      }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants