Skip to content
A subscription engine for Ruby on Rails.
Branch: master
Clone or download
Latest commit 91c59e3 Jun 11, 2019
Type Name Latest commit message Commit time
Failed to load latest commit information.
app Allow mount path to be customised (#77) Jun 11, 2019
bin Initial commit Feb 1, 2017
config Allow mount path to be customised (#77) Jun 11, 2019
db/migrate Make billable fields migration generic instead of referencing Users Mar 13, 2019
lib Version 1.0.0 Jun 11, 2019
test Version 1.0.0 Jun 11, 2019
.gitignore Bumps version for initial release Jun 12, 2017
.rubocop.yml Continues organizing API and tests all existing methods Jun 11, 2017
.travis.yml Update travis ruby to 2.6.1 Feb 11, 2019 Version 1.0.0 Jun 11, 2019
Gemfile Continues implementiong new Stripe module Jun 11, 2017
Gemfile.lock Version 1.0.0 Jun 11, 2019
MIT-LICENSE Updates gemspec; minor tinkering Feb 9, 2019 Allow mount path to be customised (#77) Jun 11, 2019
Rakefile Updates gemspec; minor tinkering Feb 9, 2019
logo.png Updates logo Feb 9, 2019


Payments engine for Ruby on Rails

Build Status

Pay is a payments engine for Ruby on Rails 4.2 and higher.

Current Payment Providers

  • Stripe (API version 2018-08-23 or higher required)
  • Braintree

Want to add a new payment provider? Contributions are welcome and the instructions are here.


Add this line to your application's Gemfile:

gem 'pay'

# To use Stripe, also include:
gem 'stripe', '< 5.0', '>= 2.8'
gem 'stripe_event', '~> 2.2'

# To use Braintree + PayPal, also include:
gem 'braintree', '< 3.0', '>= 2.92.0'

# To use Receipts
gem 'receipts', '~> 0.2.2'

And then execute:

$ bundle

Or install it yourself as:

$ gem install pay



This engine will create a subscription model and the neccessary migrations for the model you want to make "billable." The most common use case for the billable model is a User.

To add the migrations to your application, run the following migration:

$ bin/rails pay:install:migrations

This will install three migrations:

  • db/migrate/
  • db/migrate/
  • db/migrate/

The User Model

If you have a User model defined in app/models/user.rb Pay will add the fields it needs to the users table.

If you do not have a User model defined, Pay will create a users table and you will need to add app/models/user.rb.

Non-User Model

If you need to use a model other than User, check out the wiki page.

Run the Migrations

Finally, run the migrations with $ rake db:migrate

Getting NoMethodError?

NoMethodError (undefined method 'stripe_customer' for #<User:0x00007fbc34b9bf20>)

Fully restart your Rails application bin/spring stop && rails s


You'll need to add your private Stripe API key to your Rails secrets config/secrets.yml, credentials rails credentials:edit

    private_key: xxxx
    public_key: yyyy
    signing_secret: zzzz

You can also use the STRIPE_PRIVATE_KEY and STRIPE_SIGNING_SECRET environment variables.

To see how to use Stripe Elements JS & Devise, click here.

Background jobs

If a user's email is updated and they have a processor_id set, Pay will enqueue a background job (EmailSyncJob) to sync the email with the payment processor.

It's important you set a queue_adapter for this to happen. If you don't, the code will be executed immediately upon user update. More information here


Include the Pay::Billable module in the model you want to know about subscriptions.

# app/models/user.rb
class User < ActiveRecord::Base
  include Pay::Billable

To sync over customer names, your Billable model should respond to the first_name and last_name methods. Pay will sync these over to your Customer objects in Stripe and Braintree.


Need to make some changes to how Pay is used? You can create an initializer config/initializers/pay.rb

Pay.setup do |config|
  config.billable_class = 'User'
  config.billable_table = 'users'

  config.chargeable_class = 'Pay::Charge'
  config.chargeable_table = 'pay_charges'

  # For use in the receipt/refund/renewal mailers
  config.business_name = "Business Name"
  config.business_address = "1600 Pennsylvania Avenue NW"
  config.application_name = "My App"
  config.support_email = ""

  config.send_emails = true

  config.automount_webhook_routes = true
  config.webhooks_path = "/webhooks" # Only when automount_webhook_routes is true

This allows you to create your own Charge class for instance, which could add receipt functionality:

class Charge < Pay::Charge
  def receipts
    # do some receipts stuff using the gem

Pay.setup do |config|
  config.chargeable_class = 'Charge'


Email Templates

If you want to modify the email templates, you can copy over the view files using:

$ bin/rails generate pay:email_views



Emails can be enabled/disabled using the send_emails configuration option (enabled per default). When enabled, the following emails will be sent:

  • When a charge succeeded
  • When a charge was refunded
  • When a subscription is about to renew

User API


You can check if the user is on a trial by simply asking:

user = User.find_by(email: '')

user.on_trial? #=> true or false

The on_trial? method has two optional arguments with default values.

user = User.find_by(email: '')

user.on_trial?(name: 'default', plan: 'plan') #=> true or false

Generic Trials

For trials that don't require cards upfront:

user = User.create(
  email: '',
  trial_ends_at: 30.days.from_now

user.on_generic_trial? #=> true

Creating a Charge

user = User.find_by(email: '')

user.processor = 'stripe'
user.card_token = 'stripe-token'
user.charge(1500) # $15.00 USD

user = User.find_by(email: '')

user.processor = 'braintree'
user.card_token = 'nonce'
user.charge(1500) # $15.00 USD

The charge method takes the amount in cents as the primary argument.

You may pass optional arguments that will be directly passed on to either Stripe or Braintree. You can use these options to charge different currencies, etc.

Creating a Subscription

user = User.find_by(email: '')

user.processor = 'stripe'
user.card_token = 'stripe-token'

A card_token must be provided as an attribute.

The subscribe method has three optional arguments with default values.

def subscribe(name: 'default', plan: 'default', **options)

Name is an internally used name for the subscription.


Plan is the plan ID from the payment processor.


They do something?

Retrieving a Subscription from the Database

user = User.find_by(email: '')


A subscription can be retrieved by name, too.

user = User.find_by(email: '')

user.subscription(name: 'bananastand+')

Checking a User's Subscription Status

user = User.find_by(email: '')

The subscribed? method has two optional arguments with default values.

def subscribed?(name: 'default', plan: nil)

Name is an internally used name for the subscription.


Plan is the plan ID from the payment processor.

Retrieving a Payment Processor Account

user = User.find_by(email: '')

user.customer #> Stripe or Braintree customer account

Updating a Customer's Credit Card

user = User.find_by(email: '')


Retrieving a Customer's Subscription from the Processor

user = User.find_by(email: '')

user.processor_subscription(subscription_id) #=> Stripe or Braintree Subscription

Subscription API

Checking a Subscription's Trial Status

user = User.find_by(email: '')

user.subscription.on_trial? #=> true or false

Checking a Subscription's Cancellation Status

user = User.find_by(email: '')

user.subscription.cancelled? #=> true or false

Checking a Subscription's Grace Period Status

user = User.find_by(email: 'her?')

user.subscription.on_grace_period? #=> true or false

Checking to See If a Subscription Is Active

user = User.find_by(email: '') #=> true or false

Cancel a Subscription (At End of Billing Cycle)

user = User.find_by(email: '')


Cancel a Subscription Immediately

user = User.find_by(email: '')


Swap a Subscription to another Plan

user = User.find_by(email: '')


Resume a Subscription on a Grace Period

user = User.find_by(email: '')


Retrieving the Subscription from the Processor

user = User.find_by(email: '')


Customizing Pay Models

Want to add methods to Pay::Subscription or Pay::Charge? You can define a concern and simply include it in the model when Rails loads the code.

Pay uses the to_prepare method to allow concerns to be included every time Rails reloads the models in development as well.

# app/models/concerns/subscription_extensions.rb
module SubscriptionExtensions
  extend ActiveSupport::Concern

  included do
    # associations and other class level things go here

  # instance methods and code go here
# config/initializers/subscription_extensions.rb

# Re-include the SubscriptionExtensions every time Rails reloads
Rails.application.config.to_prepare do
  Pay.subscription_model.include SubscriptionExtensions


Webhooks are automatically mounted to /webhooks/{provider}.

Customizing webhook mount path

If you have a catch all route (for 404s etc) and need to control where/when the webhook endpoints mount, you will need to disable automatic mounting and mount the engine above your catch all route.

# config/initializers/pay.rb
config.automount_webhook_routes = false

# config/routes.rb
mount Pay::Engine, at: '/secret-webhook-path'

If you just want to modify where the engine mounts it's routes then you can change the path.

# config/initializers/pay.rb

config.webhooks_path = '/secret-webhook-path'



👋 Thanks for your interest in contributing. Feel free to fork this repo.

If you have an issue you'd like to submit, please do so using the issue tracker in GitHub. In order for us to help you in the best way possible, please be as detailed as you can.

If you'd like to open a PR please make sure the following things pass:

  • rake test


The gem is available as open source under the terms of the MIT License.

You can’t perform that action at this time.