Loading...

This is taking longer than expected.

Free trials and coupons

Let a new subscriber try a paid plan before paying, and accept Stripe promotion codes at checkout.

Two things soften the first payment: a trial period on every paid plan, and a promotion code field in Stripe Checkout. Both are switches in config/pricing.php, read from the environment, and both act only at the moment a subscription is created through Checkout.

Option Variable Default Effect
Free trial BILLING_FREE_TRIAL_ENABLED false New paid subscriptions start with a trial instead of a charge.
Trial length BILLING_FREE_TRIAL_DAYS 14 Days of trial. 0 or less disables the trial even when enabled.
Coupons BILLING_COUPONS_ENABLED true Checkout shows a field for a promotion code.

How the trial works

The trial is generic: one length for every paid plan, given when an account moves to a paid plan from /account/plans for the first time. A new account usually starts on the free plan, so the trial is what it gets when it leaves the free plan for a paid one. It is given once per account: an account that already had a trial, or that ever paid for a plan or an add-on, subscribes without one. It is not configured per plan, and it is not a trial on the customer that ends before a plan is picked.

What happens, step by step:

  1. The user clicks Subscribe on a paid plan. The Checkout session is created with a trial_end of today plus BILLING_FREE_TRIAL_DAYS. Stripe requires that date to be at least 48 hours away, so a trial of one day becomes two.
  2. Checkout asks for a card. Stripe collects a payment method even when the first payment is zero, and the kit does not override that, so a trial is never card-free.
  3. The subscription is created in Stripe with status trialing, and the webhook stores it locally with its trial_ends_at. From then on the user is subscribed: the plan shows as current, the limits apply, /app is open.
  4. When the trial ends Stripe issues the first invoice and charges the card. On success the status becomes active and invoice.paid arrives, which is when referral rewards and revenue are recorded. On failure the status becomes past_due and the grace period after a failed payment starts: the billing screen shows Payment failed with the date the grace period ends and a Retry payment button, and the plan is kept until then. The grace period and what happens when it runs out are in Queues and scheduled work.

An account leaving the free plan with a card already saved does not go through Checkout: its subscription moves to the paid plan inside the trial, and nothing is charged until the trial ends. The same happens when it changes the billing period at the same time.

Where the trial does not apply:

  • A free plan. There is nothing to defer, so a 0 price is subscribed directly.
  • A change between paid plans. An upgrade or a downgrade carries no trial.
  • An account that already had one. A trial already used, or any plan or add-on paid before, means the next paid plan is charged straight away, also for a subscriber who comes back after their subscription ended.

The trial also covers the add-on bootstrap: when a user with no subscription buys a paid add-on, Checkout opens with the free plan plus the add-on, and the same trial is applied to that session. See Sell add-ons.

With the trial on and plans live, the plan cards show the line "Start with a free trial of :days days!" above the interval switch. On /account/plans it shows only to an account the trial still applies to, so nobody is promised a trial they will not get; on the landing page, where there is no account to ask, it shows whenever the trial is on.

What the user sees during and after the trial

On /account/billing the status badge reads the Stripe status, Trialing, and Next payment shows the date the trial ends, read live from Stripe. Cancelling during the trial follows the ordinary rule, cancel at period end: Stripe ends the subscription when the trial ends and nothing is charged.

The kit sends no email about a trial that is about to end, and it does not handle the customer.subscription.trial_will_end event. Reminders come from Stripe: its customer email settings in the Dashboard include a trial-ending email, sent a few days before the first charge; turn it on there. A payment that needs the customer's authentication is the one case the kit can email about, through CASHIER_PAYMENT_NOTIFICATION; see Turn on plans and billing.

Who is on trial

The admin panel has no trial filter. Where a trial is visible:

  • The pricing page of a plan, /admin/billing-plans/{id}/pricing/{country}, lists its subscribers with a Status column; a trial shows trialing, and turns active after the first payment.
  • The users list counts a trialing subscription as a current plan, so the Current Plan column and the Plan filter include them.
  • The admin dashboard metric Subscriptions Created counts a subscription on the day it was created, trial or not. Daily Revenue only counts paid invoices, so a trial appears there the day it is first charged.
  • In the database, subscriptions.stripe_status is trialing and subscriptions.trial_ends_at holds the end date. In code, $subscription->onTrial().

The pricing page of a plan with its subscribers and their status

Trials on add-ons

Add-ons have trials of their own, separate from this one: set Free trial days on an add-on and the user can press Try N days. No card is asked for, the user is warned the day before it ends, and when it ends the add-on is switched off with nothing charged. One trial per account and add-on, forever. An administrator can also grant one from the user's page with Grant a trial. The whole flow is in Sell add-ons; the BILLING_FREE_TRIAL_* switches above do not touch it.

Coupons and promotion codes

With BILLING_COUPONS_ENABLED on, the Checkout session is created with allow_promotion_codes, and Stripe shows an Add promotion code field on the payment page. The kit has no coupon screen of its own: create the coupon in the Stripe Dashboard, under Product catalog, Coupons, and give it a promotion code, which is the text the customer types. Stripe applies the discount, the invoice reflects it, and the SubscriptionPaid event carries the discounted amount, so referral rewards are computed on what was actually paid.

The field appears on the two Checkouts a plan can open: a new paid subscription, and the change from a free plan to a paid one without a saved card. It is not offered on the add-on Checkout, and a plan change that happens inside the application, a swap between paid plans or an interval change, never shows Checkout and therefore never asks for a code. Turning the option off hides the field; coupons already attached to a subscription keep applying, because they live in Stripe.

Back