Stripe PHP SDK (stripe/stripe-php) оборачивает REST API Stripe в типизированные PHP-классы: аутентификация, сериализация, retry на транзиентных ошибках и десериализация ответов в объекты, к которым можно обращаться через ->. Официальная документация описывает каждый метод отдельно, но не объясняет практические вещи: когда делать expand вложенного объекта, а когда запрашивать отдельно; как ведёт себя пагинация на больших выборках; что скрывается внутри объекта ответа. Об этом здесь.
Если SDK ещё не установлен, гайд по интеграции платежей покрывает Composer, API-ключи и первый PaymentIntent. Всё ниже предполагает, что SDK подключён.
StripeClient vs Stripe::setApiKey
В SDK два способа вызывать API. Смешивать их в одном проекте не стоит.
Глобальный статический (legacy):
\Stripe\Stripe::setApiKey('sk_test_...');
$customer = \Stripe\Customer::create(['email' => '[email protected]']);
$intent = \Stripe\PaymentIntent::retrieve('pi_abc123');
Через экземпляр клиента (рекомендуется с v7.33):
$stripe = new \Stripe\StripeClient('sk_test_...');
$customer = $stripe->customers->create(['email' => '[email protected]']);
$intent = $stripe->paymentIntents->retrieve('pi_abc123');
Разница не в синтаксисе. setApiKey сохраняет ключ в статическом свойстве. Любой код в том же процессе использует тот же ключ: тесты, очереди, воркеры для разных Stripe-аккаунтов. Один воркер перезаписал ключ, следующий отправил запрос с чужими credentials и получил “No such payment_intent” без подсказки, что виноват ключ.
StripeClient хранит ключ на экземпляре. Два экземпляра с разными ключами спокойно живут в одном процессе. Для Laravel queue worker, обрабатывающего задачи Connect-аккаунтов, это единственный способ избежать утечки данных между аккаунтами.
// Connect: работа от лица подключённого аккаунта
$stripe = new \Stripe\StripeClient([
'api_key' => 'sk_test_platform_key',
'stripe_account' => 'acct_connected_123',
]);
Для нового кода всегда используйте StripeClient. Статический паттерн работает, дату удаления Stripe не объявлял, но новые endpoint-ы API поддерживаются только через сервисные методы на клиенте. Миграция с legacy занимает минуты: создать экземпляр клиента, заменить \Stripe\Customer::create( на $stripe->customers->create( по всему проекту. Массивы аргументов не меняются.
Аутентификация и API-ключи
Stripe выдаёт две пары ключей: для тестового и для живого режима.
| Префикс | Назначение | Где используется |
|---|---|---|
sk_test_ | Секретный ключ, тест | Только сервер, env-переменная |
sk_live_ | Секретный ключ, production | Только сервер, env-переменная |
pk_test_ | Публичный ключ, тест | Браузер (Stripe.js) |
pk_live_ | Публичный ключ, production | Браузер (Stripe.js) |
Секретные ключи аутентифицируют все серверные вызовы. Публичные безопасно отдавать в клиентский JavaScript: через них можно только подтверждать платежи и создавать токены, но не читать или записывать данные аккаунта.
Истекают ли API-ключи Stripe? Нет. Ключи живут, пока вы не откатите (roll) их вручную. Rollover генерирует новый ключ с grace-окном (24 часа для секретных ключей, настраивается), в течение которого работают и старый, и новый. После окна старый ключ перестаёт принимать запросы. Управление ключами доступно в Dashboard: Developers > API keys.
Restricted keys – третий тип: вы создаёте их в Dashboard с конкретными правами (читать charges, писать customers, больше ничего). Подходят для микросервисов и интеграций с третьими сторонами, где полный секретный ключ – слишком широкий доступ.
Ключи вне кода
Правило одно: ключи в переменных окружения, не в PHP-файлах. Утечка sk_live_ даёт полный доступ к Stripe-аккаунту.
// Правильно: из env при старте
$stripe = new \Stripe\StripeClient(getenv('STRIPE_SECRET_KEY'));
// Плохо: захардкожено, попадёт в git
$stripe = new \Stripe\StripeClient('sk_live_actualKeyHere');
В Laravel ключи хранятся в .env и вытаскиваются через config/services.php:
// config/services.php
'stripe' => [
'secret' => env('STRIPE_SECRET_KEY'),
],
// в любом месте приложения
$stripe = new \Stripe\StripeClient(config('services.stripe.secret'));
Полный Service Provider с singleton-биндингом и подменой в тестах описан в гайде по Laravel.
Паттерн вызовов
Все ресурсы SDK устроены одинаково. Методы соответствуют HTTP-глаголам:
| Метод SDK | HTTP | Пример |
|---|---|---|
->create([...]) | POST | Создать Customer |
->retrieve('id') | GET | Получить PaymentIntent |
->update('id', [...]) | POST | Обновить Subscription |
->delete('id') | DELETE | Удалить купон |
->all([...]) | GET | Список charges |
Первый аргумент retrieve, update и delete – всегда ID объекта строкой. create и all принимают ассоциативный массив параметров. Эта сигнатура единообразна для всех ресурсов:
$stripe = new \Stripe\StripeClient('sk_test_...');
// Создание
$customer = $stripe->customers->create([
'email' => '[email protected]',
'name' => 'Jane Doe',
'metadata' => ['internal_id' => '42'],
]);
// Получение
$customer = $stripe->customers->retrieve('cus_abc123');
// Обновление
$stripe->customers->update('cus_abc123', [
'name' => 'Jane Smith',
]);
// Удаление
$stripe->customers->delete('cus_abc123');
// Список
$customers = $stripe->customers->all(['limit' => 10]);
Дополнительные параметры
retrieve принимает ID и опциональный массив параметров API (например, expand):
$intent = $stripe->paymentIntents->retrieve(
'pi_abc123',
['expand' => ['customer', 'payment_method']]
);
update принимает ID, массив изменяемых полей, и опционально третий массив для request-level опций (idempotency key, Stripe-Account header). expand – обычный параметр API, он идёт в массив данных:
$stripe->paymentIntents->update('pi_abc123', [
'metadata' => ['order_id' => '99'],
'expand' => ['customer'],
]);
// Request-level опции идут в третий аргумент
$stripe->paymentIntents->update(
'pi_abc123',
['metadata' => ['order_id' => '99']],
['idempotency_key' => 'update_pi_abc123_v2']
);
Expand: раскрытие вложенных объектов
По умолчанию API возвращает связанные объекты как голые ID. Поле customer в PaymentIntent придёт как строка "cus_abc123", а не полный объект Customer. Параметр expand указывает Stripe подставить полный объект прямо в ответ, чтобы не делать второй запрос.
$intent = $stripe->paymentIntents->retrieve('pi_abc123', [
'expand' => ['customer', 'payment_method'],
]);
// Теперь это полные объекты, не строки
echo $intent->customer->email;
echo $intent->payment_method->card->last4;
Ограничения
Максимальная глубина вложенности – четыре уровня, и не больше 10 expand-ов на запрос. Путь через точку разворачивает цепочку:
$charge = $stripe->charges->retrieve('ch_abc', [
'expand' => ['payment_intent.customer.default_source'],
]);
Здесь разворачивается PaymentIntent внутри Charge, Customer внутри PaymentIntent, и default source на Customer. Глубже четырёх уровней или больше 10 полей в одном запросе – ошибка.
Expand в списках
При запросе списка к путям expand-а добавляется префикс data.:
$charges = $stripe->charges->all([
'limit' => 50,
'expand' => ['data.customer', 'data.balance_transaction'],
]);
foreach ($charges->data as $charge) {
echo $charge->customer->email;
echo $charge->balance_transaction->fee;
}
Каждый expand в списке умножает объём ответа и время. Stripe внутренне запрашивает каждый развёрнутый объект. Если из связанного объекта нужно одно поле на 100 записей, стоит прикинуть, не быстрее ли отдельный целевой запрос.
Expand при создании
Expand работает и на create. Полезно, когда нужно сразу получить автоматически созданный вложенный объект:
$session = $stripe->checkout->sessions->create([
'mode' => 'payment',
'line_items' => [['price' => 'price_abc', 'quantity' => 1]],
'success_url' => 'https://example.com/thanks',
'expand' => ['payment_intent'],
]);
// payment_intent – полный объект, не строка pi_xxx
echo $session->payment_intent->status;
Пагинация
Списочные endpoint-ы отдают максимум 100 объектов за вызов (по умолчанию 10). Stripe использует курсорную пагинацию: передаёте ID последнего полученного объекта, API возвращает следующую страницу.
Ручная пагинация
$hasMore = true;
$startingAfter = null;
while ($hasMore) {
$params = ['limit' => 100];
if (null !== $startingAfter) {
$params['starting_after'] = $startingAfter;
}
$charges = $stripe->charges->all($params);
foreach ($charges->data as $charge) {
processCharge($charge);
}
$hasMore = $charges->has_more;
if ($charges->data) {
$startingAfter = end($charges->data)->id;
}
}
Автопагинация
SDK предоставляет autoPagingIterator(), который берёт логику курсора на себя:
$charges = $stripe->charges->all(['limit' => 100]);
foreach ($charges->autoPagingIterator() as $charge) {
// Итерирует через ВСЕ charges, подгружая новые страницы автоматически
processCharge($charge);
}
autoPagingIterator делает новый API-вызов, когда текущая страница заканчивается. Параметр limit контролирует размер страницы, а не общее количество. С limit => 100 каждый HTTP-запрос забирает 100 объектов; итератор продолжает, пока has_more не станет false.
Встроенного способа остановиться после N объектов нет. Если нужны первые 500 charges, поставьте счётчик:
$count = 0;
foreach ($charges->autoPagingIterator() as $charge) {
processCharge($charge);
if (++$count >= 500) break;
}
Фильтрация списков
Большинство списочных endpoint-ов принимают фильтры, сужающие выборку на стороне сервера. Фильтровать до пагинации всегда лучше, чем тянуть всё и фильтровать в PHP:
// Charges за последние 7 дней
$charges = $stripe->charges->all([
'limit' => 100,
'created' => ['gte' => strtotime('-7 days')],
]);
// Подписки конкретного клиента
$subs = $stripe->subscriptions->all([
'customer' => 'cus_abc123',
'status' => 'active',
'limit' => 100,
]);
Фильтр created принимает ключи gt, gte, lt, lte с Unix-таймстампами.
Объекты ответов
Каждый ответ API приходит как StripeObject (или подкласс: Customer, PaymentIntent и т.д.). Эти объекты ведут себя как гибрид: к полям можно обращаться и через свойства ($customer->email), и через ключи массива ($customer['email']), но это ни обычный объект, ни обычный массив.
Конвертация в JSON
json_encode($stripeObject) работает: объект реализует JsonSerializable и возвращает данные ответа. Для явной конвертации:
$customer = $stripe->customers->retrieve('cus_abc123');
// Работает: сериализует поля данных объекта
$json = json_encode($customer);
// Надёжнее: сперва toArray(), потом encode
$array = $customer->toArray();
$json = json_encode($array, JSON_PRETTY_PRINT);
toArray() возвращает чистый ассоциативный массив без внутренностей SDK. Для сохранения ответа Stripe в базу или кеш используйте toArray() и json_encode на результат.
Проверка null-полей
Stripe возвращает null для незаданных полей. SDK это сохраняет, поэтому $customer->phone будет null, а не undefined. Используйте null coalescing:
$phone = $customer->phone ?? 'телефон не указан';
Свойство lastResponse
Каждый StripeObject содержит lastResponse – сырой HTTP-ответ от API-вызова:
$customer = $stripe->customers->retrieve('cus_abc123');
$httpStatus = $customer->lastResponse->code; // 200
$requestId = $customer->lastResponse->headers['Request-Id'];
$rawBody = $customer->lastResponse->body; // JSON-строка
Request-Id нужен при обращении в Stripe support. Логируйте его при каждом API-вызове в production – это первое, что у вас спросят в тикете.
Тестовые карты и токены
Тестовый режим – полноценная песочница. Деньги не списываются, карточные сети не участвуют. Но тестовые объекты – реальные API-объекты с реальными ID, и все функции работают так же, как в production: webhooks, 3DS, диспуты, возвраты.
Номера карт для Stripe.js / Elements
Когда фронтенд собирает данные карты через Stripe.js:
| Номер | Бренд | Поведение |
|---|---|---|
4242 4242 4242 4242 | Visa | Успех |
5555 5555 5555 4444 | Mastercard | Успех |
3782 822463 10005 | Amex | Успех |
4000 0025 0000 3155 | Visa | Требует 3DS-аутентификации |
4000 0000 0000 9995 | Visa | Отклонена: insufficient_funds |
4000 0000 0000 0002 | Visa | Отклонена: generic_decline |
4000 0000 0000 0069 | Visa | Отклонена: expired_card |
4000 0000 0000 0127 | Visa | Отклонена: incorrect_cvc |
Дата истечения – любая будущая, CVC – любые 3 цифры (4 для Amex).
Серверные тестовые токены
Для тестирования серверной логики без фронтенда можно подставлять готовые тестовые PaymentMethod:
$stripe = new \Stripe\StripeClient('sk_test_...');
$intent = $stripe->paymentIntents->create([
'amount' => 2000,
'currency' => 'usd',
'payment_method' => 'pm_card_visa',
'confirm' => true,
'automatic_payment_methods' => [
'enabled' => true,
'allow_redirects' => 'never',
],
]);
echo $intent->status; // "succeeded"
Токены pm_card_* пропускают шаг со Stripe.js. Полезны для интеграционных тестов, проверки webhook-пайплайна и CI.
| Токен | Что симулирует |
|---|---|
pm_card_visa | Успешный платёж Visa |
pm_card_mastercard | Успешный платёж Mastercard |
pm_card_visa_debit | Дебетовая Visa |
pm_card_chargeDeclined | generic_decline |
pm_card_chargeDeclinedInsufficientFunds | insufficient_funds |
pm_card_chargeDeclinedExpiredCard | expired_card |
pm_card_authenticationRequired | Требует 3DS |
Устаревший формат tok_visa / tok_chargeDeclined до сих пор работает через внутреннюю конвертацию, но pm_card_* – актуальная рекомендация. Используйте pm_card_* для PaymentIntents; tok_* – только если приходится работать с legacy Charges API.
Полный справочник по decline-кодам и обработке каждого из них – в гайде по ошибкам.
Версионирование API
API Stripe развивается через датированные версии вида 2026-03-25.dahlia. Каждая мажорная версия может менять формат ответов, переименовывать поля или менять дефолтное поведение. Код привязан к конкретной версии, и Stripe гарантирует обратную совместимость внутри неё.
Как SDK пиннит версию
SDK пиннит версию API к той, которая была актуальной на момент сборки конкретного релиза. У аккаунта в Dashboard своя дефолтная версия. Запросы через SDK используют версию SDK, не Dashboard.
// Узнать версию, которую использует SDK
echo \Stripe\Stripe::getApiVersion();
// например: "2026-03-25.dahlia"
Переопределение версии
Можно задать конкретную версию на уровне клиента или отдельного запроса:
// На уровне клиента
$stripe = new \Stripe\StripeClient([
'api_key' => 'sk_test_...',
'stripe_version' => '2024-12-18.acacia',
]);
// На уровне запроса (статический паттерн)
\Stripe\Stripe::setApiKey('sk_test_...');
$customer = \Stripe\Customer::create(
['email' => '[email protected]'],
['stripe_version' => '2024-12-18.acacia']
);
Переопределяйте осторожно. Один вызов на другой версии – и формат ответа отличается от остального кода. Хороший источник трудно отлавливаемых багов.
Webhooks и версии
Webhook-события приходят в формате API-версии, сконфигурированной для endpoint-а в Dashboard, а не в версии SDK. Если endpoint настроен на версию X, а SDK ожидает версию Y, структура события может не совпасть с тем, что ожидает код.
Держите их синхронизированными: после обновления SDK и проверки поднимите версию webhook endpoint-а в Dashboard. В гайде по webhooks там же про верификацию подписи.
Идемпотентность
Stripe поддерживает ключи идемпотентности на всех POST-запросах. Передайте уникальный ключ, и если тот же запрос сработает дважды (повтор из-за сети, двойной клик, повторная доставка webhook), Stripe вернёт прежний результат без создания дубликата.
$stripe = new \Stripe\StripeClient('sk_test_...');
$intent = $stripe->paymentIntents->create([
'amount' => 5000,
'currency' => 'usd',
], [
'idempotency_key' => 'order_42_payment_attempt_1',
]);
Ключ привязан к запросу. Stripe хранит результат 24 часа: повторный запрос с тем же ключом и теми же параметрами возвращает закэшированный ответ. Тот же ключ, но другие параметры – ошибка 400.
Хороший ключ: доменный идентификатор плюс намерение. order_{id}_payment_{attempt} работает. UUID тоже годится, но его сложнее отлаживать. Таймстамп один – плохо: два запроса в одну миллисекунду с одинаковым таймстампом создадут ту самую проблему, от которой идемпотентность должна защищать.
Где идемпотентность важнее всего
- Создание PaymentIntent. Сеть оборвалась после того, как Stripe обработал запрос, но до того, как ваш сервер получил ответ. Без ключа повтор создаст второй платёж.
- Возвраты. Та же ситуация. Двойной возврат хуже двойного списания: клиент не пожалуется, а вы не заметите до сверки.
- Webhooks. Handler должен быть идемпотентным по дизайну (проверить, не помечен ли заказ уже оплаченным, прежде чем помечать). Ключ идемпотентности защищает исходящую сторону; идемпотентность handler-а – входящую.
Иерархия исключений
SDK бросает типизированные исключения на каждую ошибку API. Дерево классов:
\Stripe\Exception\ApiErrorException (абстрактный базовый)
├── CardException // 402 – карта отклонена, insufficient_funds и т.д.
├── InvalidRequestException // 400 – пропущены параметры, неверный ID, не тот режим
├── AuthenticationException // 401 – неверный API-ключ
├── ApiConnectionException // сетевая ошибка, таймаут, DNS
├── IdempotencyException // конфликт ключа идемпотентности
├── PermissionException // 403 – restricted key, недостаточно прав
├── RateLimitException // 429 – слишком много запросов
├── UnknownApiErrorException // всё остальное от API
└── SignatureVerificationException // несовпадение подписи webhook (не подкласс)
SignatureVerificationException не наследует ApiErrorException, потому что возникает при локальной проверке подписи, а не при API-вызове. Если ловите ApiErrorException как catch-all, SignatureVerificationException проскользнёт мимо.
Порядок catch-блоков важен. CardException наследует ApiErrorException, поэтому если ApiErrorException стоит первым, блок CardException никогда не выполнится:
try {
$intent = $stripe->paymentIntents->create([...]);
} catch (\Stripe\Exception\CardException $e) {
// Должен стоять ДО ApiErrorException
$decline = $e->getError()->decline_code;
log("Карта отклонена: {$decline}");
} catch (\Stripe\Exception\RateLimitException $e) {
// 429: отступить и повторить
sleep(2);
} catch (\Stripe\Exception\InvalidRequestException $e) {
// Неверные параметры, не тот режим, пропущены поля
log("Неверный запрос: " . $e->getMessage());
} catch (\Stripe\Exception\ApiErrorException $e) {
// Catch-all для остального от API
log("Ошибка Stripe: " . $e->getMessage());
}
Полный разбор типов ошибок, decline-кодов и стратегий восстановления – в гайде по ошибкам.
Metadata
Каждый крупный объект Stripe принимает metadata: до 50 ключей, ключ до 40 символов, значение до 500 символов. Metadata – связующее звено между миром Stripe и вашим приложением.
$intent = $stripe->paymentIntents->create([
'amount' => 7500,
'currency' => 'eur',
'metadata' => [
'order_id' => '1042',
'campaign' => 'summer_sale',
'internal_id' => 'usr_887',
],
]);
Metadata путешествует с объектом по всему жизненному циклу. Когда PaymentIntent завершится и прилетит webhook, $event->data->object->metadata->order_id вернёт "1042". Так webhook-handler понимает, какой заказ помечать оплаченным, без запросов к базе по сумме или времени.
Metadata не копируется между объектами
На этом спотыкаются регулярно. Metadata на Checkout Session не копируется автоматически в созданный ею PaymentIntent. Если webhook-handler слушает payment_intent.succeeded и читает metadata там, массив окажется пустым – если не заполнить payment_intent_data.metadata при создании Session:
$session = $stripe->checkout->sessions->create([
'mode' => 'payment',
'line_items' => [['price' => 'price_abc', 'quantity' => 1]],
'success_url' => 'https://example.com/thanks',
'metadata' => ['order_id' => '42'], // живёт на Session
'payment_intent_data' => [
'metadata' => ['order_id' => '42'], // живёт на PaymentIntent
],
]);
Или не дублировать, а слушать checkout.session.completed вместо payment_intent.succeeded. Подробнее – в гайде по Checkout.
Логирование и отладка
Request-Id
Каждый ответ API содержит заголовок Request-Id. Логируйте его:
$customer = $stripe->customers->create(['email' => '[email protected]']);
$requestId = $customer->lastResponse->headers['Request-Id'];
error_log("Stripe request: {$requestId}");
Когда в production что-то ломается и вы открываете тикет в Stripe support, первое, о чём спросят – Request-Id. Без него отладка по обе стороны превращается в угадывание.
Логирование на уровне SDK
SDK может логировать каждый HTTP-запрос и ответ. Он принимает PSR-3 логгер (пакет psr/log; большинство фреймворков его уже подтягивают, для standalone-скриптов нужен composer require psr/log). В dev-окружении прикрепите логгер для трассировки:
\Stripe\Stripe::setLogger(new class extends \Psr\Log\AbstractLogger {
public function log($level, \Stringable|string $message, array $context = []): void
{
error_log("[Stripe {$level}] {$message}");
}
});
В production выставьте уровень error или отключите логирование полностью. Полный лог запросов/ответов содержит fingerprint-ы карт и email-ы клиентов – данные, которым не место в лог-файле.
Частые паттерны
Проверка существования клиента перед созданием
$existing = $stripe->customers->search([
'query' => "email:'[email protected]'",
]);
if ($existing->data) {
$customer = $existing->data[0];
} else {
$customer = $stripe->customers->create([
'email' => '[email protected]',
]);
}
Search API – eventually consistent. Клиент, созданный секунду назад, может не появиться в результатах поиска. Для real-time проверок храните Stripe customer ID в своей базе и запрашивайте по ID.
Обновление платёжного метода по умолчанию
$stripe->customers->update('cus_abc123', [
'invoice_settings' => [
'default_payment_method' => 'pm_newCard456',
],
]);
Старое поле default_source работает для legacy-интеграций, но invoice_settings.default_payment_method – актуальный путь. Подписки и инвойсы берут карту оттуда.
Получение charge с разбивкой комиссии
$charge = $stripe->charges->retrieve('ch_abc', [
'expand' => ['balance_transaction'],
]);
$fee = $charge->balance_transaction->fee; // комиссия в центах
$net = $charge->balance_transaction->net; // сумма минус комиссии
$details = $charge->balance_transaction->fee_details; // детализация: stripe fee, tax и т.д.
Комиссия Stripe живёт в balance_transaction, а не на Charge или PaymentIntent напрямую. Логика расчёта комиссий подробнее описана в гайде по тарифам.
Поддерживает ли Stripe PHP?
Да. stripe/stripe-php – SDK от самого Stripe, а не community-обёртка. Обновления выходят в течение дней после изменений API, SDK предоставляет типизированные исключения, автопагинацию и десериализацию ответов. Установка через Composer (composer require stripe/stripe-php), подключение vendor/autoload.php, и все классы пространства \Stripe доступны. Если Composer не вариант (shared hosting, legacy), SDK включает загрузчик init.php, который подменяет autoload. Оба пути описаны в гайде по интеграции платежей.
Что такое Stripe SDK?
SDK (Software Development Kit) – PHP-библиотека, которая берёт на себя HTTP-обвязку общения с REST API Stripe. Вместо ручных curl-запросов, настройки заголовков, парсинга JSON и обработки ошибок, SDK даёт вызов вида $stripe->customers->create([...]) и возвращает типизированный объект. Внутри всё равно HTTPS, но SDK управляет аутентификацией, сериализацией, retry и обработкой ошибок.
Stripe публикует SDK для PHP, Python, Ruby, Node.js, Java, Go и .NET. PHP SDK поддерживается самим Stripe, не сообществом. Причина, по которой API Stripe ценят разработчики – единообразие: каждый ресурс следует одному CRUD-паттерну, каждая ошибка возвращает структурированный JSON с машиночитаемым кодом, а тестовый режим – полная реплика production. SDK наследует это единообразие и добавляет PHP-удобства сверху.
Открыт ли исходный код Stripe API?
Сам API проприетарный, но PHP SDK открыт под MIT-лицензией. Исходный код на github.com/stripe/stripe-php: можно читать каждую строку, заводить issue, отправлять pull request. SDK – это HTTP-клиент; API за ним – закрытый сервис.
Что дальше
- Первый платёж с нуля: интеграция Stripe в PHP
- Готовые формы оплаты: Stripe Checkout Sessions
- Обработка событий: Stripe webhooks в PHP
- Регулярные платежи: подписки Stripe в PHP
- Обработка ошибок и decline-коды: ошибки Stripe в PHP
- Расчёт комиссий в коде: тарифы и комиссии Stripe
- SDK внутри Laravel: Stripe в Laravel
Нашли неточность на этой странице?
Сообщить об ошибке