Stripe 免費試用結束時的訂閲及用戶存取安排
When a Free Trial Ends and You Have Under 40 Active Trials
這篇指南為活躍免費試用少於 40 個的 SaaS 梳理 Stripe 試用到期時的付款處理、通知及用戶存取安排。customer.subscription.trial_will_end 通常在到期前 3 天觸發,Stripe 託管提醒電郵則在到期前 7 天發出,兩者時點不同。
When you have under 40 active free trials, the hard part isn't starting them. Stripe Checkout will do that with a couple of fields. The hard part is the last three days, when a trial is about to become a charge, or nothing, or a paused subscription you forgot about.
Most tiny SaaS setups stop at trial_period_days=14. They never decide what access looks like on the morning the trial ends, what email goes out, or what Stripe should do if there's no card on file. Then the first trial ends on a Tuesday and you're inventing policy in the Dashboard.
This is the operational side for that size. Not legal advice, just the order I'd set things in before the next trial hits day 11.
1. Know what Stripe actually fires (and when)
For the classic free trial (trial_end / trial_period_days), Stripe sends customer.subscription.trial_will_end three days before the trial ends. If the trial is shorter than three days, it fires basically immediately. That's the default on the Dashboard "Trial ending events" setting — you can change the lead time, but three days is what most accounts get.
Two things people mix up:
-
trial_will_endis a reminder, not the end. The subscription is stilltrialingwhen this lands. Nothing has charged yet. - Stripe's own hosted reminder email (the compliance one in Billing settings) is a different clock. Their docs put it at seven days before the trial ends. So you can get Stripe's email on day 7 of a 14-day trial, and your webhook on day 11. Don't assume they're the same day.
When the trial actually ends and the sub isn't paused, Stripe creates an invoice and attempts to charge it roughly an hour later. You'll also see customer.subscription.updated as status moves from trialing toward active (or into trouble).
If you already keep an entitlements table off Stripe status alone, treat trial_will_end as "check the customer has a usable payment method, then send your email." Don't wait for the charge to fail and then scramble.
2. Card-upfront vs no card: pick one, then set missing_payment_method
Two common shapes under 40 trials:
A) Card at signup. Checkout collects a payment method, trial starts at $0, day N they get charged. Simpler ops. Higher signup friction. Abuse is lower.
B) No card during trial. Faster signups, more junk accounts, and you must tell Stripe what to do when the trial ends with nothing to charge. That's trial_settings.end_behavior.missing_payment_method:
| Value | What Stripe does at trial end | Webhook you care about |
|---|---|---|
create_invoice (default) |
Creates an invoice; if it can't be paid, sub can go past_due
|
invoice.created / invoice.payment_failed
|
cancel |
Cancels the subscription | customer.subscription.deleted |
pause |
Pauses; no more invoices until you resume | customer.subscription.paused |
Stripe checks default_payment_method and default_source on both the subscription and the customer. If either has a usable method, it's not "missing."
For a tiny product with a real cost to serve (AI calls, storage, support), cancel is usually cleaner than leaving paused zombies. pause is useful if you want a soft "come back and add a card" path via the Customer Portal without creating a new subscription. create_invoice without a card is how you accidentally farm past_due rows.
Don't leave this unset and assume cancel. The default is invoice, not cancel.
(Stripe also has a newer "Trial Offers" API for flexible billing. Most indie Checkout setups still use the legacy trial_period_days path above. Same webhook names either way for the reminder.)
3. The emails: three messages, not a drip
You don't need a seven-email sequence at this size. You need three decisions written down:
1. Mid-trial (optional, day 3–5 of a 14-day trial). Only if they haven't hit your activation event. One short "here's the one thing to try" note. Skip it if they already did the thing. Calendar drips that ignore usage just train people to ignore you.
2. The trial_will_end email (your own, ~3 days out). This is the one that matters. Subject should say the date and the amount, not "Your trial is ending soon!!"
Something like:
Subject: Your trial ends Thursday. $29/mo starts then
Hey [name],
Your trial of [product] ends on [date]. If you keep the plan,
we'll charge $[amount] on that day to the card on file.
If you want to stop, cancel here before then: [portal or cancel link]
If the card isn't current, update it here: [portal / update link]
[your name]
Rules: no feature laundry list, no fake urgency, one cancel path that actually works. If there's no card on file (shape B), swap the middle for "add a payment method to keep access" and link the Customer Portal. Stripe's free-trial docs literally walk that portal flow off trial_will_end.
3. Day-of / day-after.
- Charged successfully → short receipt + "you're on the paid plan" (or rely on Stripe's receipt if you've tested it lands).
- No card + cancel → "trial ended, account is closed, here's how to start again if you want."
- No card + pause → "paused, add a card to resume" with the portal link.
- Payment failed → that's not a trial email anymore; treat it like a failed renewal (same tone as a failed-payment recovery mail, not a marketing nudge).
Under 40 trials you can send these from Resend/Loops/Postmark with a spreadsheet of trial end dates if webhooks feel scary. Webhooks are better once you're past a handful a week — but a missed webhook during a deploy is how someone gets charged with no warning. If you use webhooks, verify signatures and don't trust event order; re-fetch the subscription.
4. Access on the day the trial ends
Write this before the first trial ends. Same idea as a refund policy: inventing it while someone is logging in goes badly.
Rough matrix that works at small scale:
| Situation | Access I'd give |
|---|---|
Still trialing
|
Full product (or whatever the trial promised) |
trial_will_end fired, card on file |
Full access; show a calm banner "paid plan starts [date]" |
Trial ended, active, invoice paid |
Full paid access |
Trial ended, payment failed / past_due
|
Keep access for a short grace (e.g. 3–7 days) with a banner, then read-only or lock — match whatever you do for normal failed renewals |
Trial ended, canceled (no card + cancel) |
No access. Export/download if you promised data out. |
Trial ended, paused
|
No (or read-only) access until they resume with a card |
The mistake is checking only status === 'active'. A trial that just ended can sit in paused, canceled, past_due, or briefly look weird while the first invoice is open. If you've read how Stripe's eight subscription statuses actually map to access, you already know active alone lies in both directions.
Also decide: do trial users keep data after cancel? If yes for 30 days, say so in the day-of email. If you wipe on cancel, say that too. Silence here creates support tickets that feel like betrayal even when you did nothing sneaky.
5. A boring checklist before you open trials
Copy this into a note only you see:
Trials (internal)
- Length: __ days
- Card at signup: yes / no
- missing_payment_method: cancel / pause / create_invoice
- trial_will_end email: on (own template) / Stripe-hosted only
- Access on cancel: lock now / read-only __ days
- Access on pause: lock / read-only
- Grace on failed first charge: __ days
- Webhook events: trial_will_end, updated, deleted, paused, resumed,
invoice.paid, invoice.payment_failed
- Test with a test clock: happy path, no card, failed card
Run the test clock once. The "no card + cancel" path and the "card declines on first charge" path are the ones that surprise people in week three.
6. One product decision this is not
This whole post assumes you already picked a free trial (not freemium, not both). If you're still deciding the access model, that's a different problem. choosing free trial vs freemium before PMF is the decision frame I'd use first. Ending mechanics won't save a trial you never should have offered.
For the Stripe field names and the no-card cancel/pause setup, the source of truth is Stripe's free trial docs. The event timing table lives in their subscription webhooks guide.
If you're running trials right now: card at signup or not, and on the day it ends do you lock access immediately or leave a grace window? What broke the first time a trial actually ended?
來源:DEV Community · dev.to