Using Stripe Connect To Directly Pay Retailers

We at Instacart really like it when customers can give us money in exchange for their favorite groceries and because mailing us envelopes of cash would make it hard to support deliveries in as fast as 1 hour we make heavy use of Stripe. One of the coolest Stripe features we utilize is Connect. Stripe Connect allows us to function like a marketplace in certain situations, accepting payments from customers and then directly transferring some or all of that payment to a bank account owned by one of the retailers we partner with.

Because our payment model can be complex, particularly for marketplace type transactions, we’ve really pushed the limits of Stripe Connect and learned a lot about what it is capable of. In this post, I’ll walk you through how we make the most of this powerful tool.

Setting Up A Connected Account

First, in order to use Stripe Connect you will need to register your platform and then connect the retailer Stripe accounts to that platform. I can’t give a better explanation for that than Stripe does in their own documentation, so just take a look at that.

One important aspect of this step is storing the Stripe Connected Account id for the retailer when they connect to your platform. We do this by building a custom endpoint on our own server that takes in a retailer id and the authorization code that Stripe will generate. Then we set the Redirect URI in the Stripe Platform Settings in our Stripe Dashboard to a page so it becomes a part of the flow when retailers connect with us.

Here is what such an endpoint might look like, built in your stripe_connect_controller.rb:

STRIPE_OAUTH_OPTIONS = {
  site: 'https://connect.stripe.com',
  authorize_url: '/oauth/authorize',
  token_url: '/oauth/token'
}
...
def redirect
  if set_stripe_connect_account_id(params[:retailer_id], params[:code])
    flash[:info] = "Stripe account successfully connected!"
  else
    flash[:warning] = "Error connecting Stripe Connect account."
  end
  redirect_to :someplace_useful_for_the_retailer
end

def set_stripe_connect_account_id(retailer_id, code)
  response = oauth_call(params[:code])
  retailer = Retailer.find(retailer_id)

  retailer.stripe_connect_account_id = response.params["stripe_user_id"]
  retailer.save
  true
end

def oauth_call(code)
  client = OAuth2::Client.new(STRIPE_CONNECT_APPLICATION_ID, STRIPE_SECRET_KEY, STRIPE_OAUTH_OPTIONS)
  client.auth_code.get_token(code)
end

So now when a retailer sets up a connected account with us, we save their stripe_connect_account_id in our database. This id will be used as a destination parameter to tell Stripe where to send the retailer’s portion of the charge.

Ok! We have our retailer set up and some money burning a hole in our pocket, let’s buy things!

Auth and Capture

Now when we create a Stripe auth charge we need to specify if we will be transferring some money to a connected account. To do this we’ll be setting the destination field on the Stripe charge when we create the auth. This field can’t be added or changed once the charge is created so you need to always know if you’ll be using Stripe Connect at the time of creation.

Here’s an example:

def create_order_auth_charge(order)
  charge_params = {
    amount: order.amount_cents, # 10000 ($100)
    customer: order.customer.stripe_customer_id, #  cus_1234
    capture: false
  }
  if order.needs_direct_payment?
    charge_params[:destination] = order.retailer.stripe_connect_account_id # acct_abc123
  end

  Stripe::Charge.create(charge_params) # return a Stripe::Charge object, id: ch_1337
end

This method will create an auth charge that will transfer 100% of the amount to the account specified by the destination field. The platform account (Instacart) pays all Stripe fees in this transaction, so if $100 is captured, $100 will go to the destination account.

Let’s say we only want to give $80 of the $100 we authed to the retailer. This is done by setting an application fee on the Stripe capture:

charge = Stripe::Charge.retreive("ch_1337")
charge.capture(application_fee: 20_00)

If we want to just create a charge that is captured right away, we can include the application fee when the charge is created:

def create_order_charge(order)
  charge_params = {
    amount: order.amount_cents, # 100_00 ($100)
    customer: order.customer.stripe_customer_id, #  cus_1234
    capture: true
  }
  if order.needs_direct_payment?
    charge_params[:destination] = order.retailer.stripe_connect_account_id # acct_abc123
    charge_params[:application_fee] = order.amount_cents - order.amount_to_retailer_cents # 100_00 - 80_00
  end

  Stripe::Charge.create(charge_params) # return a Stripe::Charge object, ch_3141
end

The mechanisms behind Stripe Connect are Stripe transfers and fees between accounts that happen when the charge is captured. Here is what exactly what is happening for all involved parties:

The customer pays $100 which passes through Instacart’s platform account and is given to the retailer. Then a $20 fee is taken back from the retailer and given to Instacart. The final balance is $20 for Instacart (minus Stripe processing fees) and $80 for the retailer.

Chart 1@2x

Refunds

Refunds with Stripe Connect are a bit more complicated than a normal refund because we’re now returning money from multiple bank accounts and we care which accounts we refund that money from.

Full Refund

Unfortunately, doing a full refund of a Stripe Connect charge is not as simple as it seems like it should be.

Let’s take our first charge where we captured $100 from the customer, sent $80 to the retailer, and kept $20 for ourselves:

charge = Stripe::Charge.retreive("ch_1337")

charge.refund # this seems like what we should do, but NO! DO NOT DO THIS!

The end result of this call is $100 is returned to the customer by taking a $100 out of the platform (Instacart’s) account, with the retailer keeping the $80 we sent them earlier. That means we’re out $80 and someone from accounting is mad at me.

What we need to do is first refund the fee we took from the retailer, then take back the whole transfer from the retailer, THEN return the full $100 to the customer.

charge.refund(reverse_transfer: true, refund_application_fee: true) # DO THIS FOR FULL REFUNDS!

Chart 2@2x

Refunds from retailer portion

Refunding a portion of the charge that was sent to the retailer is also a bit complicated. Let’s say we have the same charge as before ( $100 from the customer, $80 to the retailer, $20 kept by Instacart). From this charge we want to refund $10 from the portion of the charge that was already sent to the retailer.

To do this we’ll want to manually reverse part of the transfer that was created as a part of this charge. As mentioned earlier, the mechanism behind Stripe Connect is transfers. We have a record of those transfers associated with the charge, so we can find them and work with them:

charge.refund(amount: 10_00) # this refunds $10 to the customer from the platform account
transfer = Stripe::Transfer.retrieve(charge.transfer)
# this returns $10 to the platform account from the retailer account to cover the refund
transfer.reversals.create(amount: 10_00)

We can’t use the reverse_transfer parameter in the refund method here because if we do that Stripe will try to reverse a proportional amount of the transfer, relative to the amount refunded. In the above example using reverse_transfer: true on the refund (and not doing a manual transfer reversal) would refund $8 from the retailer and $2 from the host.

Chart 3@2x

Refunds From Our Portion

Now, we finally get to something that just works the way you’d expect it to. Refunding money that we want to come from our account works like a normal Stripe refund:

charge.refund(amount: 10_00) # this refunds $10 to the customer from the platform account

Chargebacks And Disputes

It is also worth noting that when a chargeback occurs, the amount is refunded from the platform account.

Stripe Connect is a very powerful tool and we get a lot of value from it. Hopefully this provides some deeper understanding of how it can be used in real-world situations.

Animating programmatically with Redux and React

Our recent move to React has been really beneficial, but we still have a massive Backbone app holding the ship together. While Backbone has served us well in the past, its mutable models and reliance on direct DOM manipulation for view rendering does not mesh well with React’s ideals. It’s time for that ship to sail.

Redux has gained a lot of attention and praise lately, and for good reason. It’s incredibly simple and fast. It also leads to more understandable and performant web applications. However, shifting a codebase of our size over to a new state/data manager is risky and time consuming. When we started using React, the transition went smoothly because we were able to continue using our older tools alongside React. We needed a way to do the same thing with Redux.

Read More

OhMyCron — Locking, Logging and Environment setup for cron

Crons are easy to write but run in a confusing way.

Typically the shell is not your shell, the path is not your path, and the environment is not your environment. Cron runs your tasks and they fail and nothing is logged; instead it tries to send email and fails because you didn’t specify an email; and if you had it would still fail because you didn’t set up a mailer.

It seems like a lot to worry about, just to run a command in the background.

OhMyCron makes cron convenient again by adding an intuitive and conventional approach to logging and environment loading:

  • STDOUT is logged at user.info in Syslog and STDERR is logged at user.notice.
  • Crons are locked (you can set the lock name) with BSD locks, to prevent them from piling up if they run longer than expected.
  • /usr/local/bin is added to the PATH.
  • ohmycron runs jobs with Bash and loads the user’s profile before running the job.

Today, we are delighted to open source OhMyCron.

Read More

Instacart Spotlight – East Coast Operations

The East Coast Operations team came together for a “sister city retreat”. The gathering brought together teams from Philadelphia, DC, Boston, and the hosting city, New York. Team bonding ensues along with fun brainstorming and shared knowledge sessions. Here the team brings together all the strategies they employ against all of the unique challenges they face in each city.

Read More

Instacart Spotlight: Data & Analytics

The Data & Analytics team at Instacart was formed one short year ago. Since then the team has been a part of balancing supply and demand in the marketplace, improving our consumer experience, identifying and removing frictions in our shopper experience, and building a data infrastructure to enable analysis. We met up with the team to learn more about what they do.

Instacart has so much data to work with and you joined when we were about 150 employees, so you’ve seen a lot of change and growth.

Read More

Reading for Researchers (and Designers)

Improving as a researcher comes from practice, guidance, and honest critical reflection. Good books also contribute: they’ll give you new tools, better concepts, rich case experience, and strategies for moving your work forward. They’ll help you build a language of practice and highlight new areas of the work worthy of your attention.

This is a reading list designed to help you gain those things. In the spirit of directed provocations, the books are grouped into themes representing areas of researcher- and designer-competencies you should look to build. It’s not inclusive or exhaustive, but I hope robust and useful for the realm of design and research in digital products and services.

Read More

Instacart Spotlight: Payroll Team

Instacart is becoming a household name around the nation. But the groceries can’t get delivered unless people are getting paid. We caught up with our payroll team to see the inner workings of how they always put the teams’ livelihoods as their #1 priority.

How does Payroll play an integral role in instacart?

Shelby: I’m the payroll manager and have been with the company since the end of April 2015. We’ve built a strong Payroll team that helps get our corporate employees and shoppers paid timely and accurately. One of the biggest accomplishments for us to date, outside of getting everyone paid, is implementing our new integrated WorkDay system. We rolled it out this January and it’s huge because Workday is the hottest technology for HR/Payroll.

Read More

Instacart hosts UCLA alumni and the new Dean of Engineering

Our focus at Instacart is to allow anyone to order groceries and have them delivered to their door in as little as an hour. We’re always hard at work on that goal, but sometimes it’s refreshing to step back, help out the tech community in general and meet new people. In that vein, we host a number of groups, from the more technical ones (Railsbridge, Ruby meetups), to diversity focused (Lesbians Who Tech, Girls In Tech, Women Who Code, Oakland Digital), to universities (domestic and international).

Recently Instacart held a UCLA alumni networking event at our office. We hosted Dean Jayathi Murthy, who officially became the Dean of the UCLA engineering school at the beginning of this year. She is the 7th person to hold the position, and is the 1st ever female engineering Dean. It was great to build a relationship with her, the engineering school and the UCLA community living in the Bay Area.

As a Bruin and engineer on the Instacart Catalog team, I hosted the event with Melissa Chu, a fellow Bruin and Instacart’s Recruiting Programs Manager. We put together a can’t-miss speaker line-up – first the Dean and then our CEO Apoorva. Among the topics that would be covered were UCLA’s vision for engineering, Instacart’s growth and evolution, and the need for a new generation of talented students.

Read More

Doing Data Science Right – At Instacart

Two weeks ago, I wrote a piece with Daniel Tunkelang (formerly of LinkedIn) summarizing the advice they give to founders who are interested in building data science teams.

Doing Data Science Right – Your Most Common Questions Answered explains why data science is so important for many startups, when companies should begin investing in it, where to put data science in their organization and how to build a culture where data science thrives.

If you are curious to learn more about how data science is set up at Instacart, read on! We will walk through each of the big questions in the article, and explain how data science at Instacart has handled the topic.

Read More