This guide documents the three Stripe Dashboard toggles every Accrue host app must enable on its Customer Billing Portal configuration before going live. All three close revenue-recovery loopholes that the default portal config leaves wide open.
Background — the "cancel without dunning" footgun
Stripe's out-of-the-box Customer Portal lets customers cancel
immediately with one click and zero friction. From the host app's
point of view, this turns the portal into a "cancel my account" button
that bypasses every dunning workflow Accrue offers
(Accrue.Billing.Dunning, [:accrue, :ops, :dunning_exhaustion],
grace periods, retain-offer flows, and so on).
The portal's out-of-the-box defaults erase much of the revenue-recovery surface unless three specific toggles are flipped in the Stripe Dashboard. Flipping them is free, takes 30 seconds, and is a one-time per-mode (test + live) action.
Programmatic configuration via BillingPortal.Configuration is
deferred to a future processor release. Until then this guide is the
canonical install-time checklist — same convention as Pay (Rails) and
Cashier (Laravel).
The three required toggles
Open the Stripe Dashboard → Settings → Billing → Customer portal in both test mode and live mode and configure the following:
1. Retain offers — ENABLED
Section: Cancellations → Retain offers.
Action: enable at least one retain offer (e.g. "50% off the next month", "switch to annual for 20% off"). Stripe presents the offer to customers who click cancel before processing the cancellation.
Why: this is the single highest-impact lever in the entire portal. Empirically, retain offers convert ~10–25% of cancellations into saves. With it disabled the portal cancels with no resistance.
2. Require cancellation reason — ENABLED
Section: Cancellations → Cancellation reason.
Action: toggle "Ask for a cancellation reason" on. Choose either "required" or "optional with reasons list" — required is strongly preferred for the survey signal.
Why: gives the host app a structured cancellation_details.reason
field on the resulting customer.subscription.deleted /
customer.subscription.updated webhook payload. Without this you have
no churn-reason data to feed back into product / pricing / support.
3. Cancellation timing — at_period_end (NOT immediate)
Section: Cancellations → When to cancel.
Action: select "At end of billing period". Do NOT select "Immediately".
Why: with "Immediately" selected the customer is refunded prorated
charges and loses access on the spot. With at_period_end the
customer keeps access through the period they already paid for, the
subscription transitions to cancel_at_period_end: true, and
Accrue.Billing.Subscription.canceling?/1 returns true so the host
app can trigger any "we're sorry to see you go" mailers, retention
campaigns, or win-back flows during the grace period.
This is also the only setting that makes reversing a scheduled cancellation useful — you can't undo a subscription that has already been hard-deleted.
Verifying the checklist
After flipping all three toggles, click "Save" in the Dashboard.
Stripe assigns the new configuration a bpc_* id which you can find
under Settings → Billing → Customer portal → Active configuration.
To pin Accrue to that exact configuration (recommended for production
so a future Dashboard edit can't silently reset the toggles), pass
the id to Accrue.BillingPortal.Session.create/1:
{:ok, session} =
Accrue.BillingPortal.Session.create(%{
customer: current_user.customer,
return_url: url(~p"/account"),
configuration: "bpc_1Nx9aB2eZvKYlo2C..."
})If :configuration is omitted Stripe uses the account default — fine
for development, risky for production because a future Dashboard edit
to the default config silently affects every portal session.
Future programmatic support
When BillingPortal.Configuration lands in a future processor
release, this checklist will be replaced (additively, no breaking
change) with an Accrue.BillingPortal.Configuration.create/1 helper
that ensures the three toggles are set in code. Until then the
Dashboard checklist above is the source of truth.
See also
Accrue.BillingPortal.Session— wrapper moduleAccrue.Billing.Dunning— the revenue-recovery surface that these toggles protect- Stripe Dashboard — Settings → Billing → Customer portal (test and live)