Pay is a payments engine for Ruby on Rails 4.2 and higher.
Current Payment Providers
- Stripe (supports SCA, API version 2019-03-14 or higher required)
- Braintree
Want to add a new payment provider? Contributions are welcome and the instructions are here.
Want to see how Pay works? Check out our video getting started guide.
Add these lines to your application's Gemfile:
gem 'pay', '~> 2.0'
# To use Stripe, also include:
gem 'stripe', '< 6.0', '>= 2.8'
gem 'stripe_event', '~> 2.3'
# To use Braintree + PayPal, also include:
gem 'braintree', '< 3.0', '>= 2.92.0'
# To use Receipts
gem 'receipts', '~> 1.0.0'
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/create_subscriptions.pay.rb
- db/migrate/create_charges.pay.rb
- db/migrate/add_status_to_subscriptions.pay.rb
You'll also need a model that can make payments. This is called a
Billable
model. The pay
generator will add fields to the model and
add the Pay::Billable
module to it.
$ bin/rails g pay User
Finally, run the migrations with $ rake db:migrate
NoMethodError (undefined method 'stripe_customer' for #<User:0x00007fbc34b9bf20>)
Fully restart your Rails application bin/spring stop && rails s
The Pay::Billable
module should be included in the models you want to make payments and subscriptions.
# app/models/user.rb
class User < ActiveRecord::Base
include Pay::Billable
end
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.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 = "[email protected]"
config.send_emails = true
config.automount_routes = true
config.routes_path = "/pay" # Only when automount_routes is true
end
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 https://github.com/excid3/receipts gem
end
end
Pay.setup do |config|
config.chargeable_class = 'Charge'
end
You'll need to add your private Stripe API key to your Rails secrets config/secrets.yml
, credentials rails credentials:edit
development:
stripe:
private_key: xxxx
public_key: yyyy
signing_secret: zzzz
braintree:
private_key: xxxx
public_key: yyyy
merchant_id: aaaa
environment: sandbox
For Stripe, you can also use the STRIPE_PUBLIC_KEY
, STRIPE_PRIVATE_KEY
and STRIPE_SIGNING_SECRET
environment variables.
For Braintree, you can also use BRAINTREE_MERCHANT_ID
, BRAINTREE_PUBLIC_KEY
, BRAINTREE_PRIVATE_KEY
, and BRAINTREE_ENVIRONMENT
environment variables.
If you want to modify the Stripe SCA template or any other views, you can copy over the view files using:
bin/rails generate pay:views
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
You can check if the user is on a trial by simply asking:
user = User.find_by(email: '[email protected]')
user.on_trial? #=> true or false
The on_trial?
method has two optional arguments with default values.
user = User.find_by(email: '[email protected]')
user.on_trial?(name: 'default', plan: 'plan') #=> true or false
For trials that don't require cards upfront:
user = User.create(
email: '[email protected]',
trial_ends_at: 30.days.from_now
)
user.on_generic_trial? #=> true
user = User.find_by(email: '[email protected]')
user.processor = 'stripe'
user.card_token = 'payment_method_id'
user.charge(1500) # $15.00 USD
user = User.find_by(email: '[email protected]')
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.
user = User.find_by(email: '[email protected]')
user.processor = 'stripe'
user.card_token = 'payment_method_id'
user.subscribe
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)
...
end
Name is an internally used name for the subscription.
Plan is the plan ID from the payment processor.
By default, the trial specified on the subscription will be used.
trial_period_days: 30
can be set to override and a trial to the subscription. This works the same for Braintree and Stripe.
user = User.find_by(email: '[email protected]')
user.subscription
A subscription can be retrieved by name, too.
user = User.find_by(email: '[email protected]')
user.subscription(name: 'bananastand+')
user = User.find_by(email: '[email protected]')
user.on_trial_or_subscribed?
The on_trial_or_subscribed?
method has two optional arguments with default values.
def on_trial_or_subscribed?(name: 'default', plan: nil)
...
end
user = User.find_by(email: '[email protected]')
user.subscribed?
The subscribed?
method has two optional arguments with default values.
def subscribed?(name: 'default', plan: nil)
...
end
Name is an internally used name for the subscription.
Plan is the plan ID from the payment processor.
user = User.find_by(email: '[email protected]')
user.customer #> Stripe or Braintree customer account
user = User.find_by(email: '[email protected]')
user.update_card('payment_method_id')
user = User.find_by(email: '[email protected]')
user.processor_subscription(subscription_id) #=> Stripe or Braintree Subscription
user = User.find_by(email: '[email protected]')
user.subscription.on_trial? #=> true or false
user = User.find_by(email: '[email protected]')
user.subscription.cancelled? #=> true or false
user = User.find_by(email: '[email protected]')
user.subscription.on_grace_period? #=> true or false
user = User.find_by(email: '[email protected]')
user.subscription.active? #=> true or false
user = User.find_by(email: '[email protected]')
user.subscription.cancel
user = User.find_by(email: '[email protected]')
user.subscription.cancel_now!
user = User.find_by(email: '[email protected]')
user.subscription.swap("yearly")
user = User.find_by(email: '[email protected]')
user.subscription.resume
user = User.find_by(email: '[email protected]')
user.subscription.processor_subscription
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
end
# instance methods and code go here
end
# config/initializers/subscription_extensions.rb
# Re-include the SubscriptionExtensions every time Rails reloads
Rails.application.config.to_prepare do
Pay.subscription_model.include SubscriptionExtensions
end
Routes are automatically mounted to /pay
by default.
We provide a route for confirming SCA payments at /pay/payments/:payment_intent_id
Webhooks are automatically mounted at /pay/webhooks/{provider}
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_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.routes_path = '/secret-webhook-path'
We support both Stripe and Braintree and make our best attempt to standardize the two. They function differently so keep that in mind if you plan on doing more complex payments. It would be best to stick with a single payment provider in that case so you don't run into discrepancies.
development:
braintree:
private_key: xxxx
public_key: yyyy
merchant_id: zzzz
environment: sandbox
You'll need to add your private Stripe API key to your Rails secrets config/secrets.yml
, credentials rails credentials:edit
development:
stripe:
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.
Our Stripe integration requires the use of Payment Method objects to correctly support Strong Customer Authentication with Stripe. If you've previously been using card tokens, you'll need to upgrade your Javascript integration.
Subscriptions that require SCA are marked as incomplete
by default.
Once payment is authenticated, Stripe will send a webhook updating the
status of the subscription. You'll need to use the Stripe CLI to forward
webhooks to your application to make sure your subscriptions work
correctly for SCA payments.
stripe listen --forward-to localhost:3000/pay/webhooks/stripe
You should use stripe.handleCardSetup
on the client to collect card information anytime you want to save the card and charge them later (adding a card, then charging them on the next page for example). Use stripe.handleCardPayment
if you'd like to charge the customer immediately (think checking out of a shopping cart).
The Javascript will now need to use createPaymentMethod instead of createToken. https://stripe.com/docs/js/payment_intents/create_payment_method
The Javascript also needs to have a PaymentIntent or SetupIntent created server-side and the ID passed into the Javascript to do this. That way it knows how to safely handle the card tokenization if it meets the SCA requirements.
Payment Confirmations
Sometimes you'll have a payment that requires extra authentication. In this case, Pay provides a webhook and action for handling these payments. It will automatically email the customer and provide a link with the PaymentIntent ID in the url where the customer will be asked to fill out their name and card number to confirm the payment. Once done, they'll be redirected back to your application.
If you'd like to change the views of the payment confirmation page, you can install the views using the generator and modify the template.
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
👋 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.