← All answers

Answers

Billing

Charging for the app, gating what people paid for, and what Stripe needs from you.

Can I charge a one-time price instead of a subscription?

Yes. Create a one-time price in Stripe, use payment mode rather than subscription mode in the checkout session, and change what Entitlement.for returns so a completed purchase entitles the account permanently. The gate on each controller does not change, because RequireEntitlement never knew what it was checking.

Do I have to handle failed payments and refunds myself?

No. Stripe retries failed charges, emails customers, and processes refunds through its dashboard. Your app subscribes to invoice.payment_failed, invoice.payment_succeeded, charge.refunded and customer.subscription.deleted, and keeps one local Subscription row in sync. That is the whole of your responsibility.

How do I change what a paid plan unlocks in my app?

To gate a feature, add include RequireEntitlement to its controller. To ungate it, delete that line. To change what counts as paid at all, edit the single strategy line in app/models/entitlement.rb. Those are two different decisions and they live in two different places on purpose.

How do I gate a feature behind a paid subscription in Rails?

Add include RequireEntitlement to the controller. Unentitled accounts are redirected to the pricing page with a message; admins are exempt. What "entitled" means is defined separately in app/models/entitlement.rb, so you can change the gate from a subscription to a token balance or to nothing without touching any controller.

How do I let customers cancel their own subscription?

Send them to the Stripe billing portal rather than building a cancellation UI. One Shot ships the route already: a form that posts to the billing portal controller and redirects to Stripe. When they cancel there, customer.subscription.deleted arrives and the local row is updated, so access ends without you writing any of it.

How do I offer a free trial without taking a card upfront?

Set a trial period on the Stripe price or the checkout session and allow it without a payment method. The app needs no change: Subscription#entitled? returns true for both active and trialing, so a trialing account has full access, and customer.subscription.updated moves it when the trial ends.

How do I remove billing from an app that will be free?

Use the remove-billing skill. It removes the Stripe subscription flow and the mobile in-app purchase path, and leaves authentication, tenancy and the entitlement seam in place. The seam matters: include RequireEntitlement keeps working against an open strategy, so gated controllers are not rewritten and can be gated again later.

How do I run a launch price that expires on a date?

Set STRIPE_PRICE_KIT_LAUNCH and KIT_LAUNCH_PRICE_ENDS_AT. The app decides which price id to use from the date, and the same decision drives the number shown on the pricing page and the structured data. One source, so the price a customer is charged can never disagree with the price they were shown.

How do I see which accounts are actually paying me?

Query the Subscription table in the Rails console: active and trialing rows are the entitled accounts. One Shot ships no admin UI on purpose. For anything about money rather than access, read Stripe's dashboard instead, because the local row tracks entitlement and Stripe tracks what was actually charged.

How do I test a checkout flow without a Stripe account?

Do nothing. With STRIPE_SECRET_KEY unset, Billing.for(account) returns a local fake that runs checkout, entitlement and cancellation in process. You click through the real flow, the subscription row is written, and gated features unlock. Set the key when you want real Stripe, and the same code paths switch over.

What happens when a paying customer's card expires?

Stripe retries the charge on a schedule and emails the customer. Your app receives invoice.payment_failed, which moves the subscription to past_due rather than canceling it. Access continues, because past_due is a recoverable state and most of these recover. Only customer.subscription.deleted ends entitlement.

What is a statement descriptor and why set it before charging?

It is the text that appears on a customer's card statement next to the charge. Set it in the Stripe dashboard before you take any money. Changing it later only affects future charges, so early customers keep whatever was there, and an unrecognizable descriptor is one of the most common causes of a chargeback.

Which Stripe webhook events does a subscription app need?

Eight: checkout.session.completed, invoice.payment_succeeded, invoice.payment_failed, customer.subscription.updated, customer.subscription.deleted, charge.refunded, charge.dispute.created and radar.early_fraud_warning.created. The first five keep entitlement correct; the last three are the money events you want to know about rather than discover in a statement.

Why does a paying customer still see the paywall?

The local Subscription row for that account is not active. Entitlement never calls Stripe at request time, so a successful payment that produced no webhook leaves the row untouched. Check the row first, then Stripe's webhook delivery log. The third possibility is that the charge belongs to a different account than the one they are signed into.

Why does my Stripe webhook keep returning a 400 error?

Signature verification failed. Either STRIPE_WEBHOOK_SECRET does not match the endpoint sending the event, or something read and re-encoded the request body before verification ran. Each endpoint in Stripe has its own secret, and the CLI's local secret is different again, which is the usual cause of a 400 that appears only in production.