Answers · Billing

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.

The two places that change

The checkout session. A subscription session uses subscription mode and a recurring price. A one-time session uses payment mode and a one-time price. That is a parameter in the billing adapter, not a different integration.

What entitlement means. app/models/entitlement.rb holds one line that decides:

def for(account) = Subscription.new(account)

For a permanent purchase, the strategy reads a paid-once record rather than a subscription status. The file ships with several strategies and a comment saying it is a file you edit rather than a framework to extend, which is exactly what this change is.

What does not change

Every controller with include RequireEntitlement. That is the reason the seam exists: the gate and the meaning of the gate are separate, so switching your business model does not mean auditing controllers to find the ones that gate.

The webhook handling changes shape, though. One-time payments produce checkout.session.completed and charge.refunded, and no invoice or subscription events at all. So the renewal and cancellation branches simply stop firing.

Refunds are the part to think about

A subscription that stops renewing needs no decision. A permanent purchase that gets refunded does: the money went back, and the account still has the record that entitles it. Handle charge.refunded and revoke, or you are giving the product away to anyone who asks their bank.

This site does both

shiponeshot.com sells a one-time license and runs the subscription flow the kit ships with, which is why both paths exist in the codebase. config/routes.rb shows the split: the subscription routes and the kit purchase routes live side by side.

Verify before you charge

bin/check

Then run the flow against a test-mode key and refund the charge in the dashboard. If the account still has access after the refund, the revoke branch is not wired, and you want to find that out now.

Related questions

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 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.