pay

by pay-rails

pay-rails / pay

A subscription engine for Ruby on Rails.

522 Stars 83 Forks Last release: Not found MIT License 378 Commits 17 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

Pay - Payments engine for Ruby on Rails

Build Status Gem Version

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

Current Payment Providers

  • Stripe (supports SCA using API version
    2020-08-27
    )
  • Braintree

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

Check the CHANGELOG for any required migrations or changes needed if you're upgrading from a previous version of Pay.

Tutorial

Want to see how Pay works? Check out our video getting started guide.

Installation

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

Migrations

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

bin/rails pay:install:migrations

We also need to run migrations to add Pay to the User, Account, Team, etc models that we want to make payments in our app.

bin/rails g pay User

This will generate a migration to add Pay fields to our User model and automatically includes the

Pay::Billable
module in our
User
model. Repeat this for all the models you want to make payments in your app.

Finally, run the migrations

rake db:migrate

Getting NoMethodError?

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

Fully restart your Rails application

bin/spring stop && rails s

Usage

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

An

email
attribute or method on your
Billable
model is required.

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.

Configuration

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

Credentials

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.

Generators

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

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

Billable API

Trials

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

Generic Trials

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

Creating a Charge

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.

On failure, a

Pay::Error
will be raised with details about the payment failure.

Creating a Subscription

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

For example, you can pass the

quantity
option to subscribe to a plan with for per-seat pricing.
user.subscribe(name: "default", plan: "default", quantity: 3)
Name

Name is an internally used name for the subscription.

Plan

Plan is the plan ID or price ID from the payment processor. For example:

plan_xxxxx
or
price_xxxxx
Options

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.

Retrieving a Subscription from the Database

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+')

Checking a User's Trial/Subscription Status

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

Checking a User's Subscription Status

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

Name is an internally used name for the subscription.

Plan

Plan is the plan ID from the payment processor.

Retrieving a Payment Processor Account

user = User.find_by(email: '[email protected]')

user.customer #> Stripe or Braintree customer account

Updating a Customer's Credit Card

user = User.find_by(email: '[email protected]')

user.update_card('payment_method_id')

Retrieving a Customer's Subscription from the Processor

user = User.find_by(email: '[email protected]')

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

Subscription API

Checking a Subscription's Trial Status

user = User.find_by(email: '[email protected]')

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

Checking a Subscription's Cancellation Status

user = User.find_by(email: '[email protected]')

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

Checking a Subscription's Grace Period Status

user = User.find_by(email: '[email protected]')

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

Checking to See If a Subscription Is Active

user = User.find_by(email: '[email protected]')

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

Cancel a Subscription (At End of Billing Cycle)

user = User.find_by(email: '[email protected]')

user.subscription.cancel

Cancel a Subscription Immediately

user = User.find_by(email: '[email protected]')

user.subscription.cancel_now!

Swap a Subscription to another Plan

user = User.find_by(email: '[email protected]')

user.subscription.swap("yearly")

Resume a Subscription on a Grace Period

user = User.find_by(email: '[email protected]')

user.subscription.resume

Retrieving the Subscription from the Processor

user = User.find_by(email: '[email protected]')

user.subscription.processor_subscription

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 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 & Webhooks

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}

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_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'

Payment Providers

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.

Braintree

development:
  braintree:
    private_key: xxxx
    public_key: yyyy
    merchant_id: zzzz
    environment: sandbox

Stripe

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.

Strong Customer Authentication (SCA)

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.confirmCardSetup
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.confirmCardPayment
if you'd like to charge the customer immediately (think checking out of a shopping cart).

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.

Stripe SCA Payment Confirmation

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

Contributors

Contributing

๐Ÿ‘‹ 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

License

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

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.