Stripe: your dream service for automating money transfers

Stripe is just that good and we want this article to explore the basic issues of integrating this service with an e-commerce project, providing theoretical and practical examples. I hope my experience helps anyone who feels like trying out Stripe for their project.

However, before you use Stripe, double-check the location of the business you are going to provide with the service. For instance, if it’s a Russian company, Stripe is of no use to you: even though you can accept payments from any country, the Stripe account owner’s business has to be legally registered in one of the available countries; otherwise, you can’t create and authorize an account. You may check the list of the supported countries here. If you want to withdraw funds to other accounts, for example, for your vendors (more on this below), the vendors have to be legally based in countries supported by Stripe. The customer I worked with had their business registered in the USA, which allowed us to carry out payments using Stripe.

You should also keep in mind that Stripe doesn’t support the 3-D Secure xml protocol which requires the customer to enter a confirmation code sent by SMS. Stripe simply attempts to carry out the payment disregarding this option, and it works only if the bank accepts payments without 3-D Secure; otherwise, the payment will be rejected and it will not be possible to pay using this card.

Carrying out a payment

To transfer the money from the customer’s card to our Stripe account we will have to do the following steps. Using the Stripe.js script we’ll get the Stripe token on the frontend, and then we’ll use the token on the server side to carry out the payment.

Add Stripe.js and set the public key:

<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
 Stripe.setPublishableKey('your_public_key');
</script>

Make a regular HTML form and specify data-stripe attributes in input for the script to work. We will require the customer’s card number, expiration year and month and CVC. Stripe does not require the owner’s first and last name.

<form action="" method="POST" id="payment-form">
    <span class="payment-errors"></span>
    <label>Card Number</label>
    <input type="text" size="20" data-stripe="number">
    <label>Expiration (MM/YY)</label>     
    <input type="text" size="2" data-stripe="exp_month">
    <input type="text" size="2" data-stripe="exp_year">
    <label>CVC</label>
    <input type="text" size="4" data-stripe="cvc">
    <input type="submit" class="submit" value="Submit">
</form>

Now let’s get the token:

$(function() {
 var $form = $('#payment-form');
 $form.submit(function(event) {
      // Turn off the button to prevent unwanted clicks
   $form.find('.submit').prop('disabled', true);

      // Request token from Stripe
   Stripe.card.createToken($form, stripeResponseHandler);

   // Disable submit for the form
   return false;
 });
});
 
function stripeResponseHandler(status, response) {
 // Grab the form:
 var $form = $('#payment-form');

 if (response.error) { // Problem!

   // Display errors in the form:
   $form.find('.payment-errors').text(response.error.message);
   $form.find('.submit').prop('disabled', false); // Re-enable submission

 } else { // 
// Token was created

   // Get token id:
   var token = response.id;

   // Insert token in the form so that it gets to the server after we submit it:
   $form.append($('<input type="hidden" name="stripeToken">').val(token));

   // Submit the form:
   $form.get(0).submit();
 }
};

Just in case, this step is described in the official manual.

Now we can debit the customer using the server. Here are some PHP code examples:

// Setting a secret key
\Stripe\Stripe::setApiKey("your_secret_key");

// Getting the token from the form
$token = $_POST['stripeToken'];

// Creating the charge
try {
 $charge = \Stripe\Charge::create(array(
   "amount" => 1000, // sum in cents
   "currency" => "usd",
   "source" => $token,
   "description" => "Example charge"
   ));
} catch(\Stripe\Error\Card $e) {
 // Payment didn’t come through
}

This is all you have to do to transfer the money from the customer’s card to your Stripe account.

Automatic money transfers to your vendors

Let’s examine an actual example of a transfer. Imagine you are making a platform that sells rare books from small publishing houses worldwide. You need to transfer money to your vendors (publishers) so that they send the book to the client, and you charge a 10$ fee for each sale. You don’t want to be bothered with monthly reports and payments; you just want to transfer the money every time the customer pays. Stripe can do it.

As in the previous case, the vendor has to be located in one of the countries supported by Stripe to set up automatic transfers.

Stripe has a remarkable feature called Managed Acсounts, which we can use to (sort of) create a Stripe account for our vendor which we’ll manage ourselves so that the publishing house won’t have to register in Stripe.

First of all, we have to get the bank account info of the publishing house using the aforementioned Stripe.js script. Like in the case of debiting the customer’s card, to work with the bank account we require the Stripe token.

Stripe.bankAccount.createToken({
 country: $('.country').val(), // 2-character country code (US)
 currency: $('.currency').val(), // 3-character currency code (USD)
 routing_number: $('.routing-number').val(), // bank routing number
 account_number: $('.account-number').val(), // bank account number
 account_holder_name: $('.name').val(), // company owner’s name (name of the owner of the publishing house in our case)
 account_holder_type: $('.account_holder_type').val() // account type — individual enterpreneur or company (individual, company)
// account type (individual, company)
}, stripeResponseHandler);

You can also find this in the manual.

As a side note, be aware that for each country the bank info (routing_number, account_number) is filled in differently. For example, you will require the IBAN number for European countries, which is put into the account_number field while the routing_number field isn’t sent at all. Also, for some countries internal account numbers are merged into a single line when put in the fields. For example, to obtain the correct routing number for a Canadian bank, you will have to merge the transit number and the institution number. If the transit number is 02345 and the institution number is 987, the resulting routing_number will be 02345987. The account number will vary depending on the bank. For Germany you will only require the IBAN number and it’s filled in the routing_number field, e.g., IBAN: DE89370400440532013000 (22 characters). To find out how to fill in these fields for other countries, refer here.

Now that we have the token for the bank account where we will send the money for our vendors, let’s create a Managed Account. Suppose our publishing house is located in the USA, it is a company (not an individual) and we pay it in USD.

\Stripe\Stripe::setApiKey("your_secret_key");
$account = Account::create([
  "country" => 'US',
  "managed" => true,
]);
if (isset($account->id)) {
  try {
      $account->external_accounts->create(
          ["external_account" => $token] // our bank account token
      );
  } catch (InvalidRequest $error) {
     // an error happened when creating the account
  }
}

It might seem that now that we have a Managed Account we can transfer the money, but we have to verify the account first. For this we have to provide Stripe with certain legal data concerning the business. You can check here to determine what data you need in which country.

For our publishing house in the USA we have to provide the following data:

Name Description
legal_entity.address.city The city where the business is located
legal_entity.address.line1 Business address
legal_entity.address.postal_code Postal code
legal_entity.address.state State
legal_entity.business_name Business name
legal_entity.business_tax_id Tax ID number
legal_entity.dob.day Owner’s DOB (day)
legal_entity.dob.month Owner’s DOB (month)
legal_entity.dob.year Owner’s DOB (year)
legal_entity.first_name Owner’s first name
legal_entity.last_name Owner’s last name
legal_entity.ssn_last_4 Four last numbers of the owner’s social security number
legal_entity.type individual/company
tos_acceptance.date Date of accepting the Stripe ToS (Terms of Service)
tos_acceptance.ip The IP address used to accept the Stripe ToS

You can find the Stripe Terms of Service here. The person whose name is used when creating the Managed Account has to accept them.

Stripe might also request additional information. For the US:

Name Description
legal_entity.personal_id_number Personal ID number
legal_entity.verification.document Proof of identity document scan

When you’ve gathered all the necessary info, it’s time to edit the account:

\Stripe\Stripe::setApiKey("your_secret_key");
$account = Account::retrieve($accountId);
$account->legal_entity->address->city = 'New-York';
$account->legal_entity->address->state = 'New-York';
$account->legal_entity->address->postal_code = '00501';
$account->legal_entity->address->line1 = 'Some address';
$account->legal_entity->business_name = 'US TEST;
$account->legal_entity->business_tax_id = '00000001';
$account->legal_entity->dob->day = 1;
$account->legal_entity->dob->month = 1;
$account->legal_entity->dob->year =  1980;
$account->legal_entity->first_name = 'Bob';
$account->legal_entity->last_name = 'Smith';
$account->legal_entity->type = 'company';
$account->legal_entity->ssn_last_4 = '0000';
$account->tos_acceptance->date = 1466074123; // timestamp
$account->tos_acceptance->ip = 123.123.123.123;
try {
   $account->save();
   } catch (InvalidRequest $error) {
// an error happened when saving
 
  }

The Stripe team will check the data and we will see the “Verified” status in the admin panel. https://dashboard.stripe.com/test/applications/users/overview

(you will have to click on images to get a full size view).


However, this is not enough. The Stripe team might point out mistakes in the data or request additional info (for example, personal_id_number).


After the team have checked the data the account will be refreshed. You can set up a webhook for this event.


The necessary fields will be described in the account’s object:

$account->verification->fields_needed

Stripe can also set a deadline for providing the data. If there is a due date, it will be present in the property: $account->verification->due_by.

Stripe provides an excellent environment for verification testing. Using transfers from specific test cards we can simulate various scenarios of the account verification process. Here are some of the example scenarios:

  • The data is not filled in and we can’t carry out payments at all;
  • The payment limit has been reached. This happens when Stripe considers the transfer to be too large and the provided information is insufficient. In this case, it disables the Managed Account;
  • Disabling the account with the request to fill in the data by a certain deadline;
  • Uploading the scan of the document that proves the account owner’s identity.
  • Acceptance and rejection of this scan.

You can find the details on how to simulate these cases here.

In any case, you will have to handle all of these situations, and, in my experience, it is best to provide Stripe with the maximum amount of info to prevent any surprise account deactivations.

When everything is OK and Stripe has verified your Managed Account, you will need to switch to API-based transfers or turn off the automatic ones (same thing). https://dashboard.stripe.com/account/transfers


Now we have a verified account, the transfers are enabled and we can transfer money directly to the vendor.

Suppose we have a book that the vendor wants to sell for 50$. We want 10$ as a fee and we need to include Stripe’s fee for transfer in the price. Right now Stripe charges 2.9% + 30¢ for each transfer. We decide that we will pay this fee on our part; therefore, the user will have to pay 60$ for the book while we pay a fee of 2.04$ to Stripe.

We get the token using Stripe.js and carry out the payment from the server side:

$charge = Charge::create([
   "amount" => 6000, // in cents
   "currency" => 'USD',
   "source" => $token,
   "application_fee" => 1000, 
   "destination" => $managedAccountId
]);

The application_fee property allows us to specify which portion of the transfer will be left on our account. The Stripe fee will always be deducted from our account only, even if we carry out a full transfer to the vendor.

The vendor’s bank account will not be credited immediately; the money is withdrawn once in seven days. This means that we transfer the money to our vendor’s Stripe account, and in seven days it is transferred to the attached bank account.

Additional features

As a bonus, Stripe allows you to save customers, add user-defined metadata when creating payments to help you navigate them, assign descriptions to payments to better keep track of them and more. You can find all of it in the charges API manual

Useful links

Countries supported by Stripe
Custom HTML form for getting a token
Managed Accounts
Getting the token for the bank account
Necessary banking info (by country) 
Necessary legal info for Managed Accounts (by country)
Stripe Services Agreement
Testing account verification
Webhooks 
Stripe Pricing
Stripe API Reference

Stripe
Payments
PHP