Калининград+7.962.2626.555

Приём платежей через Stripe.com

27.06.2018

Многие платёжные системы приучили нас к следующему формату подключения приёма платежей: на своей стороне мы генерируем форму с набором input'ов type="hidden" и кнопкой отправки данных формы на некий url платёжной системы. Соответственно, кликая по этой кнопке, мы переходим на страницу платёжной системы и уже там нам предлагается заполнить данные карты. По завершении платёжная система делает запрос на указанный нами url, в котором содержится информация о состоянии платежа и его деталях.

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

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

Мы же попробуем обойтись без готовых решений и попробуем максимально нативно решить задачу.

Логика работы

Всё действие состоим минимум из 2-х запросов:

  1. Запрос на получение токена;
  2. Запрос на оплату.

1. Запрос на получение токена (сущность TOKENS)

<?
$publicKey = 'pk_test_....';

$arrayPost = array(
    'card' => array(
        'number' => '4242424242424242',
        'exp_month' => '12',
        'exp_year' => '2020',
        'cvc' => '123',
    ),
);

$curl = curl_init();

curl_setopt_array($curl, array(
    CURLOPT_URL => 'https://api.stripe.com/v1/tokens',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => http_build_query($arrayPost),
    CURLOPT_HTTPHEADER => array(
        'Authorization: Bearer '.$publicKey,
        'Content-Type: application/x-www-form-urlencoded',
        'Cache-Control: no-cache',
    ),
));

$response = curl_exec($curl);
$arrayInfo = curl_getinfo($curl);
curl_close($curl);

Обратите внимание на заголовок Content-Type, который должен быть строго application/x-www-form-urlencoded, а также на то как мы передаём публичный ключ.

Теперь проверяем $arrayInfo['http_code']. Документация Stripe говорит, что:

  • 200 — всё, хорошо, можно продолжать;
  • 4xx — мы передаём что-то не то. Неправильный номер карты, даты, CVC и т.д.
  • 5xx — косяк на сервере Stripe.

Про ошибки инфа тут.

Если мы получаем код 4xx, то в $response будет JSON следующего вида:

{
    "error": {
        "code": "invalid_cvc",
        "doc_url": "https://stripe.com/docs/error-codes/invalid-cvc",
        "message": "Your card's security code is invalid.",
        "param": "cvc",
        "type": "card_error"
    }
}

Если мы получаем код 200, то в $response будет такой JSON:

{
    "id": "tok_1Chh58AGwED8fJ9ZZ66a5N1V",
    "object": "token",
    "card": {
        "id": "card_1Chh58AGwED8fJ9ZkT7ccBph",
        "object": "card",
        "address_city": null,
        "address_country": null,
        "address_line1": null,
        "address_line1_check": null,
        "address_line2": null,
        "address_state": null,
        "address_zip": null,
        "address_zip_check": null,
        "brand": "Visa",
        "country": "US",
        "cvc_check": "unchecked",
        "dynamic_last4": null,
        "exp_month": 12,
        "exp_year": 2019,
        "fingerprint": "IKhrSWlsg0WBp9N1",
        "funding": "credit",
        "last4": "4242",
        "metadata": {},
        "name": null,
        "tokenization_method": null
    },
    "client_ip": "178.68.54.197",
    "created": 1530119126,
    "livemode": false,
    "type": "card",
    "used": false
}

То, что хранится в начале этого JSON, а именно в "id" — это и есть наш искомый токен.

Документация по сущности TOKENS тут.

2. Запрос на оплату (сущность CHARGES)

<?
$publicKey = 'pk_test_....';

$curl = curl_init();

$arrayPost = array(
    'source' => $token,
    'amount' => 1000,
    'currency' => 'usd',
    'description' => 'описание заказа',
);

curl_setopt_array($curl, array(
    CURLOPT_URL => "https://api.stripe.com/v1/charges",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => http_build_query($arrayPost),
    CURLOPT_HTTPHEADER => array(
        'Authorization: Bearer '.$publicKey,
        'Content-Type: application/x-www-form-urlencoded',
        'Cache-Control: no-cache',
    ),
));

$response = curl_exec($curl);
$arrayInfo = curl_getinfo($curl);
curl_close($curl);

Обратите внимание, что значение amount в POST, т.е. сумма платежа — это положительное целое число, указывающее сумму в наименьшей единице валюты (центы, евроценты, пенни, копейки и пр.). В примере amount  = 1000 означает 10USD.

Что касается принимаемых валют, то в июне 2018 Stripe поддерживал более 130 валют (список тут).

При успешном запросе $response будет хранить JSON вида:

{
    "id": "ch_1ChdO6AGwED8fJ9ZkX607gb9",
    "object": "charge",
    "amount": 1000,
    "amount_refunded": 0,
    "application": null,
    "application_fee": null,
    "balance_transaction": "txn_1ChdO6AGwED8fJ9ZxlRfB8Rr",
    "captured": true,
    "created": 1530104926,
    "currency": "usd",
    "customer": null,
    "description": "Order #21",
    "destination": null,
    "dispute": null,
    "failure_code": null,
    "failure_message": null,
    "fraud_details": {},
    "invoice": null,
    "livemode": false,
    "metadata": {},
    "on_behalf_of": null,
    "order": null,
    "outcome": {
        "network_status": "approved_by_network",
        "reason": null,
        "risk_level": "normal",
        "seller_message": "Payment complete.",
        "type": "authorized"
    },
    "paid": true,
    "receipt_email": null,
    "receipt_number": null,
    "refunded": false,
    "refunds": {
        "object": "list",
        "data": [],
        "has_more": false,
        "total_count": 0,
        "url": "/v1/charges/ch_1ChdO6AGwED8fJ9ZkX607gb9/refunds"
    },
    "review": null,
    "shipping": null,
    "source": {
        "id": "card_1ChdNuAGwED8fJ9ZzBWSjSrT",
        "object": "card",
        "address_city": null,
        "address_country": null,
        "address_line1": null,
        "address_line1_check": null,
        "address_line2": null,
        "address_state": null,
        "address_zip": null,
        "address_zip_check": null,
        "brand": "Visa",
        "country": "US",
        "customer": null,
        "cvc_check": "pass",
        "dynamic_last4": null,
        "exp_month": 12,
        "exp_year": 2019,
        "fingerprint": "IKhrSWlsg0WBp9N1",
        "funding": "credit",
        "last4": "4242",
        "metadata": {},
        "name": null,
        "tokenization_method": null
    },
    "source_transfer": null,
    "statement_descriptor": null,
    "status": "succeeded",
    "transfer_group": null
}

В случае неуспеха — что-то типа:

{
    "error": {
        "code": "token_already_used",
        "doc_url": "https://stripe.com/docs/error-codes/token-already-used",
        "message": "You cannot use a Stripe token more than once: tok_1ChdNvAGwED8fJ9ZWGT8BEcy.",
        "type": "invalid_request_error"
    }
}

Это и есть тот нативный минимум, позволяющий принять отплату через банковские карты.

Документация по сущности CHARGES тут.

И это ваш хвалёный Stripe?!

На самом деле возможности платформы значительно шире. Выше мы рассмотрели всего две сущности (TOKEN и CHARGES), в действительности есть возможность работать с балансом (сущность BALANCE), покупателями (CUSTOMERS), спорами (DISPUTES), событиями (EVENTS), файлами (FILE UPLOADS), выводом средств (PAYOUTS), товарами (PRODUCTS) и возвратами (REFUNDS).

И всё это только по продукту Stripe Payments. А есть ещё Stripe Billing, позволяющий принимать регулярные платежи и Stripe Connect, позволяющий делать выплаты третьим лицам.