How Pay Gem Works
Pay is a payments engine for Rails that abstracts Stripe (and other processors) into a Rails-friendly interface.
1. Payment Processor Pattern
Always set a payment processor before any payment operations:
@user.set_payment_processor :stripe
# OR set default on model
class User < ApplicationRecord
pay_customer default_payment_processor: :stripe
end
Access the processor via @user.payment_processor for all operations.
2. Amounts in Cents
All amounts are in cents (smallest currency unit):
@user.payment_processor.charge(15_00) # $15.00
@user.payment_processor.charge(99_99) # $99.99
3. Webhooks Are Required
Pay relies on Stripe webhooks to sync subscription status, payment confirmations, and more. Without webhooks, your local records will be stale.
# Development: Use Stripe CLI
stripe listen --forward-to localhost:3000/pay/webhooks/stripe
4. SCA/3D Secure Handling
Stripe requires Strong Customer Authentication for EU payments. Pay handles this via:
Pay::ActionRequired exception for charges needing confirmation
- Built-in
/pay/payments/:id confirmation route
- Automatic webhook sync for async confirmations
5. Fake Processor for Testing
Use the fake processor to test without hitting Stripe:
@user.set_payment_processor :fake_processor, allow_fake: true
@user.payment_processor.subscribe(plan: "fake")
What would you like to do?
- Set up Pay gem in a new project
- Create a subscription
- Manage subscriptions (cancel, pause, resume, swap)
- Create a one-time charge
- Set up Stripe Checkout
- Set up Billing Portal
- Configure webhooks
- Implement metered/usage-based billing
- Set up Stripe Connect (marketplace)
- Debug payment issues
- Write tests
- Something else
Then read the matching workflow from workflows/ and follow it.
| Response |
Workflow |
| 1, "setup", "install", "new project" |
workflows/setup-pay-gem.md |
| 2, "subscribe", "subscription", "create subscription" |
workflows/create-subscription.md |
| 3, "cancel", "pause", "resume", "swap", "manage" |
workflows/manage-subscription.md |
| 4, "charge", "one-time", "payment" |
workflows/create-charge.md |
| 5, "checkout", "stripe checkout", "hosted" |
workflows/setup-stripe-checkout.md |
| 6, "portal", "billing portal", "customer portal" |
workflows/setup-billing-portal.md |
| 7, "webhook", "webhooks", "events" |
workflows/setup-webhooks.md |
| 8, "metered", "usage", "usage-based" |
workflows/implement-metered-billing.md |
| 9, "connect", "marketplace", "platform" |
workflows/setup-stripe-connect.md |
| 10, "debug", "not working", "error", "fix" |
workflows/debug-payments.md |
| 11, "test", "tests", "testing" |
workflows/write-tests.md |
After Every Change
# 1. Does it load?
bin/rails runner "Pay::Subscription"
# 2. Do tests pass?
bin/rails test test/models/
# 3. Check webhook forwarding (development)
stripe listen --forward-to localhost:3000/pay/webhooks/stripe
Report to the user:
- "Pay models load: OK"
- "Tests: X pass, Y fail"
- "Webhooks: forwarding to localhost"
Domain Knowledge
All in references/:
Setup: installation.md, configuration.md
Core: customers.md, payment-methods.md, charges.md, subscriptions.md
Stripe Features: stripe-checkout.md, billing-portal.md, sca-payment-intents.md
Advanced: webhooks.md, metered-billing.md, stripe-connect.md
Quality: testing.md, anti-patterns.md
Workflows
All in workflows/:
| File |
Purpose |
| setup-pay-gem.md |
Install and configure Pay with Stripe |
| create-subscription.md |
Create subscriptions with trials |
| manage-subscription.md |
Cancel, pause, resume, swap |
| create-charge.md |
One-time charges and refunds |
| setup-stripe-checkout.md |
Stripe Checkout integration |
| setup-billing-portal.md |
Customer self-service portal |
| setup-webhooks.md |
Webhook configuration |
| implement-metered-billing.md |
Usage-based pricing |
| setup-stripe-connect.md |
Marketplace payments |
| debug-payments.md |
Troubleshooting |
| write-tests.md |
Testing with fake processor |
1---2name: pay-gem3description: Build Rails payments with the Pay gem and Stripe. Full lifecycle - setup, subscriptions, one-time charges, Stripe Checkout, Billing Portal, webhooks, metered billing, Stripe Connect, testing, and debugging.4---56<essential_principles>78## How Pay Gem Works910Pay is a payments engine for Rails that abstracts Stripe (and other processors) into a Rails-friendly interface.1112### 1. Payment Processor Pattern1314Always set a payment processor before any payment operations:1516```ruby17@user.set_payment_processor :stripe18# OR set default on model19class User < ApplicationRecord20 pay_customer default_payment_processor: :stripe21end22```2324Access the processor via `@user.payment_processor` for all operations.2526### 2. Amounts in Cents2728All amounts are in cents (smallest currency unit):2930```ruby31@user.payment_processor.charge(15_00) # $15.0032@user.payment_processor.charge(99_99) # $99.9933```3435### 3. Webhooks Are Required3637Pay relies on Stripe webhooks to sync subscription status, payment confirmations, and more. Without webhooks, your local records will be stale.3839```bash40# Development: Use Stripe CLI41stripe listen --forward-to localhost:3000/pay/webhooks/stripe42```4344### 4. SCA/3D Secure Handling4546Stripe requires Strong Customer Authentication for EU payments. Pay handles this via:47- `Pay::ActionRequired` exception for charges needing confirmation48- Built-in `/pay/payments/:id` confirmation route49- Automatic webhook sync for async confirmations5051### 5. Fake Processor for Testing5253Use the fake processor to test without hitting Stripe:5455```ruby56@user.set_payment_processor :fake_processor, allow_fake: true57@user.payment_processor.subscribe(plan: "fake")58```5960</essential_principles>6162<intake>6364**What would you like to do?**65661. Set up Pay gem in a new project672. Create a subscription683. Manage subscriptions (cancel, pause, resume, swap)694. Create a one-time charge705. Set up Stripe Checkout716. Set up Billing Portal727. Configure webhooks738. Implement metered/usage-based billing749. Set up Stripe Connect (marketplace)7510. Debug payment issues7611. Write tests7712. Something else7879**Then read the matching workflow from `workflows/` and follow it.**8081</intake>8283<routing>8485| Response | Workflow |86|----------|----------|87| 1, "setup", "install", "new project" | `workflows/setup-pay-gem.md` |88| 2, "subscribe", "subscription", "create subscription" | `workflows/create-subscription.md` |89| 3, "cancel", "pause", "resume", "swap", "manage" | `workflows/manage-subscription.md` |90| 4, "charge", "one-time", "payment" | `workflows/create-charge.md` |91| 5, "checkout", "stripe checkout", "hosted" | `workflows/setup-stripe-checkout.md` |92| 6, "portal", "billing portal", "customer portal" | `workflows/setup-billing-portal.md` |93| 7, "webhook", "webhooks", "events" | `workflows/setup-webhooks.md` |94| 8, "metered", "usage", "usage-based" | `workflows/implement-metered-billing.md` |95| 9, "connect", "marketplace", "platform" | `workflows/setup-stripe-connect.md` |96| 10, "debug", "not working", "error", "fix" | `workflows/debug-payments.md` |97| 11, "test", "tests", "testing" | `workflows/write-tests.md` |9899</routing>100101<verification_loop>102103## After Every Change104105```bash106# 1. Does it load?107bin/rails runner "Pay::Subscription"108109# 2. Do tests pass?110bin/rails test test/models/111112# 3. Check webhook forwarding (development)113stripe listen --forward-to localhost:3000/pay/webhooks/stripe114```115116Report to the user:117- "Pay models load: OK"118- "Tests: X pass, Y fail"119- "Webhooks: forwarding to localhost"120121</verification_loop>122123<reference_index>124125## Domain Knowledge126127All in `references/`:128129**Setup:** installation.md, configuration.md130**Core:** customers.md, payment-methods.md, charges.md, subscriptions.md131**Stripe Features:** stripe-checkout.md, billing-portal.md, sca-payment-intents.md132**Advanced:** webhooks.md, metered-billing.md, stripe-connect.md133**Quality:** testing.md, anti-patterns.md134135</reference_index>136137<workflows_index>138139## Workflows140141All in `workflows/`:142143| File | Purpose |144|------|---------|145| setup-pay-gem.md | Install and configure Pay with Stripe |146| create-subscription.md | Create subscriptions with trials |147| manage-subscription.md | Cancel, pause, resume, swap |148| create-charge.md | One-time charges and refunds |149| setup-stripe-checkout.md | Stripe Checkout integration |150| setup-billing-portal.md | Customer self-service portal |151| setup-webhooks.md | Webhook configuration |152| implement-metered-billing.md | Usage-based pricing |153| setup-stripe-connect.md | Marketplace payments |154| debug-payments.md | Troubleshooting |155| write-tests.md | Testing with fake processor |156157</workflows_index>