Integrating Stripe into OFN

Tags: #<Tag:0x00007f0484b24078>

@danielle, @lin_d_hop, @serenity, @Kirsten, @MyriamBoure, @stveep

Preamble

Allowing enterprises to accept payments in an ‘automated’ way (ie. without customer interaction every time) is a fundamental component of the Standing Orders feature. It would also be a very useful addition to the existing checkout flow, allowing customers to checkout quickly without having to enter credentials each time. There are several candidates for allowing such automated payments to be accepted: Credit Card Gateways: (eg. Pin, Stripe, PayPal), e-wallet systems (Mangopay, PayPal) or a more sophisticated customer account credit system managed within the OFN.

The current thinking is Aus is that the although an internal customer account credit system would certainly be a suitable solution for many enterprises, and is a feature that should be built at some stage, such a system is ideally implemented in conjunction with a separate system for automated top-up (credit card / e-wallet), so that enterprises do not need to manage account credit top-up manually.

Mangopay appears to be a popular option in France particularly, but due to it’s current level of availability outside of Europe (ie. none), it does not represent suitable option at present. Pin appears to only work in Australia (?) and so falls into the same basket.

While PayPal has a far more global reach than Stripe, Stripe is available in all countries with an active publicly available instance of OFN, as well as most countries where instances are under active development (with the exception of India, any others?). PayPal is markedly more expensive in most places, and I get the impression it is a fair way down many people’s list of favourite companies…

That leaves us to consider Stripe the prime candidate for an initial cut at this, at present (speak up now if you have a better option). Thankfully, Stripe also has an exceptionally well documented API and an existing and well maintained ActiveMerchant gateway (which is already available for use in the OFN via Spree).

Of course the ideal solution would be to implement other payment gateways (such as MangoPay and Paypal) as well as an account credit feature, and the hope is that we will do this in the future, we’re just looking for a good candidate for an initial cut.

How Stripe Works

Stripe does lots of cool things, but in terms of the specific use case at hand, the most interesting feature (not unique to Stripe) is the ability to store a customer’s credit card and charge it at a later date, without having to store any of the card details on our servers (or even send card details through our server: by using stripe.js). This drastically reduces the amount of work required to comply with the industry body’s standards (PCI compliance). The way this works is that the customer enters their details once and we then send a request to Stripe to store those details on their system. Stripe sends back a single-use token that we can use to either charge that specific customer for a single order, or to ask Stripe to store the associated card details permanently for later use against any number of subsequent orders.

Based on my reading so far, there are two main ways that we can deal with authorisation for doing this on behalf of OFN users.

  1. Use Stripe Connect, to allow OFN users to authorise the instance to process payments on their behalf.
  2. Get users to enter their publishable and secret Stripe API keys directly into the OFN (via a payment method), where they will need to be stored in the database, and used to charge customers directly.

Option 1 (Stripe Connect)

This looks like the best option for a platform like the OFN, as it is designed for platforms that wish to process payments on behalf of their users (ie. enterprises). To use this setup, each instance of OFN would need to have a Stripe account, and all of the users of that instance who wish to use Stripe would authorise the instance to process payments on their behalf. NOTE: this does not mean that money travels through the instance’s Stripe account, only that the instance is authorised to charge customers on behalf of it’s users.

The main advantage of the Stripe Connect option is that is massively simplifies the management of security around connected user’s Stripe accounts. Once users have connected their Stripe accounts (a process which Stripe can handle for us) all subsequent requests are authenticated using a combination of their account id (public) and the instance’s secret API key. This means that the only sensitive information we need to handle is the instance’s API key, which can be stored in an ENV variable on the sever, thus rendering any information stored in the database worthless without it.

In theory this means that only way that an attacker can do bad things with payments is if they have shell access to the instance’s server, in which case the instance is in a lot of trouble anyway. NOTE: even in this absolute worst case scenario, OFN customers could not be charged directly by a malicious attacker, the worst that could happen is that an existing customer of an existing enterprise could be charged into that enterprise’s stripe account.

Option 2 (storing API keys)

This would also work fine, as it is the way that Stripe is designed to work for many users (ie. on single-merchant websites). However, when we are thinking about storing a large number of keys in a single database, this becomes a bit risky, or at the very least the stakes are raised. In particular: in the absence of a strategy to encrypt the user’s API keys in the database, this approach would effectively enable anyone who got a copy of an instance’s database to charge an enterprise’s customers with stored details on behalf of that enterprise. ie. not very desirable. This is of course something that we want to avoid at all costs anyway, but we would probably have to consider additional controls around this data if we were to take this route… NOTE: in the same way as Option 1, even in the worst case scenario, OFN customers could not be charged directly by a malicious attacker, the worst that could happen is that an existing customer of an existing enterprise could be charged into that enterprise’s stripe account.

Progress

As mentioned, Spree ships with ActiveMerchant, which provides an out-of-the-box Stripe gateway. I have spent about a day or so looking through the Stripe documentation and playing around with the codebase in an attempt to get the basic ActiveMerchant Gateway working, which I was able to do relatively easily. The work I have done so far is available here.

The main issue that I have come across is that Spree’s implementation of the Stripe gateway is designed around single-merchant shops, and so is set up to use the regular single-user Stripe (Option 2), rather than Stripe Connect (Option 1). This means that out of the box, it requires users to enter their API keys into the configuration for the relevant payment method, where they are stored. As mentioned above, this is likely not an ideal situation because anyone who gets access to the database (or a copy of it) has the ability to charge any stored customers to the relevant enterprise’s Stripe account for any amount the card can handle.

Next steps (round 1)

Round 1 would be getting Stripe Connect set up, and allowing enterpises to offer Stripe-processed credit cards as a payment method.

1. Develop strategy for storing instance API key, and turning on feature

Probably pretty simple, just an ENV variable for the instance’s API key and a feature toggle for Stripe payments?

2. Determine which model to store connected stripe account ids against

Do we store against an enterprise, or a payment method, or against the user? If we want to use Stripe’s connect process, we need a URI that we can redirect to, which may not work so well with either interface…still working on this one.

3. Build UI for connecting accounts

Depends a lot on what we decide for (2). Stripe’s recommended connect process involves redirecting the user to Stripe, where they can confirm that they would like to connect their account, before returning them to a URI we specify…

4. Rebuild the UI for configuration of Stripe payment methods

Again, depends a lot of what we decide for (2), but basically we need to scrap the vanilla UI which ask for a a publishable and a secret API key, and replace it with one that allows us to select an id for a stripe account that has already been connected to the instance’s Stripe account. Should also notify the user if no connected Stripe accounts are available to choose from…

5. Tweak the out-of-the-box setup of the Stripe gateway to work with Stripe Connect

From what I have read, this may not be a serious issue, as the Stripe API appears to be designed to work in much the same way for either option, so hopefully we will just need to tweak the auth headers we send to Stripe in various places. As good as the Stripe docs are, I don’t have a definitive answer to this yet. Difficult to be sure until we actually try.

Next steps (round 2)

Round 2 of work would consist of the work required to allow customers to store their card details against an enterprise, and for the OFN to auto-charge this card where appropriate…

1. Add a model for holding the customers stored credentials (eg. card details) for payment methods they authorise

This could be a generic model, which could potentially be reused for PayPal/MangoPay, etc.

2. Build a UI to allow customers to store their credentials (eg. card details)

Again, if we can design this to be as generic as possible that would be ideal.

The idea would be to redesign the front-end ‘Account’ page, so that in addition to seeing their balance with each shop they engage with, they can also store their things like credit card details for later use, and maybe (later) set preferences about how they interact with each of those shops…

3. Tweak checkout UI to allow use of stored payment credentials where they exist.

Same interface should allow customers to store any card details they enter at the checkout for later use.

1 Like

Actually, the immediate next step should probably be a final proof-of-concept, to confirm that tweaking the existing Stripe gateway infrastructure to use Stripe Connect credentials will work, and to get a sense of how big this is.

Thanks for taking this on Rob. It will be very well-received in the UK. We really don’t like Paypal! I have heard good things about Stripe and several of the potential new OFN users we are talking to are already either using Stripe or interested in using it, I don’t have any better options to suggest.

Option 1 (Stripe Connect) sounds preferable to me but I am a bit confused about the Spree consequences of this and I must admit that I don’t understand enough to follow your ‘next steps’ but I do agree that it will be great to keep the development as generic as possible so that we can include things like Mangopay in the future.

Thanks again for all your work - UK shoppers are very very pleased with the performance improvements!

Rob, I think if option 2 makes things easier for Spree integration than the risk is acceptable. I don’t know if the work needed for PCI compliance negates the time saving but just saying. If the worst case is an unauthorised charge to the instance’s Stripe account, I could live with that.

Thanks for the detailed info @oeoeaio this was really informative.
@sigmundpetersen any thoughts on this?

Thanks for getting cracking on this @oeoeaio

Stripe Connect seems like the obvious option. I feel that there would be huge UX advantages for having a Stripe account per instance, benefiting both users and enterprises and thus the enterprise.

Along these same lines I feel that for Next Steps Round One Point 2 it would make sense to store the stripe acc ids per user. This would then allow a user to input their details once and easily shop from any enterprise on OFN. In theory this could also be done per payment method, but I feel that enterprises would need extra support to ensure they set up their payment methods as we intend to benefit from this. Storing ids per user would guarantee that users and enterprises benefit from super simple payments, making it easier for customers to shop between OFN enterprises.

in Next Steps Round One Point 3 my feeling is that it would be ideal to integrate the sign up process into OFN as much as possible. I know even personally that when a website redirects me I am less likely to complete the order, so I imagine for less tech savvy people the drop out rate increases significantly. UK can have budget to contribute to the extra dev it would entail… but obviously if complex this could be a phase 2 feature.

Another thought. It would be great if in designing these changes we think longer term to @MyriamBoure’s requirements to enable multi-party payments for marketplaces. I guess this won’t affect the current implementation a great deal as much of this is more the customer experience. But ensuring a modular level that can be extended within the payment process might make this future enhancement easier to slot in. I’m sure you’ll be best-practising the f$%k out of it anyway :slight_smile:

Awesome. Thanks for that feedback everybody! @lin_d_hop @NickWeir @CynthiaReynolds @Oliver

Just looking at the docs a little further, and found another argument in favour of Stripe Connect. It will allow us to store customer card details centrally (against the instance’s stripe account) and then use those to charge customers to any connected enterprise. This way we only have to ask customers to enter their card details once (ever), and we can make that card available as a payment option anywhere on the instance. Obviously this need to be explained but I think it is a fairly palatable proposition. More info in the Sharing Customers section of the Stripe docs.

The alternative where we store individual API keys would mean that we need to ask customers to enter their details at least once per shop, as there is no mechanism for sharing details across (from Stripe’s perspective) two completely disconnected accounts.

@oliver Thanks for those thoughts. I certainly don’t feel confident enough to say that the API keys option (option 2) is the easier route. Yes we have a solution that ‘works’ out of the box, but IMHO simply running with this as-is creates an unacceptable level of risk unless we do some serious work on encryption/decryption of keys (not very pretty or fun), or on enforcing some serious protocols for downloading and managing access to copies of the database (which means ongoing work). I’ve been looking at the code and I am hopeful that tweaking Spree’s Stripe gateway to work with Stripe Connect may not be huge amount of work - the API, and the existing Spree gateway appear to be well written and quite flexible. Fingers crossed, will report back when I know more.

@lin_d_hop: I think perhaps there is a little confusion about what is being proposed: at this stage I am not suggesting that customers be asked to connect their stripe accounts to the OFN, merely that they enter their card details which we can ask Stripe to store for later use. We can certainly look into supporting this as a next step, but I don’t think it should be part of the initial round of work? The flow for customers entering their card details will be entirely ‘in-house’, so no need to redirect customers to Stripe. In fact Stripe need not be mentioned to the customer at all.

The component that will require users to be directed through Stripe is the ‘Connect’ aspect for enterprise users wishing to use Stripe as a payment method ie. enterprise users connecting their stripe accounts to the instance, which authorises the instance to charge customers on their behalf. The flow for connection is relatively straight-forward: enterprises users would click a ‘Connect to Stripe’ button somewhere in the admin section, which would send them to Stripe to confirm authorisation, and then they would be sent back to the OFN. There is an example of what this might look like on this page (about halfway down). Once enterprises have ‘connected’ to the instance, they can create payment methods based on that connection, which then behaves like any other payment method (except that we can do cool stuff like auto-load info for any customers with stored card details, or charge customers with standing orders without them having to touch the site).

I am still completely undecided as to whether we should ‘Connect’ stripe accounts to users or to enterprises. What is clear to me is that if we connect them to users, we will need to allow each user to link multiple Stripe accounts to their OFN user, because a single user can foreseeably own two enterprises that need to use two separate Stripe accounts to charge customers. It is important to note that Stripe accounts connected in this way would not be at all equivalent to those connected via the front-end (if we decide to implement that at some point): the former represents an authorisation for the instance to charge on behalf of the Stripe account holder, the second represents an authorisation for the instance to charge the Stripe account holder on behalf of another. Stripe treats these as two very different classes of relationship: the first is a ‘Connected Account’ and the second is a ‘Customer’.

1 Like

Latest: @stveep and I are going to attempt to get a proof of concept using Stripe Connect working. Then we’ll move on to data model, UI and UX design.

1 Like

Thank you @oeoeaio for this extensive investigation :slight_smile:
I dont feel very competent on the tech dimension of what you said, but there is something I don’t understand on the “features” and UX side, I would need some clarification :wink:

What I understand is that proposing Stripe as a payment method would enable standing orders as a hub will be able to charge a customer the the basket at every OC, when the standing order repeats.
But for me the most important feature I had in mind with implementing a multi-vendor payment gateway, and that I think Stripe Connects does (but not the regular Stripe API keys) is saving a lot of work for the hub, who won’t have to collect the money from the customers and then pay the suppliers, the money flowing directly as “mapped” in the system (see my post on it here: How to offer Mangopay as a payment method to hubs? [Split Payments])
Is this feature included in Stripe Connect? I don’t read anything about it here so I’m not sure, sorry if I don’t understand everything :wink:
And that means also reworking some of the existing processes (like adding a step where the hub manager has to validate that the order has been delivered so that the money flows from the e-wallet of the customer to the e-wallets of the suppliers, hubs, and instance. I listed them in the other discussion, they apply to Mangopay or any multi-vendor payment gateway system).

If Stripe does that job in the way you plan to implement it, then it sounds like a more international option than Mangopay and would probably be a better value-for-work/money option for the whole community as it would benefit much more countries at once! We would probably need to propose Mangopay in France as well, but once the work has been done with Stripe, I guess it would be pretty similar with Mangopay later on. And we can start proposing Stripe to the hubs who can’t “collect and pay the suppliers” for legal reasons, so that would answer our need on a short term.

Hi @MyriamBoure!

I suspect that the best solution for your particular requirement is still going to be to an implementation of Mangopay. I understand your use case and it is certainly in my mind, but unfortunately I don’t think that Stripe Connect (or any other platform) is going to be our silver bullet for all payment gateway features we would like. Stripe Connect gives us a lot of the high priority features in a relatively universal gateway, but it is really designed to model a set of relationships between a single platform (eg. the OFN) and its users (eg. OFN shops). There is no provision for the users to then have connected accounts themselves which they can distribute funds to, so I don’t think we will be able to use it for multi-vendor payment gateway support.

###Scenario: using multiple Stripe Connect accounts on one platform

I suppose in theory, it could be possible to decentralise the model by creating a Stripe Connect account for each shop, but this is problematic for a bunch of reasons:

  • Stripe requires that each Connect user registers their “platform” before it is approved. My understanding is that registration of a platform is non-trivial. So we would have to somehow do this for each shop who wanted to use Stripe.
  • I am not even sure that Stripe themselves would allow us to register multiple Stripe Connect accounts on one platform - it is certainly not something they mention…
  • We would need customers to re-enter their details for each individual Shop they wanted to purchase through on the OFN, because the individual Stripe Connect accounts for each shop are not linked in any way.
  • All of the API key security headaches would resurface, because we would have to store a separate API key for each shop

There may be other issues too, but IMHO that is enough things to make this particular idea unworkable. We lose a lot of the benefits of the standard implementation (easy management of security for the platform, ease of use for customers and shops), for a system which looks fairly unpleasant to administrate to me…

Multi-vendor gateway support: ie. Mangopay

I understand the multi-vendor gateway use case and think it supporting it is a great idea, saving time, and allowing support of business models that require direct distributions of funds to producers for legal/tax reasons in your jurisdiction. Unfortunately however, I think the highest priority for now for the global community has to be a (relatively) universal gateway with a low barrier-to-entry, capable of handling recurring payments. I think Stripe Connect fits that bill.

As I said, from the little I know about it, I think Mangopay is absolutely the go-to for this feature, but think its implementation is going to need to be its own feature. It is a significant body of work, and sadly we simply don’t have the resources to implement both Stripe Connect and Mangopay at once.

As always, I’m very happy to work with you to map out exactly what features are required and what implementation looks like, so we can get an idea of next steps and budget, etc… :slight_smile:

1 Like

Ok, I understand @oeoeaio, thanks for the explanation, it was not clear to me… Stripe is anyway an must-have option to propose the hubs, so it’s needed anyway, and let’s continue que discussion on Mangopay here.

I’ve been making a start on this - have jotted down some thoughts on how to go about it here and on the issue https://github.com/openfoodfoundation/openfoodnetwork/issues/1143

(See issue for more techy stuff)

Here are a few questions that came up for me when thinking it through:

  • When a Customer makes a payment using the Stripe gateway:
    • Create a Stripe Customer on the instance Stripe account (or should this be hub Stripe acct?) if it doesn’t already exist (https://stripe.com/docs/connect/shared-customers)
    • This can then be used to make future charges to that Customer account.
    • Should we store this locally or retrieve as needed via Stripe API?
    • Should we have a way of creating a Customer independently of placing an order?
  • What happens when…
    • A second co-ordinator of an enterprise tries to connect a new Stripe account?

To me, it makes sense to have the accounts associated with Enterprises in the OFN system, since we are not going to use them to authenticate users (although we could). It also allows multiple users to administer stripe connect for an enterprise. So I’ll try to build it like this as a start and we’ll see how it looks. Most of what we need to do is in these guides from the stripe docs:

https://stripe/docs/recipes/store-builder

In order to connect enterprise stripe accounts to the instance we need to have the instance one set up and activated (which involves some verification of business details). So maybe @NickWeir or @lin_d_hop could set up & activate one for the UK if we don’t have one already? Then I can use this to test stuff - I’ll need API keys and ideally login details if possible.

Hi Steve - it was really good to see you and Claire yesterday - thanks for calling in and I hope your Stroudco order worked out well.

Here are my thoughts (in bold) on your questions:
When a Customer makes a payment using the Stripe gateway:
Create a Stripe Customer on the instance Stripe account (or should this be hub Stripe acct?) if it doesn’t already exist (https://stripe.com/docs/connect/shared-customers)
I think we might need individual hub Stripe accounts so that there is less chance of confusion with transactions, balances, etc

This can then be used to make future charges to that Customer account.
Should we store this locally or retrieve as needed via Stripe API?
I don’t know - let’s see if you get a more intelligent answer - if not we could talk through at the next hangout

Should we have a way of creating a Customer independently of placing an order?
I can’t imagine why we would need to do this

What happens when…
A second co-ordinator of an enterprise tries to connect a new Stripe account?
if this is going to be a problem could we make it so that only the enterprise owner (rather than any manager) can connect a new account?

@lin_d_hop have you already set up a stripe account? if not shall i set one up for OFN UK? Or shall I ask @Oliver to set one up for Stroudco so that we can test individual hubs having separate Stripe accounts?

I am pinging this to @Sara and @mags in case they want to chip in.

@NickWeir We currently don’t have an OFN UK stripe account so would you go ahead and set one up?

OK we now have an OFN UK stripe account here https://dashboard.stripe.com/test/dashboard
the login email is hello@openfoodnetwork.org.uk I will send @stveep the password and api keys separately.

Do we want to ask @oliver to set up a separate stripe account for stroudco?

Hi @stveep and @NickWeir,

Good questions Steve. I’ve had a bit of a think about this stuff and touched on some of it above, but here are my thoughts about your questions:

Create a Stripe Customer on the instance Stripe account (or should this be hub Stripe acct?) if it doesn’t already exist (https://stripe.com/docs/connect/shared-customers)
IMO Stripe Customers should be stored against the instance’s Stripe account. If we opt for storing Stripe Customer details per-shop, they will need to be re-entered for each shop, whereas if we store against the instance, we can reuse card details for any shop on the instance. Bear in mind that a Stripe customer is more closely related to an OFN User than an OFN Customer. Either way, this is a very low level question, which only developers will ever need to understand, this level of detail will never be exposed to customers or shop owners.

Should we store this locally or retrieve as needed via Stripe API?
As far as I understand, we only need to store a customer ID (given to us by Stripe when we register the customer) in order to create a charge against that customer using our instance API key. We will need to store that locally regardless, but there should be no reason to store anything else, or to retrieve any additional information from Stripe in order to create a charge. Does that sound right?

Should we have a way of creating a Customer independently of placing an order?
Yes, but this is possibly a next step - we don’t need to tackle it as part of the proof of concept. Given that we will need an interface for users to edit their stored card details anyway, it makes sense to allow them to add new cards in the same place (ie. Stripe call these ‘sources’, but we would need to create a new Stripe ‘customer’ for new OFN users with a linked Stripe customer ID, so that we can store the ‘source’ against it). Then we can support the use case where a user would like to set up their card details as soon as they create an account, so they ready to shop wherever we would like.

So this means (perhaps slightly confusingly for developers) we need to store the Stripe Customer ID against an OFN User. I think we can just retrieve all of the information about stored cards from Stripe using this Customer ID whenever we need it (eg. checkout).

What happens when a second co-ordinator of an enterprise tries to connect a new Stripe account?
Yeah, this is the part that I still haven’t gotten my head around. I think I am heading more towards the approach you mentioned of storing against an enterprise. I think having one Stripe account per enterprise is not too much of a constraint. What do you think? This way, a second coordinator would only be able to alter the existing Stripe account for a given enterprise, they would not be able to add a new one.

Hi All,

Stroudco already has a Stripe account because we have recently switched to Xero accounting software, where online payments are processed via Stripe.

@oeoeaio I’m not sure it will happen all that often that a customer shops at different food hubs so storing against the instance may only be more efficient in theory.

@Oliver that may be true but as far as I can see there are no advantages to storing the customer against the shop’s Stripe account. The disadvantages will be that the interface for adding/updating card details will need to be scoped to a particular shop (regardless of whether multiple shops are engaged with) which is confusing, and in the event that a customer does want to use a card at multiple shops, they will need to enter the exact same details for each shop, and any changes to card details will need to be replicated across the shops, I think this is fairly non-intuitive…

I agree @oeoeaio that storing customers against the instance is a better move. It has big UX advantages for OFN as a whole, making it super easy to try shopping with different enterprises. This should be a selling point of the platform as it develops.

Perfectly ok with me.