- Use OAuth to connect to your customer's accounts
- Make payments to third-party bank accounts with Recipients and Transfers
Marketplaces let multiple people sell goods and services on the same site at the same time. Stripe lets your SaaS app implement a marketplace in two different ways. With Stripe Connect your users connect their Stripe account with yours, allowing you to make charges with their Stripe account securely and passing fees through directly. This is great for a technical audience or for a marketplace whose participants want to be able to manage their own Stripe account.
Stripe recently added the ability to send transfers to any authorized US checking account via ACH transfers using a feature called Payouts. This enables for more nuanced interactions with your marketplace participants. For example, you could send their collected payments to them once a month, or only if they've passed a certain threshold. Payouts also allows non-technical people to easily participate in your marketplace since they don't have to leave your site to create a Stripe account via Stripe's OAuth flow.
Both Connect and Payouts are easily integrated into a Rails application with a few minor changes. In fact, depending on your use case you may want to hook up both and let market participants decide which is best for them.
Connect is Stripe's OAuth2-based way for your application to create transactions,
customers, subscription plans, and everything else Stripe has to offer on behalf of
another user. Hooking it up to your Rails application is easy because someone else
has done all of the hard work for you in a gem named OmniAuth::StripeConnect.
This gem uses the OmniAuth OAuth abstraction library to allow you to connect to
your users' Stripe accounts by simply sending them to /auth/stripe_connect
, which
will direct them through the OAuth2 dance and bring them back to your site.
To start hooking this up, simply add the gem to your Gemfile
:
gem 'omniauth-stripe-connect', '>= 2.4.0'
Then, add the middleware in an initializer config/initializers/omniauth.rb
:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :stripe_connect, ENV['STRIPE_CONNECT_CLIENT_ID'], ENV['STRIPE_SECRET_KEY']
end
STRIPE_SECRET_KEY
is the same key we set up in config/initializers/stripe.rb
way
back in the beginning. To get the value for STRIPE_CONNECT_CLIENT_ID
you need to
register an application. This should be a simple process but I've had trouble with it
before. Feel free to contact Stripe's very helpful support if you hit any snags.
When you send the user through /auth/stripe_connect
, after registering them or
logging them in Stripe will send them back to /auth/stripe_connect/callback
(via
OmniAuth). Add a route to handle this:
get '/auth/stripe_connect/callback', to: 'stripe_connect#create'
Let's set up that controller in app/controllers/stripe_connect.rb
:
class StripeConnectController < ApplicationController
def create
auth_hash = request.env['omniauth.auth']
current_user.stripe_id = auth_hash['uid']
current_user.stripe_access_key = auth_hash['credentials']['token']
current_user.stripe_publishable_key = auth_hash['info']['stripe_publishable_key']
current_user.stripe_refresh_token = auth_hash['credentials']['refresh_token']
current_user.save!
flash[:notice] = "Stripe info saved"
redirect_to '/'
end
end
OmniAuth will run before your controller and populate a key in the request env
named omniauth.auth
with the OAuth2 information that Stripe returned. The salient
bits are the user's uid
, their stripe_access_key, stripe_publishable_key
, and
refresh token. You need to save the refresh token so you can regenerate your access
key if necessary. For example, if you were vulnerable to the Heartbleed SSL attack
you should have rolled your customers' access tokens.
Before any of this will work we need to add the fields to User
:
$ rails g migration AddStripeConnectFieldsToUser \
stripe_id:string \
stripe_access_key:string \
stripe_publishable_key:string \
stripe_refresh_token:string
$ rake db:migrate
To actually charge cards with an authenticated user's credentials all you have to do is pass the user's access key to the create call:
charge = Stripe::Charge.create(
{
amount: 1000,
application_fee: 100,
currency: 'usd',
source: params[:stripeToken],
description: '[email protected]',
},
user.stripe_access_key
)
Note also in this example that we're passing the application_fee
option. This
subtracts that amount from the total amoun
t after Stripe subtracts it's fee. So, in
this example the user would get $8.41 deposited in their account seven days later:
Amount 1000
Stripe Fee: 59 (1000 * 0.029) + 30
Application Fee 100
-----------------------
Total Paid to User 841
You'll also need to use the user's publishable key instead of your own when
tokeninzing cards with stripe.js
. For a custom form that would be:
<script type="text/javascript">
$(function(){
Stripe.setPublishableKey('<%= current_user.stripe_publishable_key %>');
});
</script>
While for a Stripe Checkout form you'd use something like this:
<script src="https://checkout.stripe.com/v2/checkout.js"
class="stripe-button"
data-key="<%= current_user.stripe_publishable_key %>"
data-description="<%= @product.name %>"
data-amount="<%= @product.price %>"></script>
Stripe will send webhook events to your application for any connected users. There's one gotcha here, in that Stripe will by default send both live and test mode events to your application's live end point. If you don't filter those events out you'll see a lot of errors. Here's an extension of our StripeEvent retriever from the Webhooks chapter that filters out test-mode events:
StripeEvent.event_retriever = lambda do |params|
return nil if Rails.env.production? && !params[:livemode]
return nil if StripeWebhook.exists?(stripe_id: params[:id])
StripeWebhook.create!(stripe_id: params[:id])
Stripe::Event.retrieve(params[:id])
end
This would also be an excellent place to filter out any events for users that don't have active subscriptions. This filter would go after you retrieve the event from Stripe so you can look up the account ID in your database.
Stripe Transfers are another, more flexible way to implement marketplaces with
Stripe. Instead of connecting to a user's Stripe account and making charges
through it, you get authorization to make deposits directly into their checking
account. Charges run through your Stripe account and you decide when to pay out
to the user. This is useful if your marketplace participants don't care that you're
using Stripe, or if signing them up for an account is more burdensome than you
want to deal with. For example, one of the initial customers for Stripe Transfers
was Lyft, a do-it-yourself taxi service. Drivers give Lyft their checking account
info who then create Stripe::Recipients
. Passengers pay with their credit cards
through Lyft's mobile app, which uses Stripe behind the scenes to actually run
payments. Drivers never have to deal with Stripe directly, instead Lyft just pays out
to their accounts periodically.
One thing to keep in mind is that once you create a Stripe::Recipient
and turn on
production transfers in Stripe's management interface your account will no longer
receive automatic payouts. Instead, Stripe will hold funds in your account until you
tell them where to send them.
Theoretically you could collect marketplace participants' checking account information via a normal Rails action because PCI-DSS does not consider them sensitive information. However, stripe.js provides the capability to tokenize the numbers the same way it tokenizes credit card numbers and you really should take advantage of it. That way sensitive information never touches your server:
<%= form_tag update_checking_account_path(id: @user.id), :class => 'form-horizontal', :id => 'account-form' do %>
<div class="control-group">
<label class="control-label" for="fullName">Full Name</label>
<div class="controls">
<input type="text" name="fullName" id="fullName" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="number">Routing Number</label>
<div class="controls">
<input type="text" size="9" class="routingNumber" id="number" placeholder="*********"/>
</div>
</div>
<div class="control-group">
<label class="control-label">Account Number</label>
<div class="controls">
<input type="text" class="accountNumber" />
</div>
</div>
<div class="form-row">
<div class="controls">
<button type="submit" class="btn btn-primary">Pay</button>
</div>
</div>
<% end %>
$('#account-form').submit(function() {
Stripe.bankAccount.createToken({
country: 'US',
routingNumber: $('.routingNumber').val(),
accountNumber: $('.accountNumber').val()
}, stripeResponseHandler);
return false;
});
function stripeResponseHandler(status, response) {
var form = $('#account-form');
form.append("<input type='hidden' name='stripeToken' value='" + response.id + "'/>");
form.get(0).submit();
}
This is a simplified form of the normal Stripe card tokenizing form and works
basically the same way. You call Stripe.bankAccount
.createToken with the routing
number, account number, and country of the account which we're hard coding to
'US'. createToken
takes a callback which then appends a hidden input to the form
and submits it using the DOM method instead of the jQuery method so we avoid
creating loops.
On the server side, just create a Stripe::Recipient
and save the ID to the user's
record:
recipient = Stripe::Recipient.create(
name: params[:fullName],
type: 'individual',
bank_account: params[:stripeToken]
)
current_user.update_attributes(:stripe_recipient_id => recipient.id)
Now, just create charges as normal while keeping track of which recipient the
charges are intended for. The easiest way to do this is to attach the recipient_id
or
user_id
to a Sale
record.
When you're ready to pay out to a recipient, either on a schedule or when the user
requests it, all you have to do is create a Stripe::Transfer
:
transfer = Stripe::Transfer.create(
amount: 10000,
currency: 'usd',
recipient: user.stripe_recipient_id,
description: 'Transfer'
)
This will initiate a transfer of $100 into the user's registered account. If you
instead use self
for the recipient
option it will transfer the requested amount
into the account you've attached to the Stripe account. If you've configured a
callback URL Stripe will send you an event when the transfer completes named
transfer.paid
. You can use this event to send the user a receipt or a notification.
You'll also get an event transfer.failed
if there was an error anywhere along the
line.
Each transfer costs a fixed $0.25 which is removed from your Stripe account at the time you create the transfer. If the transfer fails Stripe charges you $1, which will again be removed from your account.