Skip to content

Instantly share code, notes, and snippets.

@matthewarkin
Last active January 25, 2022 21:10
Show Gist options
  • Save matthewarkin/1c9a89b0ea2523aadff4 to your computer and use it in GitHub Desktop.
Save matthewarkin/1c9a89b0ea2523aadff4 to your computer and use it in GitHub Desktop.
Plaid and Managed Accounts

Ensuring Valid External Accounts with Stripe and Plaid

When adding a bank account to a customer in Stripe, Stripe forces you to verify the bank account (either through microdeposits or through Plaid. This is useful because you don't want to withdraw money from a bank account that a user does not own. However when it comes to sending money to bank accounts, Stripe does not require you to verify the account. After all why would you send money to an account that you're not in control of. But lets face it, users make mistakes and its not uncommon for them to type their routing or account number which can significantly delay their transfers.

One way to prevent this is to get their routing and account number directly from their bank and avoid the possibility of user error. This is where Plaid comes in. Plaid provides instant ACH verification by having the user log into their bank account (similiar to what you may done if you've ever used Mint). The user logs into their bank account using Plaid, and you're able to get their routing and bank account number via Plaid's API with no risk of error.

So first create a Plaid account (I'd recommend using this link so that you'll be able to also use your Plaid account to verify bank accounts for incoming ACH -- aka charging customers).

Once you have that set up you can start using Plaid in test mode.

To use Plaid for Production, you'll need an activated Plaid account, this will require an email to Plaid support as well as an appointment with their Compliance team.

There are two ways to use Plaid. The more complicated way is to use the Plaid Connect API directly and pass sensitive information like the username and password for a user's bank account through your server to communicate with Plaid. As that poses quite a high security risk, I'd recommend using Plaid Link (you can think of it as Stripe Checkout but for Plaid). Its a simple iFrame you add to your site where the user can log into their bank account, and in return you get a token that your server can use.

Obtain a Plaid Public Token

<!-- A hidden input named public_token will be appended to this form
once the user has completed the Link flow. Link will then submit the
form, sending the public_token to your server. -->
<form id="plaidForm" method="POST" action="/authenticate">
<input type="hidden" name="plaidToken" id="plaidToken" val=""/>
<input type="hidden" name="plaidAcctId" id="plaidAcctId" val=""/>
<button type="button" id="linkButton">Link your bank account</button>
</form>

<script src="https://cdn.plaid.com/link/stable/link-initialize.js"></script>
<script>
var linkHandler = Plaid.create({
  selectAccount: true,
  env: 'tartan',
  clientName: 'Client Name',
  key: '[YOUR PUBLIC_KEY]',
  product: 'auth',
  onSuccess: function(public_token, metadata) {
    // The onSuccess function is called when the user has successfully
    // authenticated and selected an account to use.
    //
    // When called, you will send the public_token and the selected
    // account ID, metadata.account_id, to your backend app server.
    //
    // sendDataToBackendServer({
    //   public_token: public_token,
    //   account_id: metadata.account_id
    // });
    document.getElementById('plaidToken').value = public_token;
    document.getElementById('plaidAcctId').value = metadata.account_id;
    document.getElementById('plaidForm').submit();
  },
});
er the standard Institution Select view
document.getElementById('linkButton').onclick = function() {
  linkHandler.open();
};
</script>

The above code create a form with a button. When the button is clicked, the Plaid Link form opens allowing the customer to choose their bank and log in. Once they log in, they'll be prompted to choose which bank account (for instance their checking or savings account).

Once the user has selected the account they want to use, Plaid will call the onSuccess function which adds the two values we need, the public_token and metadata.account_id to a form, and then submits the form to an endpoint on our server.

Now that our server has a public_token and an account id, we have two options, we can convert that to an access_token which we can use to get the routing and account number directly from Plaid.

Convert Public Token to an Access Token Plaid has an exchange_token api endpoint that will take a public token and account id and convert that to a Plaid Access Token

$ curl -X POST https://tartan.plaid.com/exchange_token \
>   -d client_id="$plaid_client_id" \
>   -d secret="$plaid_secret" \
>   -d public_token="$public_token_from_plaid_link_module" \
>   -d account_id="$account_id_from_plaid_link_module"

This will return an object that looks like:

{
  "access_token": "foobar_plaid_access_token"
}

Get Routing Number and Account Number from an Access Token Once you have the access_token you'll call the auth retrieve_data Plaid API:

$ curl -X POST https://tartan.plaid.com/auth/get \
>  -d client_id="{CLIENT_ID}" \
>  -d secret="{SECRET}" \
>  -d access_token="{ACCESS_TOKEN}"

This will return a list of bank accounts for the user:

"accounts": [
   {
     "_id": "QPO8Jo8vdDHMepg41PBwckXm4KdK1yUdmXOwK",
     "_item": "KdDjmojBERUKx3JkDd9RuxA5EvejA4SENO4AA",
     "_user": "eJXpMzpR65FP4RYno6rzuA7OZjd9n3Hna0RYa",
     "balance": {
       "available": 1203.42,
       "current": 1274.93
     },
     "institution_type": "fake_institution",
     "meta": {
       "name": "Plaid Savings",
       "number": "9606"
     },
     "numbers": {
       "routing": "021000021",
       "account": "9900009606",
       "wireRouting": "021000021"
     },
     "subtype": "savings",
     "type": "depository"
   }
 ],
 "access_token": "test_wells"
}

Then you can grab the routing and account number from the response and use it in Stripe.

stripe.accounts.createExternalAccount("acct_15sp5KA73Hg1F43I",{
  external_account: {
	  object: "bank_account",
	  account_number: account, // from Plaid
	  routing_number: routing,
	  country: "US",
	  currency: "USD"
  },
  function(err, bank_account) {
    // asynchronously called
  }
);

Presto! You've just used Plaid to obtain correct bank account information for your managed accounts!

If you have any questions just send me a quick email: markin+stripe@kollective.it

@MichaelBrew
Copy link

@Willamin I was running into the same issue, but fixed it by connecting my Stripe account within the Plaid dashboard. Reference: https://plaid.com/docs/link/stripe/#step-1-set-up-your-plaid-and-stripe-accounts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment