Answers · Billing

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.

The change

class ReportsController < ApplicationController
  include RequireEntitlement
end

That is the whole gate. The concern adds a before_action that runs after authentication, so Current.account is already set when it checks.

Why the meaning lives somewhere else

RequireEntitlement deliberately does not know what entitlement is. It asks Entitlement.for(Current.account).entitled? and acts on the answer. The definition is one line in app/models/entitlement.rb:

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

Point that at a different strategy and every gated controller changes at once. The file ships with a paid subscription as the default and an Open strategy that gates nothing, which is what the remove-billing skill switches to when an app turns out to be free. No controller is edited either way.

What the gate does not do

It answers one question: may this workspace use this surface at all. It says nothing about whether this user may see this particular record. That is Pundit's job, and the two are orthogonal. A gated controller still calls authorize and policy_scope, or a paying customer can read another paying customer's rows.

Check it both ways

A gate is only real if a spec proves the closed case:

bin/rspec spec/requests/projects_spec.rb

Two examples, minimum: an entitled account gets 200, an unentitled one is redirected. The example Project slice has both, and it is the shape to copy.

Entitlement reads never call Stripe

The check reads a local Subscription row that the webhook keeps in sync. That matters for latency and for failure modes: a Stripe outage does not log out your paying customers, because nothing at request time talks to Stripe at all.

Related questions

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

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.