githubEdit

circle-smallWiring Up Stripe

Payments is one of the places where we violate our own advice to use rest APIs directly instead of using Gems. In the case of payments, there is quite a lot to do in order to support the basics, so rolling our own solution here would be hard to keep updated and maintain consistency with over time. For example:

  • Maintaining our own database records for customers, charges, subscriptions.

  • Keeping everything in sync in our system when activity happens on the Stripe Platform

It just so happens that there's a very good gemarrow-up-right for that.

Pay Gem Setup

chevron-rightAdd the Gem(s)hashtag
# Gemfile
gem "pay", "~> 11.1"

# To use Stripe, also include:
gem "stripe", "~> 15.3"
chevron-rightCreate and run the migrationshashtag
rails pay:install:migrations
circle-info

If your user model happens to have an id column that's not the same type as your other id columns (usually integer), you should open up the migration file before running rails db:migrate and update the column type to the correct field type around line 6. This can happen if you're using uuid for user ids for example.

rails db:migrate
chevron-rightAdd an initializerhashtag
# config/initializers/pay.rb
Pay.setup do |config|
  # For use in the receipt/refund/renewal mailers
  config.business_name = "My Biz"
  config.business_address = ""
  config.application_name = "My App"
  config.support_email = "Scout <support@use-scout.com>"

  config.default_product_name = "default"
  config.default_plan_name = "default"

  config.automount_routes = true
  config.routes_path = "/pay" # Only when automount_routes is true
  # All processors are enabled by default. If a processor is already implemented in your application, you can omit it from this list and the processor will not be set up through the Pay gem.
  config.enabled_processors = [:stripe, :braintree, :paddle_billing, :paddle_classic, :lemon_squeezy]

  # To disable all emails, set the following configuration option to false:
  config.send_emails = true

  # By default emails are sent via Pay::UserMailer which inherits from Pay::ApplicationMailer. Instead, you may wish to inherit from ApplicationMailer, or use a different mailer entirely.
  config.parent_mailer = "ApplicationMailer"
  config.mailer = "MyCustomPayMailer"

  # All emails can be configured independently as to whether to be sent or not. The values can be set to true, false or a custom lambda to set up more involved logic. The Pay defaults are show below and can be modified as needed.
  config.emails.payment_action_required = true
  config.emails.payment_failed = true
  config.emails.receipt = true
  config.emails.refund = true
  # This example for subscription_renewing only applies to Stripe, therefore we supply the second argument of price
  config.emails.subscription_renewing = ->(pay_subscription, price) {
    (price&.type == "recurring") && (price.recurring&.interval == "year")
  }
  config.emails.subscription_trial_will_end = true
  config.emails.subscription_trial_ended = true

end
chevron-rightUpdate the user modelhashtag
# Tell the user model to use the pay gem
pay_customer default_payment_processor: :stripe, stripe_attributes: :stripe_attributes

# Set up the data to be sent to Stripe
def stripe_attributes(pay_customer)
  {
    metadata: {
      pay_customer_id: pay_customer.id,
      user_id: id 
    }
  }
end
chevron-rightSet Up Webhooks On Stripehashtag

We need Stripe to notify our app when things change over there. To do this

Events

Account

  • account.updated

Charge

  • charge.dispute.created

  • charge.succeeded

  • charge.updated

Checkout

  • checkout.session.async_payment_succeeded

  • checkout.session.completed

Customer

  • customer.deleted

  • customer.updated

  • customer.subscription.created

  • customer.subscription.deleted

  • customer.subscription.trial_will_end

  • customer.subscription.updated

Invoice

  • invoice.payment_action_required

  • invoice.payment_failed

  • invoice.upcoming

Payment Intent

  • payment_intent.succeeded

Payment Method

  • payment_method.attached

  • payment_method.detached

  • payment_method.updated

Subscription Schedule

  • subscription_schedule.created


A note on event types

The list of all webhooks the pay gem supports are herearrow-up-right. Most of the file names correspond directly to event names in stripe, except for

  • payment_action_required -> invoice.payment_action_required

  • payment_failed -> invoice.payment_failed

  • subscription_created -> customer_subscription_created

  • subscription_renewing -> invoice.upcoming

chevron-rightAdd sections to Active Adminhashtag

Add one file per Pay Database Model.

Further Setup

The previous steps ensure that our app data gets updated when user actions happen on Stripe. The following steps add the necessary screens and routes into our app.

  • Allow user to see what plan they're subscribed to and when (if) it expires.

  • Allow user to click a "Subscribe" button if they don't already have a plan (the actual credit card entry happens on Stripe).

  • Allow user to click a "Manage Subscription" button which takes them to a customer portal where they can manage or cancel their subscription.

chevron-rightAdd Routeshashtag
chevron-rightModel Methodshashtag

Include concern in user model

Create The Concern

chevron-rightControllerhashtag
chevron-rightStripe Utilshashtag

This is a tiny helper that we can use to enrich the raw data stored in the database which can be used in views, controllers, or emails

chevron-rightViewshashtag

Last updated