В этом посте я объясню, как создать свою собственную торговую площадку. Это означает, что вы можете создавать платежи, как обычно, с помощью Stripe, а также можете попросить своих пользователей настроить учетную запись Stripe на вашем веб-сайте через их API.

Смущенный? Надеюсь, этот пример поможет прояснить ситуацию. Я являюсь командой разработчиков Shopify.com, и я хочу, чтобы мои пользователи могли создавать магазины в моем веб-приложении (на shopify.com), и они также должны иметь возможность взимать плату со своих клиентов. Вы не хотите отсылать пользователей для настройки их платежной информации, потому что это приведет к увеличению количества отказов, и вам придется иметь дело с платежными переводами.

Торговые площадки и платформы используют Stripe Connect для приема денег и выплат третьим лицам. Connect предоставляет полный набор строительных блоков для поддержки практически любой бизнес-модели, включая бизнес по запросу, электронную коммерцию, краудфандинг, поездки и мероприятия. (Взято с сайта Stripe).

Я буду касаться только учетных записей Managed / Custom Stripe Connect, то есть с Stripe нет OAuth. Все аспекты учетной записи управляются в вашем веб-приложении через Stripe API.

Настраивать

Все это руководство выполнено на PHP. В следующих статьях я опубликую это на Python и Node.js. Я собираюсь запустить все это локально с помощью MAMP (Mac, Apache, MySQL, PHP) на моем Mac. Если вы используете Windows, посмотрите WAMP или Linux, посмотрите LAMP. Вам также потребуется установить Composer.

Запустите локальный сервер и перейдите в свой рабочий каталог на терминале. Установите необходимые пакеты PHP с помощью:

composer require stripe/stripe-php

Это загрузит библиотеку Stripe PHP. Будет создан файл composer.json и composer.lock.

Предположения

В этом посте я сделаю несколько предположений. Я предполагаю, что у вас есть какая-то система входа в систему, и у каждого пользователя есть первичный ключ (адрес электронной почты, идентификационный номер и т. Д.).

Настройка учетной записи пользователя

Первый шаг в собственном программировании - это ввести пользователя в основную информацию, чтобы убедиться, что он может продавать и обрабатывать платежи. Это зависит от страны и введенной информации. Всю потенциально необходимую информацию можно найти в разделе Учетная запись в их Stripe docs.

Создайте новую страницу PHP с именем setup.php, которая будет отображать форму, запрашивающую необходимую (отсутствующую) информацию. $stripeAccountId - это столбец в вашей таблице пользователей, который начинается с null.

Вот шаг 1 моего кода (полный код внизу):

<?php
 // Required to import the Stripe library and others.
 require_once('./vendor/autoload.php');
 // Two things grabbed from your account setup. Your user must be
 // logged in. And then you do a look up in your user table for 
 // their corresponding stripeAccountId. If they haven't set it up 
 // yet, it will be null or ''.
 $currentUserId = -1;
 $stripeAccountId = '';
 // Put look up in user session here.
 $currentUserId = 1;
 // YOU_NEED_TO_ADD_CODE
 // Validate user logged in
 if ($currentUserId == -1) {
  die('Error: Invalid user log in.');
 }
 // Put stripe account id from table look up here.
 $stripeAccountId = '';
 // YOU_NEED_TO_ADD_CODE
 // The structure for rendering and updating data is I'm going to
 // load one needed item at a time. So for example if I need:
 // sin_number, phone_number, name... I'll just load sin_number
 // update that info then move on.
 // When there is no account id, load begin_setup as the value.
 // In the action script, I'll know to create an account object.
 if ($stripeAccountId == '') {
  // Case: Brand new user, never setup account.
  echo '<form action="./setup-action.php" method="POST">';
   echo '<input type="text" hidden name="action_type" value="begin_setup" />';
   echo '<button type="submit">Begin Account Setup</button>';
  echo '</form>';
  
 } else {
  // Configure the library. You need to input your own test/live API 
  // Key. This can be found on your Stripe dashboard.
  \Stripe\Stripe::setApiKey('YOUR_SECRET_API_KEY____sk_');
  // Retrieve object stuff will go here
 }
?>

Вы должны увидеть следующую страницу:

Далее мы добавим код «действия». Это обработает форму, которую мы создаем в setup.php. Вот шаг 1 кода действия, полный код внизу. Я прокомментировал большую часть этого, и это касается только объекта создания учетной записи. Вам не нужно сохранять какие-либо банковские данные пользователя, потому что Stripe делает это безопасно. Вы не должны. У них там много умных людей, пусть они этим занимаются. Вам просто нужно сохранить id в результате.

<?php
 // Required to import the Stripe library and others.
 require_once('./vendor/autoload.php');
 
 // Two things grabbed from your account setup. Your user must be
 // logged in. And then you do a look up in your user table for 
 // their corresponding stripeAccountId. If they haven't set it up 
 // yet, it will be null or ''.
 $currentUserId = -1;
 $stripeAccountId = '';
 $currentUserEmail = '';
 // Put look up in user session here.
 $currentUserId = 1;
 // YOU_NEED_TO_ADD_CODE
 // Validate user logged in
 if ($currentUserId == -1) {
  die('Error: Invalid user log in.');
 }
 // Put stripe account id from table look up here.
 $stripeAccountId = '';
 // YOU_NEED_TO_ADD_CODE
 // You also need to lookup the user email at the same time.
 $currentUserEmail = '[email protected]';
 // YOU_NEED_TO_ADD_CODE
 // Get the action type from the form submission.
 $actionType = $_POST['action_type'];
 // More info about this on setup.php
 \Stripe\Stripe::setApiKey('YOUR_SECRET_API_KEY____sk_');
 if ($actionType === 'begin_setup') {
  // Create a new Stripe Connect Account object.
  // For more info: https://stripe.com/docs/api#create_account
  $result = \Stripe\Account::create(array(
   "type" => "custom",
   "country" => "US",
   "email" => $currentUserEmail,
  ));
  // About these parameters, you can support more countries but
  // you will have to limit your users. You need the proper
  // country code and different type of information is required.
  // Personally, I had issues with opening it up too much b/c
  // a field may be called "Tax Id" which is called different
  // things in different countries. In Canada, it's your SIN
  // but when the label said Tax Id there was a high drop off
  // rate because they didn't know what that was. We know it as
  // a SIN number.
  // Result will contain a response from the Stripe API call.
  // You want to use this information.
  // Both these functions will print out a tone of data returned.
  // print_r($result);
  // var_dump($result);
  // What you want is the stripeAccountId whic is accessed
  // with $result->id or $result['id']
  // It will give you a string like this acct_112314324ANOSDNAID
  // And YOU NEED TO SAVE IT WITH YOUR USER INFO. This is what
  // we reference on setup.php this is now their stripeAccountId.
 } else {
  die('Invalid action type.');
 }
?>

И вы создали свой первый объект аккаунта! Но вам все равно нужно много информации. Если вы откроете свою панель управления, вы можете увидеть все это в красивом пользовательском интерфейсе или получить доступ через API.

Следующим шагом является программирование setup.php для проверки отсутствующей / необходимой информации, поскольку существует идентификатор учетной записи.

Мы собираемся извлечь объект учетной записи на основе идентификатора и посмотреть необходимые элементы. Вы можете видеть в возвращенном объекте (на основе документации), что есть массив необходимых полей ($stripeObj->verfication->fields_needed).

Чтобы этот пост оставался разумным по длине, весь приведенный ниже код идет туда, где комментарий Retrieve object stuff goes here.

<?php
... other code
  $stripeAccountObj = \Stripe\Account::retrieve($stripeAccountId);
  # So if fields needed is empty, you are good.
  if (count($stripeAccountObj->verification->fields_needed) == 0) {
   die('You are all setup!');
  } else {
   # Other wise load element one.
   # Following the same structure as above:
   $neededCode = $stripeAccountObj->verification->fields_needed[0];
   echo '<form action="./setup-action.php" method="POST">';
    echo '<input type="text" hidden name="action_type" value="' . $neededCode . '" />';
    # I recommend customizing this a little.
    echo '<label>' . $neededCode . '</label><br/>';
    echo '<input type="text" name="value_textbox" required /><br/>';
    echo '<button type="submit">Update</button>';
   echo '</form>';
  }
... other code
?>

Итак, вы увидите первый нужный элемент. Теперь для каждого поля может потребоваться определенный формат или конкретная метка. Я не включаю это в эту публикацию, это самый минимум, но я рекомендую контролировать маркировку для лучшего взаимодействия с пользователем. Первое поле, необходимое для моей учетной записи, - это день моего рождения.

Нам нужно добавить тип действия в setup-action.php, чтобы он мог обновить объект учетной записи. Чтобы это было максимально динамично, я не буду программировать каждое потенциальное поле по отдельности, а буду обновлять поле в зависимости от типа действия.

Приведенный ниже код находится в setup-action.php и заменяет оператор else.

<?php
...
} else {
  // Get the value
  $value = $_POST['value_textbox'];
  if ($value == "") {
   die('error: value cant be blank.');
  }
  $stripeAccountObj = \Stripe\Account::retrieve($stripeAccountId);
  // Check for action type is actually needed
  if (!in_array($actionType, $stripeAccountObj->verification->fields_needed)) {
   die('Error: Not a required action type.');
  }
  // Update the action type
  $stripeAccountObj[$actionType] = $value;
  $stripeAccountObj->save();
  // For more about this refer to the docs:
  // https://stripe.com/docs/api#update_account
  echo 'Done';
 }
...
?>

Единственная проблема с приведенным выше кодом заключается в том, что объект учетной записи не имеет такой простой структуры. Например, дата рождения вложена в собственный объект, поэтому вам нужно создать функцию для обработки этого:

function updateNestedObject($stripeObj, $actionType, $value) {
  // Use the explode to split by .
  $arrayOfNestObjectKeys = explode('.', $actionType);
  return updateChild($stripeObj, $arrayOfNestObjectKeys, $value);
 }
 function updateChild ($root, $listOfKeys, $value) {
  if (count($listOfKeys) == 1) {
   // Last element
   $root[$listOfKeys[0]] = $value;
   return $root;
  } else {
   // Other wise find the child
   // Param 1: Grabs the child object
   // Param 2: Takes the first elment off array
   $child = $root[$listOfKeys[0]];
   $remainingKeys = array_splice($listOfKeys, 1);
   $root[$listOfKeys[0]] = updateChild ($child, $remainingKeys, $value);
   return $root;
  }
 }

Поместите эти две функции в начало файла и добавьте следующий код вместо $stripeAccountObj[$actionType] = $value;:

  if (strpos($actionType, '.') !== false) {
   // If the actionType has a . means it's nested.
   // Ex. Date of birth is legal_entity.dob.day
   //     so to reference it you need to do
   // $obj->legal_entity->dob->day
   $stripeAccountObj = updateNestedObject($stripeAccountObj, $actionType, $value);
   $stripeAccountObj->save();
  } else {
   // Update the action type  
   $stripeAccountObj[$actionType] = $value;
   $stripeAccountObj->save();
  }

И если вы попробуете еще раз, это все равно не сработает из-за:

Missing required param: legal_entity[dob][month]

это означает, что вам нужно иметь месяц и год одновременно. Поэтому нам придется изменить setup-action.php, чтобы искать следующий формат YYYY-MM-DD, если тип действия - любой из дат рождения.

Эти две функции бесполезны для даты рождения, но будут полезны для будущих атрибутов.

Нам нужно будет создать особый случай. Замените оператор if текущего типа действия следующим:

  if (
   $actionType === 'legal_entity.dob.day' ||
   $actionType === 'legal_entity.dob.month' ||
   $actionType === 'legal_entity.dob.year'
  ) {
   // Special case:
   $valueInPieces = explode('-', $value);
   // If invalid format, hence not 3 -
   if (count($valueInPieces) != 3) {
    // So not array of 3
    die('Error: Invalid format for date, must be YYYY-MM-DD');
   }
   // Otherwise good, save it!
   $stripeAccountObj->legal_entity->dob->year = $valueInPieces[0];
   $stripeAccountObj->legal_entity->dob->month = $valueInPieces[1];
   $stripeAccountObj->legal_entity->dob->day = $valueInPieces[2];
   $stripeAccountObj->save();
  } else if (strpos($actionType, '.') !== false) {
   // If the actionType has a . means it's nested.
   // Ex. Date of birth is legal_entity.dob.day
   //     so to reference it you need to do
   // $obj->legal_entity->dob->day
   $stripeAccountObj = updateNestedObject($stripeAccountObj, $actionType, $value);
   $stripeAccountObj->save();
  } else {
   // Update the action type  
   $stripeAccountObj[$actionType] = $value;
   $stripeAccountObj->save();
  }

Если вы заблудились, полный код находится внизу. Проверяю это, и это работает!

Когда вы перезагрузите setup.php, вы должны увидеть новое поле.

И точно так же вам больше не нужно добавлять код, потому что эти две добавленные функции могут обрабатывать legal_entity.first_name регистр. Я просто ввел имя, перезагрузил настройки, затем ввел фамилию и т. Д. legal_entity.type будет одним из обязательных полей. Это означает, что зарядка осуществляется компанией или частным лицом. В этом случае потребуется другая налоговая информация. Обратитесь к документации Stripe для всех полей, потому что некоторые из них имеют ограниченные ответы. Например, с типом юридического лица их всего два: individual или company. Я хочу изменить свой setup.php, чтобы эти параметры загружались только как <select />.

   ... other code ...
   # Following the same structure as above:
   $neededCode = $stripeAccountObj->verification->fields_needed[0];
   if ($neededCode === 'legal_entity.type') {
    // Special case
    $OPTIONS = ['individual' => 'Individual', 'company'=>'Company'];
    
    echo '<form action="./setup-action.php" method="POST">';
     echo '<input type="text" hidden name="action_type" value="' . $neededCode . '" />';
     echo '<label>' . $neededCode . '</label><br/>';
     // Using a Select
     echo '<select name="value_textbox">';
      foreach ($OPTIONS as $key => $value) {
       echo '<option value="' . $key . '">' . $value . '</option>';
      }
     echo '</select><br/>';
     echo '<button type="submit">Update</button>';
    echo '</form>';
   } else {
    echo '<form action="./setup-action.php" method="POST">';
     echo '<input type="text" hidden name="action_type" value="' . $neededCode . '" />';
     # I recommend customizing this a little.
     echo '<label>' . $neededCode . '</label><br/>';
     echo '<input type="text" name="value_textbox" required /><br/>';
     echo '<button type="submit">Update</button>';
    echo '</form>';
   }
   ... other code ...

И это сработало идеально:

Вы найдете еще один особый случай с адресом. Объект адреса ожидает сразу адрес, город, почтовый индекс / почтовый индекс и страну. Для этого поля должен быть пользовательский интерфейс.

Добавьте еще один особый случай в setup.php (тот же оператор if, что и в legal_entity.types).

   } else if (
    $neededCode === 'legal_entity.address.city' ||
    $neededCode === 'legal_entity.address.country' ||
    $neededCode === 'legal_entity.address.line1' ||
    $neededCode === 'legal_entity.address.line2' ||
    $neededCode === 'legal_entity.address.postal_code' ||
    $neededCode === 'legal_entity.address.state'
   ) {
    $COUNTRY_OPTIONS = ["CA" => "Canada", "US" => "United States"];
// Special case: address
    echo '<form action="./setup-action.php" method="POST">';
     echo '<input type="text" hidden name="action_type" value="address" />';
     echo '<label>Address:</label><br/>';
     echo '<input type="text" placeholder="123 Fake Street" name="line_textbox" required /><br/>';
     echo '<input type="text" placeholder="Apartment 1" name="line2_textbox" /><br/>';
     echo '<input type="text" placeholder="Toronto" name="city_textbox" required /><br/>';
     echo '<input type="text" placeholder="Ontario" name="state_textbox" required /><br/>';
     echo '<select name="country_textbox">';
      foreach ($COUNTRY_OPTIONS as $key => $value) {
       echo '<option value="' . $key . '">' . $value . '</option>';
      }
     echo '</select><br/>';
     echo '<input type="text" placeholder="M8K 8L3" name="postal_textbox" required /><br/>';
     echo '<button type="submit">Update</button>';
    echo '</form>';

И это будет выглядеть так:

Затем вам нужно будет обновить файл setup-action.php для поддержки этого. Я включу вторую половину этого файла, так как она стала довольно большой. Весь этот код входит в else if и else проверки if ($actionType == 'begin_setup') {.

 } else if (
  $actionType === 'legal_entity.address.city' ||
  $actionType === 'legal_entity.address.country' ||
  $actionType === 'legal_entity.address.line1' ||
  $actionType === 'legal_entity.address.line2' ||
  $actionType === 'legal_entity.address.postal_code' ||
  $actionType === 'legal_entity.address.state' ||
  $actionType === 'address'
 ) {
  // Special case for address
  $line = $_POST['line_textbox'];
  $line2 = $_POST['line2_textbox'];
  $city = $_POST['city_textbox'];
  $state = $_POST['state_textbox'];
  $country = $_POST['country_textbox'];
  $postal = $_POST['postal_textbox'];
  // Check for all required values
  if (
   $line == '' ||
   $city == '' ||
   $state == '' ||
   $country == '' ||
   $postal == ''
  ) {
   die('Error: Missing required address value.');
  }
  // Got this error: Uncaught InvalidArgumentException: You cannot set 'line2'to an empty string. We interpret empty strings as NULL in requests. You may set obj->line2 = NULL to delete the property in
  // So this handles it
  if ($line2 == '') {
   $line = null;
  }
  $stripeAccountObj = \Stripe\Account::retrieve($stripeAccountId);
  $stripeAccountObj->legal_entity->address->line1 = $line;
  if ($line2 != "") {
   $stripeAccountObj->legal_entity->address->line2 = $line2;
  }
  $stripeAccountObj->legal_entity->address->city = $city;
  $stripeAccountObj->legal_entity->address->state = $state;
  $stripeAccountObj->legal_entity->address->country = $country;
  $stripeAccountObj->legal_entity->address->postal_code = $postal;
  $stripeAccountObj->save();
  echo 'Done';
 } else {
  ... other code
 }

Если вы посмотрите MAMP/logs/php_error.log, вы сможете найти причину, по которой бросается 500. Я поднял это сообщение потому, что получаю сообщение об ошибке (Address for business must match account country). При этом самое простое решение - удалить параметр Canada из setup.php.

После внесения этого изменения адрес должен работать! Следующее, что требуется - это legal_entity.ssn_last_4 (последние 4 цифры номера SSN). Это не особый случай. Затем вас попросят ввести legal_entity.personal_id_number.

В конце концов вы наткнетесь на следующее обязательное поле legal_entity.verification.document, и здесь должен быть особый случай, как во внешнем интерфейсе для загрузки файлов, так и в серверной части. Я собираюсь использовать загрузку файлов W3 Schools для необходимого кода внешнего интерфейса и мой Github Gist для кода на стороне сервера (я собираюсь удалить часть AWS S3).

В моем setup.php будет следующее:

... other code
   } else if ($neededCode == 'legal_entity.verification.document') {
    // Special case:
    echo '<form action="./setup-action.php" method="POST" enctype="multipart/form-data">';
     echo '<input type="text" hidden name="action_type" value="' . $neededCode . '" />';
     # I recommend customizing this a little.
     echo '<label>' . $neededCode . '</label><br/>';
     echo '<input type="file" name="fileToUpload" required /><br/>';
     echo '<button type="submit">Update</button>';
    echo '</form>';
   } else {
   ... other code

Прежде чем приступить к работе с серверной частью кода, я должен упомянуть, что собираюсь подать руководство по загрузке файлов, предоставленное Stripe. На основе документации мы возьмем файл из формы, загрузим его в корзину Stripe S3 и получим ответ. В ответе будет идентификатор, и мы сохраним его в нашем объекте учетной записи. Код сервера будет:

} else if ($actionType === 'legal_entity.verification.document') {
  $file = $_FILES["fileToUpload"]['tmp_name'];
  $fp = fopen($file, 'r');
  $fileResponse = \Stripe\FileUpload::create(array(
   'purpose' => 'identity_document',
   'file' => $fp
  ));
  $stripeAccountObj = \Stripe\Account::retrieve($stripeAccountId);
  $stripeAccountObj->legal_entity->verification->document =        
$fileResponse->id;
  $stripeAccountObj->save();
  echo 'Done';
 } else {
... other code

И работает! Вы можете загрузить для этого любое изображение. Stripe пытается проверить вашего клиента, поэтому они запрашивают сканирование удостоверения личности государственного образца. Но для тестирования подойдет любое изображение. Если это не сработает, скорее всего, это ошибка разрешений.

Сразу после обновления я получил tos_acceptance.date, что означает дату, когда они приняли Условия обслуживания. Что касается TOS, я рекомендую заняться этим в самом начале. Сообщение типа Нажимая« Начать настройку , вы соглашаетесь с нашими условиями и услугами в дополнение к Соглашению об учетной записи Stripe Connected». У Stripe есть много информации об этом на их сайте TOS.

Я собираюсь обновить исходную форму в setup.php:

 if ($stripeAccountId == '') {
  // Case: Brand new user, never setup account.
  echo '<form action="./setup-action.php" method="POST">';
   echo '<input type="text" hidden name="action_type" value="begin_setup" />';
   echo '<label>By registering your account, you agree to our <a href="#Terms">Services Agreement</a> and the <a href="https://stripe.com/us/connect-account/legal">Stripe Connected Account Agreement</a>.</label>';
   echo '<button type="submit">Begin Account Setup</button>';
  echo '</form>';
 } else {
  ... other code

а на стороне действия нам нужно будет добавить извлечение и обновление сразу после создания. Я добавил:

if ($actionType === 'begin_setup') {
  // Create a new Stripe Connect Account object.
  // For more info: https://stripe.com/docs/api#create_account
  $result = \Stripe\Account::create(array(
   "type" => "custom",
   "country" => "US",
   "email" => $currentUserEmail,
  ));
  $stripeAccountId = $result->id;
  // Save stripe account id in db
  // echo 'stripeAccountId:' . $stripeAccountId;
  // Accept the TOS
  $stripeAccountObj = \Stripe\Account::retrieve($stripeAccountId);
  $stripeAccountObj->tos_acceptance->date = time();
  $stripeAccountObj->tos_acceptance->ip = $_SERVER['REMOTE_ADDR'];
  $stripeAccountObj->save();

Вам нужно будет очистить текущего пользователя, с которым вы тестируете. Они никогда не смогут принять TOS, потому что вы уже создали идентификатор. Удалите stripeAccountId в таблице и перезагрузите страницу настройки. Вы должны увидеть кнопку начала настройки.

Вам придется снова пройти весь процесс проверки, но теперь Условия использования принимаются с самого начала. Итак, дата рождения, имя, фамилия, тип учетной записи, адрес, ssn_last_4, personal_id_number (должно быть 9 цифр) и подтверждающий документ.

Первым новым полем будет банковский счет. Банковский счет - это особый случай, но не такой, как другие особые случаи. Для этого требуется некоторый Javascript и создание токена с использованием библиотеки Stripe JS и передача этого токена в бэкэнд.

Если вы посмотрите документы, они предоставят вам информацию для настройки. Вам нужно будет добавить как JS, так и PHP-код. Начиная с формы (setup.php), вы добавите еще один регистр:

   } else if ($neededCode == 'bank_account') {
    echo '<form id="bankingForm" method="POST">';
     echo '<input type="text" hidden name="action_type" value="banking" />';
     # I recommend customizing this a little.
     echo '<label>' . $neededCode . '</label><br/>';
     echo '<input type="text" id="routing_number" required /><br/>';
     echo '<input type="text" id="account_number" required /><br/>';
     echo '<input type="text" id="account_holder_name" required /><br/>';
     $ACCOUNT_TYPE_OPTIONS = ['individual' => 'Individual', 'business' => 'Business'];
     echo '<select id="account_holder_type">';
      foreach ($ACCOUNT_TYPE_OPTIONS as $key => $value) {
       echo '<option value="' . $key . '">' . $value . '</option>';
      }
     echo '</select><br/>';
    echo '<button type="submit">Update</button>';
    echo '</form>';
    echo '<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>';
    echo '<script src="https://js.stripe.com/v3/"></script>';
    echo '<script>';
     echo 'var stripe = Stripe(\'YOUR_PUBLISHABLE_API_KEY____pb_\', { stripeAccount: \'' . $stripeAccountId . '\' });';
    echo '</script>';
    echo '<script src="./bank-account.js"></script>';
   } else {
   ... other code

Объясняя приведенный выше код, у вас есть форма с возможными банковскими элементами. И вы должны динамически добавлять идентификатор Stripe Account. Там вы увидите публикуемый API-ключ, вам нужно будет получить его на панели инструментов. Затем вам нужно создать новый файл JS с именем bank-account.js. Это будет использовать библиотеку Stripe.js и сгенерировать токен. Затем вы передадите токен сценарию действия через почтовый запрос AJAX. Вот почему теперь включен JQuery для выполнения этого запроса AJAX.

bank-account.js выглядит так:

$("#bankingForm").submit(function(event){
 event.preventDefault();
 console.log('submitBankAccount');
 stripe.createToken('bank_account', {
  country: 'US',
  currency: 'usd',
  routing_number: $("#routing_number").val(),
  account_number: $("#account_number").val(),
  account_holder_name: $("#account_holder_name").val(),
  account_holder_type: $("#account_holder_type").val(),
 }).then(function(result) {
   // Handle result.error or result.token
   console.log(result);
   if (result.error) {
    alert('Error');
    console.log(result.error);
   } else {
    $.post(
     './setup-action.php',
     {
      token: result.token,
      action_type: 'banking',
     },
     function (data) {
      console.log('response');
      console.log(data);
      // If successful reload page
      location.reload();
     }
    );
   }
 });
 return false;
});

Я рекомендую добавить несколько проверок форматирования и прочего, но это сработает. Если запрос будет успешным, он обновит страницу. Затем вам нужно добавить код stripe-action.php. Это похоже на то, что токен создается во внешнем интерфейсе, и все, что вы передаете, - это весь объект, но вам просто нужен идентификатор.

 } else if ($actionType === 'banking') {
  $token = $_POST['token'];
  if ($token == '') {
   $response['success'] = false;
   $response['message'] = 'No token';
   echo json_encode($response);
   die('');
  }
  $stripeAccountObj = \Stripe\Account::retrieve($stripeAccountId);
  $stripeAccountObj->external_accounts->create(array("external_account" => $token['id']));
  $stripeAccountObj->save();
 } else {
  ... other code ...

Это сохранит токен. Для тестирования загляните на страницу Stripe Connect Testing. В нем есть все номера тестовых учетных записей и тому подобное. Для этого вам нужны учетные записи в США.

Отправьте форму и:

Вы сделали! Мы протестировали только индивидуальную учетную запись и только для США, но вам просто нужно добавить больше особых случаев.

Я собираюсь включить полный код, но ниже он будет взимать плату с людей.

setup.php

<?php
 // Required to import the Stripe library and others.
 require_once('./vendor/autoload.php');
// Two things grabbed from your account setup. Your user must be
 // logged in. And then you do a look up in your user table for 
 // their corresponding stripeAccountId. If they haven't set it up
 // yet, it will be null or ''.
 $currentUserId = -1;
 $stripeAccountId = '';
// Put look up in user session here.
 $currentUserId = 1;
 // YOU_NEED_TO_ADD_CODE
// Validate user logged in
 if ($currentUserId == -1) {
  die('Error: Invalid user log in.');
 }
// Put stripe account id from table look up here.
 $stripeAccountId = '';
 // YOU_NEED_TO_ADD_CODE
// The structure for rendering and updating data is I'm going to
 // load one needed item at a time. So for example if I need:
 // sin_number, phone_number, name... I'll just load sin_number
 // update that info then move on.
// When there is no account id, load begin_setup as the value.
 // In the action script, I'll know to create an account object.
if ($stripeAccountId == '') {
  // Case: Brand new user, never setup account.
echo '<form action="./setup-action.php" method="POST">';
   echo '<input type="text" hidden name="action_type" value="begin_setup" />';
   echo '<label>By registering your account, you agree to our <a href="#Terms">Services Agreement</a> and the <a href="https://stripe.com/us/connect-account/legal">Stripe Connected Account Agreement</a>.</label>';
   echo '<button type="submit">Begin Account Setup</button>';
  echo '</form>';
  
 } else {
  // Configure the library. You need to input your own test/live API Key.
  // This can be found on your Stripe dashboard.
  \Stripe\Stripe::setApiKey('YOUR_SECRET_API_KEY____sk_');
$stripeAccountObj = \Stripe\Account::retrieve($stripeAccountId);
# So if fields needed is empty, you are good.
  if (count($stripeAccountObj->verification->fields_needed) == 0) {
   die('You are all setup!');
  } else {
   # Other wise load element one.
# Following the same structure as above:
   $neededCode = $stripeAccountObj->verification->fields_needed[0];
if ($neededCode === 'legal_entity.type') {
    // Special case
    $OPTIONS = ['individual' => 'Individual', 'company'=>'Company'];
    echo '<form action="./setup-action.php" method="POST">';
     echo '<input type="text" hidden name="action_type" value="' . $neededCode . '" />';
     echo '<label>' . $neededCode . '</label><br/>';
     // Using a Select
     echo '<select name="value_textbox">';
      foreach ($OPTIONS as $key => $value) {
       echo '<option value="' . $key . '">' . $value . '</option>';
      }
     echo '</select><br/>';
     echo '<button type="submit">Update</button>';
    echo '</form>';
   } else if (
    $neededCode === 'legal_entity.address.city' ||
    $neededCode === 'legal_entity.address.country' ||
    $neededCode === 'legal_entity.address.line1' ||
    $neededCode === 'legal_entity.address.line2' ||
    $neededCode === 'legal_entity.address.postal_code' ||
    $neededCode === 'legal_entity.address.state'
   ) {
    $COUNTRY_OPTIONS = ["US" => "United States"];
    // Special case: address
    echo '<form action="./setup-action.php" method="POST">';
     echo '<input type="text" hidden name="action_type" value="address" />';
     echo '<label>Address:</label><br/>';
     echo '<input type="text" placeholder="123 Fake Street" name="line_textbox" required /><br/>';
     echo '<input type="text" placeholder="Apartment 1" name="line2_textbox" /><br/>';
     echo '<input type="text" placeholder="Toronto" name="city_textbox" required /><br/>';
     echo '<input type="text" placeholder="Ontario" name="state_textbox" required /><br/>';
     echo '<select name="country_textbox">';
      foreach ($COUNTRY_OPTIONS as $key => $value) {
       echo '<option value="' . $key . '">' . $value . '</option>';
      }
     echo '</select><br/>';
     echo '<input type="text" placeholder="M8K 8L3" name="postal_textbox" required /><br/>';
     echo '<button type="submit">Update</button>';
    echo '</form>';
   } else if ($neededCode == 'legal_entity.verification.document') {
    // Special case:
    echo '<form action="./setup-action.php" method="POST" enctype="multipart/form-data">';
     echo '<input type="text" hidden name="action_type" value="' . $neededCode . '" />';
     # I recommend customizing this a little.
     echo '<label>' . $neededCode . '</label><br/>';
     echo '<input type="file" name="fileToUpload" required /><br/>';
     echo '<button type="submit">Update</button>';
    echo '</form>';
   } else if ($neededCode == 'bank_account') {
    echo '<form id="bankingForm" method="POST">';
     echo '<input type="text" hidden name="action_type" value="banking" />';
     # I recommend customizing this a little.
     echo '<label>' . $neededCode . '</label><br/>';
     echo '<input type="text" id="routing_number" required /><br/>';
     echo '<input type="text" id="account_number" required /><br/>';
     echo '<input type="text" id="account_holder_name" required /><br/>';
$ACCOUNT_TYPE_OPTIONS = ['individual' => 'Individual', 'business' => 'Business'];
     echo '<select id="account_holder_type">';
      foreach ($ACCOUNT_TYPE_OPTIONS as $key => $value) {
       echo '<option value="' . $key . '">' . $value . '</option>';
      }
     echo '</select><br/>';
    echo '<button type="submit">Update</button>';
    echo '</form>';
    echo '<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>';
    echo '<script src="https://js.stripe.com/v3/"></script>';
    echo '<script>';
     echo 'var stripe = Stripe(\'YOUR_PUBLISHABLE_API_KEY____pb_\', { stripeAccount: \'' . $stripeAccountId . '\' });';
    echo '</script>';
    echo '<script src="./bank-account.js"></script>';
   } else {
    echo '<form action="./setup-action.php" method="POST">';
     echo '<input type="text" hidden name="action_type" value="' . $neededCode . '" />';
     # I recommend customizing this a little.
     echo '<label>' . $neededCode . '</label><br/>';
     echo '<input type="text" name="value_textbox" required /><br/>';
     echo '<button type="submit">Update</button>';
    echo '</form>';
   }
  }
 }
?>

setup-action.php

<?php
 function updateNestedObject($stripeObj, $actionType, $value) {
  // Use the explode to split by .
  $arrayOfNestObjectKeys = explode('.', $actionType);
  return updateChild($stripeObj, $arrayOfNestObjectKeys, $value);
 }
 function updateChild ($root, $listOfKeys, $value) {
  if (count($listOfKeys) == 1) {
   // Last element
   $root[$listOfKeys[0]] = $value;
   return $root;
  } else {
   // Other wise find the child
   // Param 1: Grabs the child object
   // Param 2: Takes the first elment off array
   $child = $root[$listOfKeys[0]];
   $remainingKeys = array_splice($listOfKeys, 1);
   $root[$listOfKeys[0]] = updateChild ($child, $remainingKeys, $value);
   return $root;
  }
 }
// Required to import the Stripe library and others.
 require_once('./vendor/autoload.php');
 
 // Two things grabbed from your account setup. Your user must be
 // logged in. And then you do a look up in your user table for 
 // their corresponding stripeAccountId. If they haven't set it up
 // yet, it will be null or ''.
 $currentUserId = -1;
 $stripeAccountId = '';
 $currentUserEmail = '';
// Put look up in user session here.
 $currentUserId = 1;
 // YOU_NEED_TO_ADD_CODE
 // Validate user logged in
 if ($currentUserId == -1) {
  die('Error: Invalid user log in.');
 }
// Put stripe account id from table look up here.
 $stripeAccountId = '';
 // YOU_NEED_TO_ADD_CODE
// You also need to lookup the user email at the same time.
 $currentUserEmail = '[email protected]';
 // YOU_NEED_TO_ADD_CODE
// Get the action type from the form submission.
 $actionType = $_POST['action_type'];
 // More info about this on setup.php
 \Stripe\Stripe::setApiKey('YOUR_SECRET_API_KEY____sk_');
if ($actionType === 'begin_setup') {
  // Create a new Stripe Connect Account object.
  // For more info: https://stripe.com/docs/api#create_account
  $result = \Stripe\Account::create(array(
   "type" => "custom",
   "country" => "US",
   "email" => $currentUserEmail,
  ));
// About these parameters, you can support more countries but
  // you will have to limit your users. You need the proper
  // country code and different type of information is required.
  // Personally, I had issues with opening it up too much b/c
  // a field may be called "Tax Id" which is called different
  // things in different countries. In Canada, it's your SIN
  // but when the label said Tax Id there was a high drop off
  // rate because they didn't know what that was. We know it as
  // a SIN number.
// Result will contain a response from the Stripe API call.
  // You want to use this information.
// Both these functions will print out a tone of data returned.
  // print_r($result);
  // var_dump($result);
// What you want is the stripeAccountId whic is accessed
  // with $result->id or $result['id']
// It will give you a string like this acct_112314324ANOSDNAID
  // And YOU NEED TO SAVE IT WITH YOUR USER INFO. This is what
  // we reference on setup.php this is now their stripeAccountId.
  $stripeAccountId = $result->id;
  echo 'stripeAccountId:' . $stripeAccountId;
// Accept the TOS
  $stripeAccountObj = \Stripe\Account::retrieve($stripeAccountId);
  $stripeAccountObj->tos_acceptance->date = time();
  $stripeAccountObj->tos_acceptance->ip = $_SERVER['REMOTE_ADDR'];
  $stripeAccountObj->save();
} else if (
  $actionType === 'legal_entity.address.city' ||
  $actionType === 'legal_entity.address.country' ||
  $actionType === 'legal_entity.address.line1' ||
  $actionType === 'legal_entity.address.line2' ||
  $actionType === 'legal_entity.address.postal_code' ||
  $actionType === 'legal_entity.address.state' ||
  $actionType === 'address'
 ) {
  // Special case for address
  $line = $_POST['line_textbox'];
  $line2 = $_POST['line2_textbox'];
  $city = $_POST['city_textbox'];
  $state = $_POST['state_textbox'];
  $country = $_POST['country_textbox'];
  $postal = $_POST['postal_textbox'];
// Check for all required values
  if (
   $line == '' ||
   $city == '' ||
   $state == '' ||
   $country == '' ||
   $postal == ''
  ) {
   die('Error: Missing required address value.');
  }
$stripeAccountObj = \Stripe\Account::retrieve($stripeAccountId);
  $stripeAccountObj->legal_entity->address->line1 = $line;
  if ($line2 != "") {
   $stripeAccountObj->legal_entity->address->line2 = $line2;
  }
  $stripeAccountObj->legal_entity->address->city = $city;
  $stripeAccountObj->legal_entity->address->state = $state;
  $stripeAccountObj->legal_entity->address->country = $country;
  $stripeAccountObj->legal_entity->address->postal_code = $postal;
  $stripeAccountObj->save();
echo 'Done';
 } else if ($actionType === 'legal_entity.verification.document') {
  $file = $_FILES["fileToUpload"]['tmp_name'];
$fp = fopen($file, 'r');
  $fileResponse = \Stripe\FileUpload::create(array(
   'purpose' => 'identity_document',
   'file' => $fp
  ));
$stripeAccountObj = \Stripe\Account::retrieve($stripeAccountId);
  $stripeAccountObj->legal_entity->verification->document = $fileResponse->id;
  $stripeAccountObj->save();
echo 'Done';
 } else if ($actionType === 'banking') {
  $token = $_POST['token'];
  if ($token == '') {
   $response['success'] = false;
   $response['message'] = 'No token';
   echo json_encode($response);
   die('');
   }
$stripeAccountObj = \Stripe\Account::retrieve($stripeAccountId);
   $stripeAccountObj->external_accounts->create(array("external_account" => $token['id']));
   $stripeAccountObj->save();
 } else {
  // Get the value
  $value = $_POST['value_textbox'];
if ($value == "") {
   die('error: value cant be blank.');
  }
$stripeAccountObj = \Stripe\Account::retrieve($stripeAccountId);
// Check for action type is actually needed
  if (!in_array($actionType, $stripeAccountObj->verification->fields_needed)) {
   die('Error: Not a required action type.');
  }
if (
   $actionType === 'legal_entity.dob.day' ||
   $actionType === 'legal_entity.dob.month' ||
   $actionType === 'legal_entity.dob.year'
  ) {
   // Special case:
   $valueInPieces = explode('-', $value);
// If invalid format, hence not 3 -
   if (count($valueInPieces) != 3) {
    // So not array of 3
    die('Error: Invalid format for date, must be YYYY-MM-DD');
   }
// Otherwise good, save it!
   $stripeAccountObj->legal_entity->dob->year = $valueInPieces[0];
   $stripeAccountObj->legal_entity->dob->month = $valueInPieces[1];
   $stripeAccountObj->legal_entity->dob->day = $valueInPieces[2];
   $stripeAccountObj->save();
  } else if (strpos($actionType, '.') !== false) {
   // If the actionType has a . means it's nested.
   // Ex. Date of birth is legal_entity.dob.day
   //     so to reference it you need to do
   // $obj->legal_entity->dob->day
   $stripeAccountObj = updateNestedObject($stripeAccountObj, $actionType, $value);
   $stripeAccountObj->save();
  } else {
   // Update the action type  
   $stripeAccountObj[$actionType] = $value;
   $stripeAccountObj->save();
  }
  // For more about this refer to the docs:
  // https://stripe.com/docs/api#update_account
  echo 'Done';
 }
?>

bank-account.js

$("#bankingForm").submit(function(event){
 event.preventDefault();
 console.log('submitBankAccount');
 stripe.createToken('bank_account', {
  country: 'US',
  currency: 'usd',
  routing_number: $("#routing_number").val(),
  account_number: $("#account_number").val(),
  account_holder_name: $("#account_holder_name").val(),
  account_holder_type: $("#account_holder_type").val(),
 }).then(function(result) {
   // Handle result.error or result.token
   console.log(result);
   if (result.error) {
    alert('Error');
    console.log(result.error);
   } else {
    $.post(
     './setup-action.php',
     {
      token: result.token,
      action_type: 'banking',
     },
     function (data) {
      console.log('response');
      console.log(data);
      // If successful reload page
      location.reload();
     }
    );
   }
 });
 return false;
});

Создание списания

Создать списание довольно просто, все, что вам нужно, это идентификатор учетной записи Stripe. Как и при обычном списании, вы просто передаете идентификатор:

\Stripe\Stripe::setApiKey("YOUR_SECRET_STRIPE_KEY");

$charge = \Stripe\Charge::create(array(
  "amount" => 1000,
  "currency" => "usd",
  "source" => "tok_visa",
), array("stripe_account" => $stripeAccountId));

В приведенном выше коде предполагается, что у вас есть переменная с именем stripeAccountId.