1. Пакеты
  2. Laravel Cashier (Stripe)

Присоединяйся к нашему Telegram сообществу @webblend!

Здесь ты найдешь сниппеты по Laravel и полезные советы по веб-разработке.

Введение

Laravel Cashier Stripe предоставляет выразительный, плавный интерфейс для Stripe's услуг подписочного выставления счетов. Он обрабатывает практически весь шаблонный код для выставления счетов за подписку, который вы не хотите писать. Помимо основного управления подписками, Cashier может обрабатывать купоны, замену подписок, "количество" подписок, периоды отсрочки отмены и даже создавать PDF-счета.

Обновление Cashier

При обновлении до новой версии Cashier важно тщательно ознакомиться с руководством по обновлению.

Внимание Чтобы избежать изменений, нарушающих обратную совместимость, Cashier использует фиксированную версию Stripe API. Cashier 14 использует версию Stripe API 2022-11-15. Версия Stripe API будет обновляться в минорных выпусках для использования новых функций и улучшений Stripe.

Установка

Сначала установите пакет Cashier для Stripe с помощью менеджера пакетов Composer:

composer require laravel/cashier

Внимание Чтобы Cashier правильно обрабатывал все события Stripe, не забудьте настроить обработку вебхуков Cashier.

Миграции базы данных

Сервис-провайдер Cashier регистрирует свой собственный каталог миграций в базе данных, поэтому не забудьте выполнить миграцию базы данных после установки пакета. Миграции Cashier добавят несколько столбцов в вашу таблицу users. Они также создадут новую таблицу subscriptions для хранения всех подписок вашего клиента и таблицу subscription_items для подписок с несколькими ценами:

php artisan migrate

Если вам нужно перезаписать миграции, поставляемые с Cashier, вы можете их опубликовать, используя команду Artisan vendor:publish:

php artisan vendor:publish --tag="cashier-migrations"

Если вы хотите полностью предотвратить выполнение миграций Cashier, вы можете использовать метод ignoreMigrations, предоставленный Cashier. Обычно этот метод следует вызывать в методе register вашего AppServiceProvider:

use Laravel\Cashier\Cashier;
 
/**
* Регистрация любых служб приложения.
*/
public function register(): void
{
Cashier::ignoreMigrations();
}

Внимание Stripe рекомендует, чтобы любой столбец, используемый для хранения идентификаторов Stripe, был чувствителен к регистру. Поэтому вы должны убедиться, что сортировка столбца stripe_id установлена в utf8_bin при использовании MySQL. Дополнительную информацию можно найти в документации Stripe.

Настройка

Платежная модель

Прежде чем использовать Cashier, добавьте трейт Billable к определению вашей модели, поддерживающей выставление счетов. Обычно это будет модель App\Models\User. Этот трейт предоставляет различные методы, позволяющие выполнять общие задачи по выставлению счетов, такие как создание подписок, применение купонов и обновление информации о платежном методе:

use Laravel\Cashier\Billable;
 
class User extends Authenticatable
{
use Billable;
}

Cashier предполагает, что вашей моделью для выставления счетов будет класс App\Models\User, который поставляется с Laravel. Если вы хотите изменить это, вы можете указать другую модель с помощью метода useCustomerModel. Обычно этот метод следует вызывать в методе boot вашего класса AppServiceProvider:

use App\Models\Cashier\User;
use Laravel\Cashier\Cashier;
 
/**
* Инициализация любых служб приложения.
*/
public function boot(): void
{
Cashier::useCustomerModel(User::class);
}

Внимание Если вы используете модель, отличную от предоставленной Laravel модели App\Models\User, вам нужно опубликовать и изменить миграции Cashier, предоставленные, чтобы они соответствовали названию таблицы вашей альтернативной модели.

API-ключи

Затем вам следует настроить ключи API Stripe в файле .env вашего приложения. Вы можете получить ключи API Stripe из панели управления Stripe:

STRIPE_KEY=your-stripe-key
STRIPE_SECRET=your-stripe-secret
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret

Внимание Убедитесь, что переменная окружения STRIPE_WEBHOOK_SECRET определена в файле .env вашего приложения, так как эта переменная используется для обеспечения того, что входящие вебхуки действительно от Stripe.

Настройка валюты

Валютой Cashier по умолчанию являются доллары США. Вы можете изменить валюту по умолчанию, установив переменную окружения CASHIER_CURRENCY в файле .env вашего приложения:

CASHIER_CURRENCY=eur

Помимо настройки валюты Cashier, вы также можете указать локаль для использования при форматировании денежных значений для отображения на счетах. Внутренне Cashier использует класс NumberFormatter PHP для установки локали валюты:

CASHIER_CURRENCY_LOCALE=nl_BE

Внимание Чтобы использовать локали, отличные от en, убедитесь, что расширение PHP ext-intl установлено и настроено на вашем сервере.

Настройка налогов

Благодаря Stripe Tax можно автоматически рассчитывать налоги для всех счетов, созданных Stripe. Вы можете включить автоматический расчет налогов, вызвав метод calculateTaxes в методе boot класса App\Providers\AppServiceProvider вашего приложения:

use Laravel\Cashier\Cashier;
 
/**
* Инициализация любых служб приложения.
*/
public function boot(): void
{
Cashier::calculateTaxes();
}

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

Для правильной работы этой функции данные о биллинге вашего клиента, такие как имя клиента, адрес и налоговый идентификационный номер, должны быть синхронизированы с Stripe. Вы можете использовать синхронизацию данных о клиенте и Tax ID методы, предлагаемые Cashier, чтобы выполнить это.

Внимание Для одноразовых платежей или одноразовых проверок оплаты не рассчитывается налог.

Логирование

Cashier позволяет вам указать канал журнала, который следует использовать при регистрации фатальных ошибок Stripe. Вы можете указать канал журнала, определив переменную окружения CASHIER_LOGGER в файле .env вашего приложения:

CASHIER_LOGGER=stack

Исключения, генерируемые при вызовах API Stripe, будут зарегистрированы через канал журнала по умолчанию вашего приложения.

Использование собственных моделей

Вы можете расширить модели, используемые внутренне Cashier, определив свою собственную модель и расширив соответствующую модель Cashier:

use Laravel\Cashier\Subscription as CashierSubscription;
 
class Subscription extends CashierSubscription
{
// ...
}

После определения вашей модели вы можете указать Cashier использовать вашу собственную модель через класс Laravel\Cashier\Cashier. Обычно вы должны сообщить Cashier о ваших собственных моделях в методе boot класса App\Providers\AppServiceProvider вашего приложения:

use App\Models\Cashier\Subscription;
use App\Models\Cashier\SubscriptionItem;
 
/**
* Инициализация любых служб приложения.
*/
public function boot(): void
{
Cashier::useSubscriptionModel(Subscription::class);
Cashier::useSubscriptionItemModel(SubscriptionItem::class);
}

Клиенты

Получение клиентов

Вы можете получить клиента по его Stripe ID с использованием метода Cashier::findBillable. Этот метод вернет экземпляр модели billable:

use Laravel\Cashier\Cashier;
 
$user = Cashier::findBillable($stripeId);

Создание клиентов

Иногда вам может потребоваться создать клиента Stripe без начала подписки. Вы можете сделать это с помощью метода createAsStripeCustomer:

$stripeCustomer = $user->createAsStripeCustomer();

После создания клиента в Stripe вы можете начать подписку в будущем. Вы можете предоставить дополнительный необязательный массив $options для передачи любых дополнительных параметров создания клиента, поддерживаемых Stripe API:

$stripeCustomer = $user->createAsStripeCustomer($options);

Если вы хотите вернуть объект клиента Stripe для модели billable, вы можете использовать метод asStripeCustomer:

$stripeCustomer = $user->asStripeCustomer();

Метод createOrGetStripeCustomer можно использовать, если вы хотите получить объект клиента Stripe для данной модели billable, но не уверены, является ли модель billable уже клиентом в Stripe. Этот метод создаст нового клиента в Stripe, если его еще не существует:

$stripeCustomer = $user->createOrGetStripeCustomer();

Обновление клиентов

Иногда вам может потребоваться обновить клиента Stripe напрямую дополнительной информацией. Вы можете сделать это с помощью метода updateStripeCustomer. Этот метод принимает массив опций обновления клиента, поддерживаемых Stripe API:

$stripeCustomer = $user->updateStripeCustomer($options);

Балансы

Stripe позволяет начислять или уменьшать "баланс" клиента. Позже этот баланс будет начислен или уменьшен на новых счетах. Для проверки общего баланса клиента вы можете использовать метод balance, который доступен в вашей модели billable. Метод balance вернет отформатированную строковую репрезентацию баланса в валюте клиента:

$balance = $user->balance();

Чтобы начислить баланс клиента, вы можете предоставить значение методу creditBalance. Если вы хотите, вы также можете предоставить описание:

$user->creditBalance(500, 'Premium customer top-up.');

Предоставление значения методу debitBalance уменьшит баланс клиента:

$user->debitBalance(300, 'Bad usage penalty.');

Метод applyBalance создаст новые транзакции баланса клиента. Вы можете получить эти записи транзакций, используя метод balanceTransactions, что может быть полезно для предоставления журнала начислений и уменьшений для рассмотрения клиентом:

// Получение всех транзакций...
$transactions = $user->balanceTransactions();
 
foreach ($transactions as $transaction) {
// Сумма транзакции...
$amount = $transaction->amount(); // $2.31
 
// Получение связанного счета, если он доступен...
$invoice = $transaction->invoice();
}

Идентификаторы налогов

Cashier предлагает простой способ управления налоговыми идентификаторами клиента. Например, метод taxIds может использоваться для получения всех идентификаторов налога, назначенных клиенту, в виде коллекции:

$taxIds = $user->taxIds();

Вы также можете получить конкретный идентификатор налога для клиента по его идентификатору:

$taxId = $user->findTaxId('txi_belgium');

Вы можете создать новый идентификатор налога, указав допустимый тип и значение методу createTaxId:

$taxId = $user->createTaxId('eu_vat', 'BE0123456789');

Метод createTaxId сразу добавит идентификатор НДС на счет клиента. Проверка идентификаторов НДС также выполняется Stripe; однако это асинхронный процесс. Вы можете получать уведомления об обновлениях проверки, подписавшись на событие веб-хука customer.tax_id.updated и проверив параметр verification идентификаторов НДС. Для получения дополнительной информации о обработке веб-хуков обратитесь к документации по определению обработчиков веб-хуков.

Вы можете удалить идентификатор налога, используя метод deleteTaxId:

$user->deleteTaxId('txi_belgium');

Синхронизация данных клиента с Stripe

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

Для автоматизации этого процесса вы можете определить слушатель событий в вашей модели billable, который реагирует на событие updated модели. Затем внутри вашего слушателя событий вы можете вызвать метод syncStripeCustomerDetails на модели:

use App\Models\User;
use function Illuminate\Events\queueable;
 
/**
* Метод "booted" модели.
*/
protected static function booted(): void
{
static::updated(queueable(function (User $customer) {
if ($customer->hasStripeId()) {
$customer->syncStripeCustomerDetails();
}
}));
}

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

Вы можете настроить столбцы, используемые для синхронизации информации о клиенте с Stripe, переопределяя различные методы, предоставленные Cashier. Например, вы можете переопределить метод stripeName для настройки атрибута, который должен считаться "именем" клиента при синхронизации информации о клиенте с Stripe:

/**
* Получение имени клиента, которое должно синхронизироваться с Stripe.
*/
public function stripeName(): string|null
{
return $this->company_name;
}

Точно так же вы можете переопределить методы stripeEmail, stripePhone, stripeAddress и stripePreferredLocales. Эти методы будут синхронизировать информацию с соответствующими параметрами клиента при обновлении объекта клиента Stripe. Если вы хотите полностью контролировать процесс синхронизации информации о клиенте, вы можете переопределить метод syncStripeCustomerDetails.

Портал выставления счетов

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

use Illuminate\Http\Request;
 
Route::get('/billing-portal', function (Request $request) {
return $request->user()->redirectToBillingPortal();
});

По умолчанию, когда пользователь закончит управление своей подпиской, он сможет вернуться на маршрут home вашего приложения по ссылке в портале выставления счетов Stripe. Вы можете предоставить пользовательский URL, на который пользователь должен вернуться, передав URL в качестве аргумента методу redirectToBillingPortal:

use Illuminate\Http\Request;
 
Route::get('/billing-portal', function (Request $request) {
return $request->user()->redirectToBillingPortal(route('billing'));
});

Если вы хотите сгенерировать URL для портала выставления счетов без создания HTTP-ответа с перенаправлением, вы можете вызвать метод billingPortalUrl:

$url = $request->user()->billingPortalUrl(route('billing'));

Платежные методы

Хранение платежных методов

Чтобы создавать подписки или выполнять "одноразовые" оплаты с Stripe, вам нужно будет сохранить способ оплаты и получить его идентификатор из Stripe. Подход, используемый для достижения этого, различается в зависимости от того, планируете ли вы использовать способ оплаты для подписок или одноразовых оплат, поэтому мы рассмотрим оба варианта ниже.

Платежные методы для подписок

При сохранении информации о кредитной карте клиента для будущего использования подпиской необходимо использовать API Stripe "Setup Intents" для безопасного сбора данных о платежном методе клиента. "Setup Intent" указывает Stripe намерение списать средства с платежного метода клиента. В трейт Cashier входит метод createSetupIntent, который позволяет легко создать новый Setup Intent. Вы должны вызвать этот метод из маршрута или контроллера, который будет отображать форму для сбора данных о платежном методе вашего клиента:

return view('update-payment-method', [
'intent' => $user->createSetupIntent()
]);

После создания Setup Intent и передачи его в представление, вы должны прикрепить его секрет к элементу, который будет собирать данные о платежном методе. Например, рассмотрим эту форму "обновления платежного метода":

<input id="card-holder-name" type="text">
 
<!-- Stripe Elements Placeholder -->
<div id="card-element"></div>
 
<button id="card-button" data-secret="{{ $intent->client_secret }}">
Update Payment Method
</button>

Затем библиотеку Stripe.js можно использовать для присоединения элемента Stripe к форме и безопасного сбора данных о платежном методе клиента:

<script src="https://js.stripe.com/v3/"></script>
 
<script>
const stripe = Stripe('stripe-public-key');
 
const elements = stripe.elements();
const cardElement = elements.create('card');
 
cardElement.mount('#card-element');
</script>

Затем карту можно проверить, и из Stripe можно получить безопасный "идентификатор платежного метода" с использованием метода confirmCardSetup Stripe:

const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
const clientSecret = cardButton.dataset.secret;
 
cardButton.addEventListener('click', async (e) => {
const { setupIntent, error } = await stripe.confirmCardSetup(
clientSecret, {
payment_method: {
card: cardElement,
billing_details: { name: cardHolderName.value }
}
}
);
 
if (error) {
// Показать пользователю "error.message"...
} else {
// Карта успешно проверена...
}
});

После того как карта будет проверена Stripe, вы можете передать полученный идентификатор setupIntent.payment_method в ваше приложение Laravel, где его можно прикрепить к клиенту. Способ оплаты может быть либо добавлен как новый способ оплаты, либо использован для обновления способа оплаты по умолчанию. Вы также можете немедленно использовать идентификатор способа оплаты для создания новой подписки.

Примечание Если вы хотите получить больше информации о намерениях настройки и сборе данных об оплате клиента, пожалуйста, прочтите этот обзор, предоставленный Stripe.

Платежные методы для одноразовых платежей

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

<input id="card-holder-name" type="text">
 
<!-- Stripe Elements Placeholder -->
<div id="card-element"></div>
 
<button id="card-button">
Process Payment
</button>

После определения такой формы библиотеку Stripe.js можно использовать для присоединения элемента Stripe к форме и безопасного сбора данных о платежном методе клиента:

<script src="https://js.stripe.com/v3/"></script>
 
<script>
const stripe = Stripe('stripe-public-key');
 
const elements = stripe.elements();
const cardElement = elements.create('card');
 
cardElement.mount('#card-element');
</script>

Затем карту можно проверить, и из Stripe можно получить безопасный "идентификатор платежного метода" с использованием метода createPaymentMethod Stripe:

const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
 
cardButton.addEventListener('click', async (e) => {
const { paymentMethod, error } = await stripe.createPaymentMethod(
'card', cardElement, {
billing_details: { name: cardHolderName.value }
}
);
 
if (error) {
// Показать пользователю "error.message"...
} else {
// Карта успешно проверена...
}
});

Если карта успешно проверена, вы можете передать paymentMethod.id в ваше приложение Laravel и обработать одноразовый платеж.

Получение платежных методов

Метод paymentMethods на экземпляре модели billable возвращает коллекцию экземпляров Laravel\Cashier\PaymentMethod:

$paymentMethods = $user->paymentMethods();

По умолчанию этот метод вернет способы оплаты типа card. Чтобы получить способы оплаты другого типа, вы можете передать тип в качестве аргумента методу:

$paymentMethods = $user->paymentMethods('sepa_debit');

Для получения способа оплаты по умолчанию клиента можно использовать метод defaultPaymentMethod:

$paymentMethod = $user->defaultPaymentMethod();

Вы можете получить конкретный способ оплаты, привязанный к модели billable, с использованием метода findPaymentMethod:

$paymentMethod = $user->findPaymentMethod($paymentMethodId);

Определение наличия у пользователя платежного метода

Чтобы определить, привязан ли к модели billable способ оплаты по умолчанию, вызовите метод hasDefaultPaymentMethod:

if ($user->hasDefaultPaymentMethod()) {
// ...
}

Вы можете использовать метод hasPaymentMethod для определения, привязан ли к модели billable хотя бы один способ оплаты:

if ($user->hasPaymentMethod()) {
// ...
}

Этот метод определит, привязаны ли к модели billable способы оплаты типа card. Чтобы определить, существует ли для модели способ оплаты другого типа, вы можете передать тип в качестве аргумента методу:

if ($user->hasPaymentMethod('sepa_debit')) {
// ...
}

Обновление платежного метода по умолчанию

Метод updateDefaultPaymentMethod можно использовать для обновления информации о способе оплаты по умолчанию клиента. Этот метод принимает идентификатор способа оплаты Stripe и назначит новый способ оплаты как способ оплаты по умолчанию для выставления счетов:

$user->updateDefaultPaymentMethod($paymentMethod);

Чтобы синхронизировать информацию о вашем способе оплаты по умолчанию с информацией о способе оплаты по умолчанию клиента в Stripe, вы можете использовать метод updateDefaultPaymentMethodFromStripe:

$user->updateDefaultPaymentMethodFromStripe();

Внимание Способ оплаты по умолчанию у клиента может использоваться только для выставления счетов и создания новых подписок. Из-за ограничений, накладываемых Stripe, его нельзя использовать для одноразовых платежей.

Добавление платежных методов

Чтобы добавить новый способ оплаты, вы можете вызвать метод addPaymentMethod на модели billable, передав идентификатор способа оплаты:

$user->addPaymentMethod($paymentMethod);

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

Удаление платежных методов

Чтобы удалить способ оплаты, вы можете вызвать метод delete на экземпляре Laravel\Cashier\PaymentMethod, который вы хотите удалить:

$paymentMethod->delete();

Метод deletePaymentMethod удалит конкретный способ оплаты из модели billable:

$user->deletePaymentMethod('pm_visa');

Метод deletePaymentMethods удалит всю информацию о способах оплаты для модели billable:

$user->deletePaymentMethods();

По умолчанию этот метод удалит способы оплаты типа card. Чтобы удалить способы оплаты другого типа, вы можете передать тип в качестве аргумента методу:

$user->deletePaymentMethods('sepa_debit');

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

Подписки

Подписки предоставляют способ настройки повторяющихся платежей для ваших клиентов. Подписки Stripe, управляемые Cashier, поддерживают несколько цен подписки, количества подписок, пробные периоды и многое другое.

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

Чтобы создать подписку, сначала получите экземпляр вашей модели, которая обычно будет экземпляром App\Models\User. После получения экземпляра модели вы можете использовать метод newSubscription для создания подписки модели:

use Illuminate\Http\Request;
 
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription(
'default', 'price_monthly'
)->create($request->paymentMethodId);
 
// ...
});

Первым аргументом, передаваемым методу newSubscription, должно быть внутреннее имя подписки. Если ваше приложение предлагает только одну подписку, вы можете назвать ее default или primary. Это имя подписки предназначено только для внутреннего использования в приложении и не должно отображаться для пользователей. Кроме того, оно не должно содержать пробелов и никогда не должно изменяться после создания подписки. Вторым аргументом является конкретная цена, на которую пользователь подписывается. Это значение должно соответствовать идентификатору цены в Stripe.

Метод create, который принимает идентификатор способа оплаты Stripe или объект Stripe PaymentMethod, начнет подписку, а также обновит вашу базу данных идентификатором клиента Stripe и другой актуальной информацией по выставлению счетов модели.

Внимание Передача идентификатора метода оплаты непосредственно в метод создания подписки create также автоматически добавит его в сохраненные методы оплаты пользователя.

Сбор повторяющихся платежей через электронные письма счетов

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

$user->newSubscription('default', 'price_monthly')->createAndSendInvoice();

Время, в течение которого клиент должен оплатить счет, прежде чем его подписка будет отменена, определяется параметром days_until_due. По умолчанию это 30 дней, но вы можете предоставить конкретное значение для этого параметра, если хотите:

$user->newSubscription('default', 'price_monthly')->createAndSendInvoice([], [
'days_until_due' => 30
]);

Количества

Если вы хотите установить конкретное количество для цены при создании подписки, вы должны вызвать метод quantity на строителе подписки перед созданием подписки:

$user->newSubscription('default', 'price_monthly')
->quantity(5)
->create($paymentMethod);

Дополнительные детали

Если вы хотите указать дополнительные параметры клиента или подписки, поддерживаемые Stripe, вы можете сделать это, передав их вторым и третьим аргументами методу create:

$user->newSubscription('default', 'price_monthly')->create($paymentMethod, [
'email' => $email,
], [
'metadata' => ['note' => 'Some extra information.'],
]);

Купоны

Если вы хотите применить купон при создании подписки, вы можете использовать метод withCoupon:

$user->newSubscription('default', 'price_monthly')
->withCoupon('code')
->create($paymentMethod);

Или, если вы хотите применить промокод Stripe, вы можете использовать метод withPromotionCode:

$user->newSubscription('default', 'price_monthly')
->withPromotionCode('promo_code_id')
->create($paymentMethod);

Предоставленный идентификатор промокода должен быть идентификатором API Stripe, присвоенным промокоду, а не клиентскому промокоду. Если вам нужно найти идентификатор промокода на основе предоставленного клиентского промокода, вы можете использовать метод findPromotionCode:

// Поиск идентификатора промо-кода по его коду для клиента...
$promotionCode = $user->findPromotionCode('SUMMERSALE');
 
// Поиск активного идентификатора промо-кода по его коду для клиента...
$promotionCode = $user->findActivePromotionCode('SUMMERSALE');

В приведенном выше примере возвращаемый объект $promotionCode является экземпляром Laravel\Cashier\PromotionCode. Этот класс декорирует базовый объект Stripe\PromotionCode. Вы можете получить купон, связанный с промокодом, вызвав метод coupon:

$coupon = $user->findPromotionCode('SUMMERSALE')->coupon();

Экземпляр купона позволяет определить сумму скидки и то, представляет ли купон фиксированную скидку или скидку в процентах:

if ($coupon->isPercentage()) {
return $coupon->percentOff().'%'; // 21.5%
} else {
return $coupon->amountOff(); // $5.99
}

Вы также можете получить скидки, которые в настоящее время применяются к клиенту или подписке:

$discount = $billable->discount();
 
$discount = $subscription->discount();

Возвращаемые экземпляры Laravel\Cashier\Discount декорируют базовый объект Stripe\Discount. Вы можете получить купон, связанный с этой скидкой, вызвав метод coupon:

$coupon = $subscription->discount()->coupon();

Если вы хотите применить новый купон или промокод к клиенту или подписке, вы можете сделать это с помощью методов applyCoupon или applyPromotionCode:

$billable->applyCoupon('coupon_id');
$billable->applyPromotionCode('promotion_code_id');
 
$subscription->applyCoupon('coupon_id');
$subscription->applyPromotionCode('promotion_code_id');

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

Дополнительную информацию по этому вопросу можно найти в документации Stripe по купонам и промокодам.

Добавление подписок

Если вы хотите добавить подписку клиенту, у которого уже есть способ оплаты по умолчанию, вы можете вызвать метод add на строителе подписок:

use App\Models\User;
 
$user = User::find(1);
 
$user->newSubscription('default', 'price_monthly')->add();

Создание подписок из панели управления Stripe

Вы также можете создавать подписки прямо из панели управления Stripe. При этом Cashier будет синхронизировать только что добавленные подписки и присваивать им имя default. Чтобы настроить имя подписки, которое присваивается подпискам, созданным в панели управления, расширьте WebhookController и перезапишите метод newSubscriptionName.

Кроме того, вы можете создавать только один тип подписки через панель управления Stripe. Если ваше приложение предлагает несколько подписок с разными именами, только один тип подписки можно добавить через панель управления Stripe.

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

Проверка статуса подписки

После того как клиент подписан на ваше приложение, вы легко можете проверить статус его подписки с использованием различных удобных методов. Во-первых, метод subscribed возвращает true, если у клиента есть активная подписка, даже если подписка в настоящее время находится в пробном периоде. Метод subscribed принимает имя подписки в качестве первого аргумента:

if ($user->subscribed('default')) {
// ...
}

Метод subscribed также отлично подходит для использования в промежуточном программном обеспечении маршрута, позволяя фильтровать доступ к маршрутам и контроллерам на основе статуса подписки пользователя:

<?php
 
namespace App\Http\Middleware;
 
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
 
class EnsureUserIsSubscribed
{
/**
* Обработка входящего запроса.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->user() && ! $request->user()->subscribed('default')) {
// Этот пользователь не является платящим клиентом...
return redirect('billing');
}
 
return $next($request);
}
}

Если вы хотите определить, находится ли пользователь все еще в пробном периоде, вы можете использовать метод onTrial. Этот метод может быть полезен, чтобы определить, следует ли отображать предупреждение пользователю о том, что он все еще находится в пробном периоде:

if ($user->subscription('default')->onTrial()) {
// ...
}

Метод subscribedToProduct может быть использован для определения, подписан ли пользователь на данный продукт на основе идентификатора данного продукта Stripe. В Stripe продукты - это наборы цен. В этом примере мы определим, активно подписан ли пользователь на "премиум" продукт приложения в его default подписке. Указанный идентификатор продукта Stripe должен соответствовать одному из идентификаторов ваших продуктов в панели управления Stripe:

if ($user->subscribedToProduct('prod_premium', 'default')) {
// ...
}

Передачей массива в метод subscribedToProduct вы можете определить, активно ли подписано пользователя на "базовый" или "премиум" приложение с его default подпиской:

if ($user->subscribedToProduct(['prod_basic', 'prod_premium'], 'default')) {
// ...
}

Метод subscribedToPrice может быть использован для определения того, соответствует ли подписка клиента заданному идентификатору цены:

if ($user->subscribedToPrice('price_basic_monthly', 'default')) {
// ...
}

Метод recurring может использоваться для определения, подписан ли пользователь в настоящее время и находится ли вне пробного периода:

if ($user->subscription('default')->recurring()) {
// ...
}

Внимание Если у пользователя две подписки с одинаковым именем, метод subscription всегда будет возвращать самую последнюю подписку. Например, у пользователя может быть две записи подписок с именем default; однако одна из подписок может быть старой, просроченной подпиской, тогда как другая - текущей, активной подпиской. Всегда будет возвращена самая последняя подписка, в то время как старые подписки сохраняются в базе данных для исторического обзора.

Статус отмененной подписки

Чтобы определить, был ли пользователь когда-то активным подписчиком, но отменил свою подписку, вы можете использовать метод canceled:

if ($user->subscription('default')->canceled()) {
// ...
}

Вы также можете определить, отменил ли пользователь свою подписку, но все еще находится в периоде "отсрочки" до полного истечения срока действия подписки. Например, если пользователь отменяет подписку 5 марта, которая изначально была запланирована для завершения 10 марта, пользователь находится в периоде "отсрочки" до 10 марта. Обратите внимание, что метод subscribed все еще возвращает true в течение этого времени:

if ($user->subscription('default')->onGracePeriod()) {
// ...
}

Чтобы определить, отменил ли пользователь свою подписку и находится ли вне "периода отсрочки", вы можете использовать метод ended:

if ($user->subscription('default')->ended()) {
// ...
}

Состояние не завершено и просрочено

Если для подписки требуется второе платежное действие после создания, подписка будет отмечена как incomplete. Состояния подписки хранятся в столбце stripe_status таблицы подписок Cashier.

Точно так же, если для замены цен требуется второе платежное действие, подписка будет отмечена как past_due. Когда ваша подписка находится в одном из этих состояний, она не будет активной, пока клиент не подтвердит свой платеж. Определить, есть ли у подписки неполный платеж, можно с помощью метода hasIncompletePayment на экземпляре модели Billable или подписки:

if ($user->hasIncompletePayment('default')) {
// ...
}
 
if ($user->subscription('default')->hasIncompletePayment()) {
// ...
}

Когда у подписки есть неполный платеж, вы должны направить пользователя на страницу подтверждения платежа Cashier, передав идентификатор latestPayment. Вы можете использовать метод latestPayment в экземпляре подписки для получения этого идентификатора:

<a href="{{ route('cashier.payment', $subscription->latestPayment()->id) }}">
Please confirm your payment.
</a>

Если вы хотите, чтобы подписка все равно считалась активной, находясь в состоянии past_due или incomplete, вы можете использовать методы keepPastDueSubscriptionsActive и keepIncompleteSubscriptionsActive, предоставленные Cashier. Обычно эти методы следует вызывать в методе register вашего App\Providers\AppServiceProvider:

use Laravel\Cashier\Cashier;
 
/**
* Регистрация любых служб приложения.
*/
public function register(): void
{
Cashier::keepPastDueSubscriptionsActive();
Cashier::keepIncompleteSubscriptionsActive();
}

Внимание Когда подписка находится в состоянии incomplete, ее нельзя изменить, пока оплата не будет подтверждена. Поэтому методы swap и updateQuantity будут вызывать исключение, когда подписка находится в состоянии incomplete.

Области подписки

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

// Получение всех активных подписок...
$subscriptions = Subscription::query()->active()->get();
 
// Получение всех отмененных подписок для пользователя...
$subscriptions = $user->subscriptions()->canceled()->get();

Полный список доступных областей запроса доступен ниже:

Subscription::query()->active();
Subscription::query()->canceled();
Subscription::query()->ended();
Subscription::query()->incomplete();
Subscription::query()->notCanceled();
Subscription::query()->notOnGracePeriod();
Subscription::query()->notOnTrial();
Subscription::query()->onGracePeriod();
Subscription::query()->onTrial();
Subscription::query()->pastDue();
Subscription::query()->recurring();

Изменение цен

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

use App\Models\User;
 
$user = App\Models\User::find(1);
 
$user->subscription('default')->swap('price_yearly');

Если у клиента есть пробный период, пробный период сохранится. Кроме того, если существует "количество" для подписки, это количество также будет сохранено.

Если вы хотите заменить цены и отменить любой текущий пробный период, вы можете вызвать метод skipTrial:

$user->subscription('default')
->skipTrial()
->swap('price_yearly');

Если вы хотите заменить цены и немедленно выставить счет клиенту, не дожидаясь следующего биллингового цикла, вы можете использовать метод swapAndInvoice:

$user = User::find(1);
 
$user->subscription('default')->swapAndInvoice('price_yearly');

Прорейтинг

По умолчанию Stripe пропорционально распределяет платежи при замене цен. Метод noProrate может использоваться для обновления цены подписки без пропорционального распределения платежей:

$user->subscription('default')->noProrate()->swap('price_yearly');

Дополнительную информацию о пропорциональном распределении платежей при замене цен можно найти в документации Stripe.

Внимание Использование метода noProrate перед методом swapAndInvoice не повлияет на прорату. Счет всегда будет выставлен.

Количество подписки

Иногда подписки зависят от "количества". Например, приложение для управления проектами может взимать плату в размере $10 в месяц за проект. Вы можете использовать методы incrementQuantity и decrementQuantity для увеличения или уменьшения количества подписок с легкостью:

use App\Models\User;
 
$user = User::find(1);
 
$user->subscription('default')->incrementQuantity();
 
// Добавление пяти к текущему количеству подписки...
$user->subscription('default')->incrementQuantity(5);
 
$user->subscription('default')->decrementQuantity();
 
// Вычитание пяти из текущего количества подписки...
$user->subscription('default')->decrementQuantity(5);

Кроме того, вы можете установить конкретное количество с использованием метода updateQuantity:

$user->subscription('default')->updateQuantity(10);

Метод noProrate может использоваться для обновления количества подписки без пропорционального распределения платежей:

$user->subscription('default')->noProrate()->updateQuantity(10);

Дополнительную информацию о количествах подписок можно найти в документации Stripe.

Количества для подписок с несколькими продуктами

Если ваша подписка - это подписка с несколькими продуктами, вы должны передать идентификатор цены, количество которой вы хотите увеличить или уменьшить, вторым аргументом методам увеличения/уменьшения:

$user->subscription('default')->incrementQuantity(1, 'price_chat');

Подписки с несколькими продуктами

Подписка с несколькими продуктами позволяют назначить несколько продуктов для одной подписки. Например, представьте, что вы создаете приложение для обслуживания клиентов с базовой стоимостью подписки $10 в месяц, но предлагаете дополнительный продукт "чат в реальном времени" за дополнительные $15 в месяц. Информация для подписок с несколькими продуктами хранится в таблице базы данных subscription_items Cashier.

Вы можете указать несколько продуктов для данной подписки, передав массив цен вторым аргументом методу newSubscription:

use Illuminate\Http\Request;
 
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription('default', [
'price_monthly',
'price_chat',
])->create($request->paymentMethodId);
 
// ...
});

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

$user = User::find(1);
 
$user->newSubscription('default', ['price_monthly', 'price_chat'])
->quantity(5, 'price_chat')
->create($paymentMethod);

Если вы хотите добавить еще одну цену к существующей подписке, вы можете вызвать метод addPrice подписки:

$user = User::find(1);
 
$user->subscription('default')->addPrice('price_chat');

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

$user->subscription('default')->addPriceAndInvoice('price_chat');

Если вы хотите добавить цену с определенным количеством, вы можете передать количество вторым аргументом методов addPrice или addPriceAndInvoice:

$user = User::find(1);
 
$user->subscription('default')->addPrice('price_chat', 5);

Вы можете удалить цены из подписок с помощью метода removePrice:

$user->subscription('default')->removePrice('price_chat');

Внимание Вы не можете удалить последнюю цену в подписке. Вместо этого просто отмените подписку.

Замена цен

Вы также можете изменить цены, присоединенные к подписке с несколькими продуктами. Например, представьте себе, что у клиента есть подписка price_basic с дополнительным продуктом price_chat, и вы хотите перейти от price_basic к цене price_pro:

use App\Models\User;
 
$user = User::find(1);
 
$user->subscription('default')->swap(['price_pro', 'price_chat']);

При выполнении указанного выше примера будет удален базовый продукт подписки с ценой price_basic, а продукт с ценой price_chat сохранится. Кроме того, будет создан новый продукт подписки с ценой price_pro.

Вы также можете указать параметры продукта подписки, передав массив пар ключ / значение в метод swap. Например, вам может потребоваться указать количество цен подписки:

$user = User::find(1);
 
$user->subscription('default')->swap([
'price_pro' => ['quantity' => 5],
'price_chat'
]);

Если вы хотите заменить одну цену в подписке, вы можете сделать это с помощью метода swap самого продукта подписки. Этот подход особенно полезен, если вы хотите сохранить все существующие метаданные по другим ценам подписки:

$user = User::find(1);
 
$user->subscription('default')
->findItemOrFail('price_basic')
->swap('price_pro');

Прорейтинг

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

$user->subscription('default')->noProrate()->removePrice('price_chat');

Количества

Если вы хотите обновить количество для отдельных цен подписок, вы можете сделать это с использованием существующих методов количества, передав имя цены в качестве дополнительного аргумента для метода:

$user = User::find(1);
 
$user->subscription('default')->incrementQuantity(5, 'price_chat');
 
$user->subscription('default')->decrementQuantity(3, 'price_chat');
 
$user->subscription('default')->updateQuantity(10, 'price_chat');

Внимание Когда у подписки есть несколько цен, атрибуты stripe_price и quantity в модели Subscription будут null. Для доступа к отдельным атрибутам цены следует использовать отношение items, доступное в модели Subscription.

Элементы подписки

Когда у подписки есть несколько цен, в вашей базе данных будет храниться несколько "элементов" подписки в таблице subscription_items. Вы можете получить к ним доступ через отношение items подписки:

use App\Models\User;
 
$user = User::find(1);
 
$subscriptionItem = $user->subscription('default')->items->first();
 
// Получение Stripe-цены и количества для конкретного товара...
$stripePrice = $subscriptionItem->stripe_price;
$quantity = $subscriptionItem->quantity;

Вы также можете получить конкретную цену, используя метод findItemOrFail:

$user = User::find(1);
 
$subscriptionItem = $user->subscription('default')->findItemOrFail('price_chat');

Несколько подписок

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

Когда ваше приложение создает подписки, вы можете указать имя подписки в методе newSubscription. Имя может быть любой строкой, представляющей тип подписки, которую пользователь инициирует:

use Illuminate\Http\Request;
 
Route::post('/swimming/subscribe', function (Request $request) {
$request->user()->newSubscription('swimming')
->price('price_swimming_monthly')
->create($request->paymentMethodId);
 
// ...
});

В этом примере мы инициировали ежемесячную подписку на плавание для клиента. Однако позже они могут захотеть перейти на годовую подписку. При корректировке подписки клиента мы просто можем заменить цену на подписке swimming:

$user->subscription('swimming')->swap('price_swimming_yearly');

Конечно же, вы также можете отменить подписку полностью:

$user->subscription('swimming')->cancel();

Метрическое тарификация

Метрическая тарификация позволяет вам выставлять счета клиентам на основе их использования продукта в течение биллингового цикла. Например, вы можете выставлять счета клиентам в зависимости от количества отправленных ими текстовых сообщений или электронных писем в месяц.

Чтобы начать использовать метрическую тарификацию, сначала нужно создать новый продукт в вашей панели управления Stripe с метрической ценой. Затем используйте meteredPrice, чтобы добавить идентификатор метрической цены к подписке клиента:

use Illuminate\Http\Request;
 
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription('default')
->meteredPrice('price_metered')
->create($request->paymentMethodId);
 
// ...
});

Вы также можете начать метрическую подписку через Stripe Checkout:

$checkout = Auth::user()
->newSubscription('default', [])
->meteredPrice('price_metered')
->checkout();
 
return view('your-checkout-view', [
'checkout' => $checkout,
]);

Отчетность по использованию

Когда ваш клиент использует ваше приложение, вы будете сообщать Stripe о его использовании, чтобы ему можно было выставлять счета точно. Чтобы увеличить использование метрической подписки, вы можете использовать метод reportUsage:

$user = User::find(1);
 
$user->subscription('default')->reportUsage();

По умолчанию к биллинговому периоду добавляется "количество использования" 1. Кроме того, вы можете передать конкретное "использование", которое следует добавить к использованию клиента за биллинговый период:

$user = User::find(1);
 
$user->subscription('default')->reportUsage(15);

Если ваше приложение предлагает несколько цен на одной подписке, вам нужно будет использовать метод reportUsageFor, чтобы указать метрическую цену, для которой вы хотите сообщить об использовании:

$user = User::find(1);
 
$user->subscription('default')->reportUsageFor('price_metered', 15);

Иногда вам может потребоваться обновить использование, которое вы ранее сообщили. Для этого вы можете передать отметку времени или экземпляр DateTimeInterface в качестве второго параметра для reportUsage. При этом Stripe обновит использование, которое было сообщено в этот момент времени. Вы можете продолжать обновлять предыдущие записи использования, поскольку указанная дата и время все еще находятся в текущем биллинговом периоде:

$user = User::find(1);
 
$user->subscription('default')->reportUsage(5, $timestamp);

Получение записей использования

Чтобы получить прошедшее использование клиента, вы можете использовать метод usageRecords экземпляра подписки:

$user = User::find(1);
 
$usageRecords = $user->subscription('default')->usageRecords();

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

$user = User::find(1);
 
$usageRecords = $user->subscription('default')->usageRecordsFor('price_metered');

Методы usageRecords и usageRecordsFor возвращают экземпляр Collection, содержащий ассоциативный массив записей использования. Вы можете перебирать этот массив, чтобы отобразить общее использование клиента:

@foreach ($usageRecords as $usageRecord)
- Period Starting: {{ $usageRecord['period']['start'] }}
- Period Ending: {{ $usageRecord['period']['end'] }}
- Total Usage: {{ $usageRecord['total_usage'] }}
@endforeach

Для полного справочника всех возвращаемых данных использования и информации о том, как использовать пагинацию Stripe на основе курсоров, обратитесь к официальной документации Stripe API.

Налоги на подписку

Внимание Вместо того чтобы рассчитывать налоговые ставки вручную, вы можете автоматически рассчитывать налоги с использованием Stripe Tax

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

/**
* Налоговые ставки, которые должны применяться к подпискам клиента.
*
* @return array<int, string>
*/
public function taxRates(): array
{
return ['txr_id'];
}

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

Если вы предлагаете подписки с несколькими продуктами, вы можете определить разные ставки налога для каждой цены, реализовав метод priceTaxRates в вашей модели, поддерживающей выставление счетов:

/**
* Налоговые ставки, которые должны применяться к подпискам клиента.
*
* @return array<string, array<int, string>>
*/
public function priceTaxRates(): array
{
return [
'price_monthly' => ['txr_id'],
];
}

Внимание Метод taxRates применяется только к платежам по подписке. Если вы используете Cashier для создания "одноразовых" платежей, вам нужно будет вручную указать ставку налога в этот момент.

Синхронизация налоговых ставок

При изменении жестко закодированных идентификаторов ставок налога, возвращаемых методом taxRates, настройки налогов для любых существующих подписок пользователя останутся неизменными. Если вы хотите обновить значение налога для существующих подписок новыми значениями taxRates, вы должны вызвать метод syncTaxRates для экземпляра подписки пользователя:

$user->subscription('default')->syncTaxRates();

Это также синхронизирует ставки налога для подписки с несколькими продуктами. Если ваше приложение предлагает подписки с несколькими продуктами, убедитесь, что ваша модель, поддерживающая выставление счетов, реализует метод priceTaxRates обсуждаемый выше.

Налоговое освобождение

Cashier также предлагает методы isNotTaxExempt, isTaxExempt и reverseChargeApplies для определения, является ли клиент налоговым освобожденным. Эти методы будут вызывать API Stripe для определения статуса налогового освобождения клиента:

use App\Models\User;
 
$user = User::find(1);
 
$user->isTaxExempt();
$user->isNotTaxExempt();
$user->reverseChargeApplies();

Внимание Эти методы также доступны для любого объекта Laravel\Cashier\Invoice. Однако, при вызове на объекте Invoice, методы определят статус освобождения от налогов на момент создания счета.

Якорная дата подписки

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

use Illuminate\Http\Request;
 
Route::post('/user/subscribe', function (Request $request) {
$anchor = Carbon::parse('first day of next month');
 
$request->user()->newSubscription('default', 'price_monthly')
->anchorBillingCycleOn($anchor->startOfDay())
->create($request->paymentMethodId);
 
// ...
});

Для получения дополнительной информации о управлении биллинговыми циклами подписок обратитесь к документации Stripe по биллинговым циклам

Отмена подписок

Для отмены подписки вызовите метод cancel для подписки пользователя:

$user->subscription('default')->cancel();

Когда подписка отменяется, Cashier автоматически устанавливает значение столбца ends_at в вашей таблице базы данных subscriptions. Этот столбец используется для определения, когда метод subscribed должен начать возвращать false.

Например, если клиент отменяет подписку 1 марта, но подписка не должна была завершиться до 5 марта, метод subscribed будет продолжать возвращать true до 5 марта. Это делается потому, что пользователю обычно разрешается продолжать использовать приложение до конца своего биллингового цикла.

Вы можете определить, отменил ли пользователь свою подписку, но все еще находится в "периоде благосклонности", используя метод onGracePeriod:

if ($user->subscription('default')->onGracePeriod()) {
// ...
}

Если вы хотите отменить подписку немедленно, вызовите метод cancelNow для подписки пользователя:

$user->subscription('default')->cancelNow();

Если вы хотите отменить подписку немедленно и выставить счет за все оставшиеся несчетные метрические использования или новые/ожидающиеся элементы счета прората, вызовите метод cancelNowAndInvoice для подписки пользователя:

$user->subscription('default')->cancelNowAndInvoice();

Вы также можете выбрать отмену подписки в конкретный момент времени:

$user->subscription('default')->cancelAt(
now()->addDays(10)
);

Возобновление подписок

Если клиент отменил свою подписку, и вы хотите возобновить ее, вы можете вызвать метод resume для подписки. Клиент должен все еще находиться в своем "периоде благосклонности", чтобы возобновить подписку:

$user->subscription('default')->resume();

Если клиент отменяет подписку, а затем возобновляет ее до полного истечения срока действия подписки, клиенту не будет выставлен счет сразу. Вместо этого их подписка будет вновь активирована, и им будет выставлен счет в соответствии с исходным биллинговым циклом.

Пробные периоды подписки

С платежным методом впереди

Если вы хотите предложить пробные периоды своим клиентам, сохраняя при этом информацию о платежном методе заранее, вы должны использовать метод trialDays при создании ваших подписок:

use Illuminate\Http\Request;
 
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription('default', 'price_monthly')
->trialDays(10)
->create($request->paymentMethodId);
 
// ...
});

Этот метод устанавливает дату окончания пробного периода в записи подписки в базе данных и указывает Stripe не начинать выставление счетов клиенту до этой даты. При использовании метода trialDays Cashier перезапишет любой настроенный по умолчанию пробный период для цены в Stripe.

Внимание Если подписка клиента не отменена до окончания пробного периода, ему будет выставлен счет сразу после окончания пробного периода, поэтому убедитесь, что вы уведомили пользователей о дате окончания их пробного периода.

Метод trialUntil позволяет указать экземпляр DateTime, который определяет, когда должен завершиться пробный период:

use Carbon\Carbon;
 
$user->newSubscription('default', 'price_monthly')
->trialUntil(Carbon::now()->addDays(10))
->create($paymentMethod);

Вы можете определить, находится ли пользователь в своем пробном периоде, используя метод onTrial экземпляра пользователя или метод onTrial экземпляра подписки. Два приведенных ниже примера эквивалентны:

if ($user->onTrial('default')) {
// ...
}
 
if ($user->subscription('default')->onTrial()) {
// ...
}

Вы можете использовать метод endTrial, чтобы немедленно завершить пробный период подписки:

$user->subscription('default')->endTrial();

Чтобы определить, истек ли срок действия существующего пробного периода, вы можете использовать методы hasExpiredTrial:

if ($user->hasExpiredTrial('default')) {
// ...
}
 
if ($user->subscription('default')->hasExpiredTrial()) {
// ...
}

Определение дней пробного периода в Stripe / Cashier

Вы можете определить, сколько дней пробного периода должна получить ваша цена в панели управления Stripe или всегда передавать их явно с помощью Cashier. Если вы выберете определение дней пробного периода вашей цены в Stripe, вы должны знать, что новые подписки, включая новые подписки для клиента, у которого уже была подписка в прошлом, всегда будут получать пробный период, если вы явно не вызовете метод skipTrial().

Без платежного метода впереди

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

use App\Models\User;
 
$user = User::create([
// ...
'trial_ends_at' => now()->addDays(10),
]);

Внимание Не забудьте добавить приведение типов даты для атрибута trial_ends_at в определении класса вашей модели, в которой реализован интерфейс Billable.

Cashier называет этот тип пробного периода "общим пробным", поскольку он не привязан к какой-либо существующей подписке. Метод onTrial экземпляра модели, поддерживающей выставление счетов, вернет true, если текущая дата еще не прошла значение trial_ends_at:

if ($user->onTrial()) {
// Пользователь находится в пробном периоде...
}

Как только вы будете готовы создать фактическую подписку для пользователя, вы можете использовать метод newSubscription как обычно:

$user = User::find(1);
 
$user->newSubscription('default', 'price_monthly')->create($paymentMethod);

Чтобы получить дату окончания пробного периода пользователя, вы можете использовать метод trialEndsAt. Этот метод вернет экземпляр даты Carbon, если пользователь находится в пробном периоде, или null, если он не находится. Вы также можете передать необязательный параметр имени подписки, если хотите получить дату окончания пробного периода для конкретной подписки, отличной от той, что по умолчанию:

if ($user->onTrial()) {
$trialEndsAt = $user->trialEndsAt('main');
}

Вы также можете использовать метод onGenericTrial, если хотите знать, что пользователь находится именно в своем "общем" пробном периоде и еще не создал фактическую подписку:

if ($user->onGenericTrial()) {
// Пользователь находится в "общем" пробном периоде...
}

Продление пробных периодов

Метод extendTrial позволяет продлить пробный период подписки после создания подписки. Если пробный период уже истек, и клиент уже выставляется счет за подписку, вы все равно можете предложить ему продленный пробный период. Время, проведенное в пробном периоде, будет вычтено из следующего счета клиента:

use App\Models\User;
 
$subscription = User::find(1)->subscription('default');
 
// Завершение пробного периода через 7 дней...
$subscription->extendTrial(
now()->addDays(7)
);
 
// Добавление дополнительных 5 дней к пробному периоду...
$subscription->extendTrial(
$subscription->trial_ends_at->addDays(5)
);

Обработка вебхуков Stripe

Примечание Вы можете использовать Stripe CLI для тестирования вебхуков во время локальной разработки.

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

По умолчанию контроллер вебхуков Cashier автоматически обрабатывает отмену подписок слишком много неудачных попыток оплаты (как определено в ваших настройках Stripe), обновления клиента, удаления клиента, обновления подписки и изменения платежного метода; однако, как мы вскоре узнаем, вы можете расширить этот контроллер для обработки любого события вебхука Stripe, который вам нравится.

Чтобы ваше приложение могло обрабатывать вебхуки Stripe, убедитесь, что вы настроили URL вебхука в панели управления Stripe. По умолчанию контроллер вебхука Cashier реагирует на путь /stripe/webhook. Полный список всех вебхуков, которые вы должны включить в панели управления Stripe, следующий:

  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • customer.updated
  • customer.deleted
  • payment_method.automatically_updated
  • invoice.payment_action_required
  • invoice.payment_succeeded

Для удобства Cashier включает команду Artisan cashier:webhook. Эта команда создаст вебхук в Stripe, который прослушивает все события, необходимые для Cashier:

php artisan cashier:webhook

По умолчанию созданный вебхук будет указывать на URL, определенный переменной окружения APP_URL и маршруту cashier.webhook, который включен в Cashier. Вы можете передать опцию --url при вызове команды, если хотите использовать другой URL:

php artisan cashier:webhook --url "https://example.com/stripe/webhook"

Вебхук, который создается, будет использовать версию Stripe API, совместимую с вашей версией Cashier. Если вы хотите использовать другую версию Stripe, вы можете передать опцию --api-version:

php artisan cashier:webhook --api-version="2019-12-03"

После создания вебхук сразу будет активным. Если вы хотите создать вебхук, но отключить его до готовности, вы можете передать опцию --disabled при вызове команды:

php artisan cashier:webhook --disabled

Внимание Убедитесь, что вы защитили входящие запросы вебхука Stripe с помощью включенного в Cashier подтверждения подписи вебхука.

Вебхуки и защита от CSRF

Поскольку вебхуки Stripe должны обходить защиту CSRF в Laravel, убедитесь включить URI в исключения вашего middleware App\Http\Middleware\VerifyCsrfToken или разместить маршрут вне группы middleware web:

protected $except = [
'stripe/*',
];

Определение обработчиков событий вебхуков

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

  • Laravel\Cashier\Events\WebhookReceived
  • Laravel\Cashier\Events\WebhookHandled

Оба события содержат полную нагрузку вебхука Stripe. Например, если вы хотите обработать вебхук invoice.payment_succeeded, вы можете зарегистрировать слушателя, который будет обрабатывать событие:

<?php
 
namespace App\Listeners;
 
use Laravel\Cashier\Events\WebhookReceived;
 
class StripeEventListener
{
/**
* Обработка полученных вебхуков Stripe.
*/
public function handle(WebhookReceived $event): void
{
if ($event->payload['type'] === 'invoice.payment_succeeded') {
// Обработка входящего события...
}
}
}

После того как ваш слушатель будет определен, вы можете зарегистрировать его в EventServiceProvider вашего приложения:

<?php
 
namespace App\Providers;
 
use App\Listeners\StripeEventListener;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Laravel\Cashier\Events\WebhookReceived;
 
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
WebhookReceived::class => [
StripeEventListener::class,
],
];
}

Проверка подписей вебхуков

Чтобы обеспечить безопасность ваших вебхуков, вы можете использовать подписи вебхуков Stripe. Для удобства Cashier автоматически включает middleware, которое проверяет, что входящий запрос вебхука Stripe действителен.

Для включения проверки вебхука убедитесь, что переменная окружения STRIPE_WEBHOOK_SECRET установлена в вашем файле .env вашего приложения. Значение секрета вебхука можно получить в панели управления вашего аккаунта Stripe.

Одноразовые платежи

Простой платеж

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

use Illuminate\Http\Request;
 
Route::post('/purchase', function (Request $request) {
$stripeCharge = $request->user()->charge(
100, $request->paymentMethodId
);
 
// ...
});

Метод charge принимает массив в качестве своего третьего аргумента, что позволяет передавать любые опции, которые вы хотите в создаваемом заряде Stripe. Дополнительную информацию о доступных вам опциях при создании зарядов можно найти в документации Stripe:

$user->charge(100, $paymentMethod, [
'custom_option' => $value,
]);

Вы также можете использовать метод charge без базового клиента или пользователя. Для этого вызовите метод charge на новом экземпляре модели вашего приложения:

use App\Models\User;
 
$stripeCharge = (new User)->charge(100, $paymentMethod);

Метод charge вызовет исключение, если заряд не удастся. Если заряд успешен, из метода будет возвращен экземпляр Laravel\Cashier\Payment:

try {
$payment = $user->charge(100, $paymentMethod);
} catch (Exception $e) {
// ...
}

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

Платеж с счетом

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

$user->invoicePrice('price_tshirt', 5);

Счет будет немедленно оплачен по умолчанию через платежный метод пользователя. Метод invoicePrice также принимает массив в качестве своего третьего аргумента. Этот массив содержит параметры выставления счета для элемента счета. Четвертым аргументом, принимаемым методом, также является массив, который должен содержать параметры выставления счета для самого счета:

$user->invoicePrice('price_tshirt', 5, [
'discounts' => [
['coupon' => 'SUMMER21SALE']
],
], [
'default_tax_rates' => ['txr_id'],
]);

Точно так же, как invoicePrice, вы можете использовать метод tabPrice для создания одноразового счета за несколько товаров (до 250 товаров на один счет), добавив их в "вкладку" клиента, а затем выставив счет клиенту. Например, мы можем выставить счет клиенту за пять рубашек и две кружки:

$user->tabPrice('price_tshirt', 5);
$user->tabPrice('price_mug', 2);
$user->invoice();

В качестве альтернативы вы можете использовать метод invoiceFor для создания "одноразового" счета по умолчанию через платежный метод клиента:

$user->invoiceFor('One Time Fee', 500);

Хотя метод invoiceFor доступен для использования, рекомендуется использовать методы invoicePrice и tabPrice с предопределенными ценами. Таким образом, у вас будет доступ к более подробной аналитике и данным в вашей панели управления Stripe относительно ваших продаж на основе каждого товара.

Внимание Методы invoice, invoicePrice и invoiceFor создадут счет Stripe, который будет повторно пытаться списать неудачные попытки выставления счета. Если вы не хотите, чтобы счета повторно пытались списать неудачные суммы, вам нужно будет закрыть их с использованием API Stripe после первой неудачной попытки.

Создание платежных намерений

Вы можете создать новый платежный намерение Stripe, вызвав метод pay на экземпляре модели, поддерживающей выставление счетов. Вызов этого метода создаст платежное намерение, обернутое в экземпляр Laravel\Cashier\Payment:

use Illuminate\Http\Request;
 
Route::post('/pay', function (Request $request) {
$payment = $request->user()->pay(
$request->get('amount')
);
 
return $payment->client_secret;
});

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

При использовании метода pay будут доступны платежные методы, включенные по умолчанию в вашей панели управления Stripe. В качестве альтернативы, если вы хотите разрешить использование только некоторых конкретных платежных методов, вы можете использовать метод payWith:

use Illuminate\Http\Request;
 
Route::post('/pay', function (Request $request) {
$payment = $request->user()->payWith(
$request->get('amount'), ['card', 'bancontact']
);
 
return $payment->client_secret;
});

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

Возврат средств за платежи

Если вам нужно вернуть средства за заряд Stripe, вы можете использовать метод refund. Этот метод принимает идентификатор платежного намерения Stripe в качестве первого аргумента:

$payment = $user->charge(100, $paymentMethodId);
 
$user->refund($payment->id);

Счета

Получение счетов

Вы можете легко получить массив счетов модели, поддерживающей выставление счетов, с использованием метода invoices. Метод invoices возвращает коллекцию экземпляров Laravel\Cashier\Invoice:

$invoices = $user->invoices();

Если вы хотите включить ожидающие счета в результаты, вы можете использовать метод invoicesIncludingPending:

$invoices = $user->invoicesIncludingPending();

Вы можете использовать метод findInvoice для получения конкретного счета по его ID:

$invoice = $user->findInvoice($invoiceId);

Отображение информации о счете

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

<table>
@foreach ($invoices as $invoice)
<tr>
<td>{{ $invoice->date()->toFormattedDateString() }}</td>
<td>{{ $invoice->total() }}</td>
<td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td>
</tr>
@endforeach
</table>

Предстоящие счета

Чтобы получить предстоящий счет для клиента, вы можете использовать метод upcomingInvoice:

$invoice = $user->upcomingInvoice();

Точно так же, если у клиента есть несколько подписок, вы также можете получить предстоящий счет для конкретной подписки:

$invoice = $user->subscription('default')->upcomingInvoice();

Предварительный просмотр счетов подписок

Используя метод previewInvoice, вы можете предварительно просмотреть счет перед внесением изменений в цену. Это позволит вам определить, каким будет счет вашего клиента при внесении изменений в цену:

$invoice = $user->subscription('default')->previewInvoice('price_yearly');

Вы можете передать массив цен методу previewInvoice, чтобы предварительно просмотреть счета с несколькими новыми ценами:

$invoice = $user->subscription('default')->previewInvoice(['price_yearly', 'price_metered']);

Генерация PDF-счетов

Перед созданием PDF-счетов вы должны использовать Composer для установки библиотеки Dompdf, которая является рендерером счетов по умолчанию для Cashier:

composer require dompdf/dompdf

Из маршрута или контроллера вы можете использовать метод downloadInvoice для создания загрузки PDF для указанного счета. Этот метод автоматически генерирует необходимый HTTP-ответ для загрузки счета:

use Illuminate\Http\Request;
 
Route::get('/user/invoice/{invoice}', function (Request $request, string $invoiceId) {
return $request->user()->downloadInvoice($invoiceId);
});

По умолчанию все данные на счете происходят из данных клиента и счета, хранящихся в Stripe. Имя файла основано на значении конфигурации app.name. Однако вы можете настроить некоторые из этих данных, предоставив массив в качестве второго аргумента методу downloadInvoice. Этот массив позволяет настраивать информацию, такую как данные о вашей компании и продукции:

return $request->user()->downloadInvoice($invoiceId, [
'vendor' => 'Your Company',
'product' => 'Your Product',
'street' => 'Main Str. 1',
'location' => '2000 Antwerp, Belgium',
'phone' => '+32 499 00 00 00',
'email' => '[email protected]',
'url' => 'https://example.com',
'vendorVat' => 'BE123456789',
]);

Метод downloadInvoice также позволяет использовать настраиваемое имя файла через его третий аргумент. Это имя файла автоматически будет снабжено суффиксом .pdf:

return $request->user()->downloadInvoice($invoiceId, [], 'my-invoice');

Пользовательский рендеринг счетов

Cashier также позволяет использовать пользовательский рендерер счетов. По умолчанию Cashier использует реализацию DompdfInvoiceRenderer, которая использует библиотеку PHP dompdf для генерации счетов Cashier. Однако вы можете использовать любой рендерер по вашему желанию, реализовав интерфейс Laravel\Cashier\Contracts\InvoiceRenderer. Например, вы можете решить рендерить счет PDF с использованием вызова API стороннего сервиса рендеринга PDF:

use Illuminate\Support\Facades\Http;
use Laravel\Cashier\Contracts\InvoiceRenderer;
use Laravel\Cashier\Invoice;
 
class ApiInvoiceRenderer implements InvoiceRenderer
{
/**
* Отображение заданного счета и возврат сырых байтов PDF.
*/
public function render(Invoice $invoice, array $data = [], array $options = []): string
{
$html = $invoice->view($data)->render();
 
return Http::get('https://example.com/html-to-pdf', ['html' => $html])->get()->body();
}
}

После того как вы реализовали контракт рендерера счетов, вы должны обновить значение конфигурации cashier.invoices.renderer в файле конфигурации вашего приложения config/cashier.php. Это значение конфигурации должно быть установлено в имя класса вашей собственной реализации рендерера.

Проверка

Cashier Stripe также предоставляет поддержку Stripe Checkout. Stripe Checkout упрощает внедрение пользовательских страниц для приема платежей, предоставляя заранее созданную, размещенную страницу платежа.

В следующей документации содержится информация о том, как начать использовать Stripe Checkout с Cashier. Чтобы узнать больше о Stripe Checkout, вы также можете рассмотреть собственную документацию Stripe по Checkout.

Проверка продуктов

Вы можете выполнить оформление заказа для существующего продукта, созданного в вашей панели управления Stripe, используя метод checkout на экземпляре модели, поддерживающей выставление счетов. Метод checkout инициирует новую сессию Stripe Checkout. По умолчанию вам нужно передать идентификатор цены Stripe:

use Illuminate\Http\Request;
 
Route::get('/product-checkout', function (Request $request) {
return $request->user()->checkout('price_tshirt');
});

При необходимости вы также можете указать количество продукции:

use Illuminate\Http\Request;
 
Route::get('/product-checkout', function (Request $request) {
return $request->user()->checkout(['price_tshirt' => 15]);
});

Когда клиент посещает этот маршрут, его перенаправляют на страницу Checkout Stripe. По умолчанию, когда пользователь успешно завершает или отменяет покупку, его перенаправляют на ваш маршрут home, но вы можете указать настраиваемые URL обратного вызова, используя параметры success_url и cancel_url:

use Illuminate\Http\Request;
 
Route::get('/product-checkout', function (Request $request) {
return $request->user()->checkout(['price_tshirt' => 1], [
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});

При определении вашей опции success_url для оформления заказа вы можете указать Stripe добавить идентификатор сеанса Checkout как параметр строки запроса при вызове вашего URL. Для этого добавьте литерал {CHECKOUT_SESSION_ID} в строку запроса вашего success_url. Stripe заменит этот заполнитель фактическим идентификатором сеанса Checkout:

use Illuminate\Http\Request;
use Stripe\Checkout\Session;
use Stripe\Customer;
 
Route::get('/product-checkout', function (Request $request) {
return $request->user()->checkout(['price_tshirt' => 1], [
'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => route('checkout-cancel'),
]);
});
 
Route::get('/checkout-success', function (Request $request) {
$checkoutSession = $request->user()->stripe()->checkout->sessions->retrieve($request->get('session_id'));
 
return view('checkout.success', ['checkoutSession' => $checkoutSession]);
})->name('checkout-success');

Промо-коды

По умолчанию Stripe Checkout не позволяет промокоды, которые можно использовать пользователем. К счастью, есть простой способ включить их для вашей страницы Checkout. Для этого вы можете вызвать метод allowPromotionCodes:

use Illuminate\Http\Request;
 
Route::get('/product-checkout', function (Request $request) {
return $request->user()
->allowPromotionCodes()
->checkout('price_tshirt');
});

Проверка единичных платежей

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

use Illuminate\Http\Request;
 
Route::get('/charge-checkout', function (Request $request) {
return $request->user()->checkoutCharge(1200, 'T-Shirt', 5);
});

Внимание При использовании метода checkoutCharge Stripe всегда создает новый продукт и цену в вашей панели управления Stripe. Поэтому мы рекомендуем создавать продукты заранее в вашей панели управления Stripe и использовать метод checkout.

Проверка подписок

Внимание Для использования Stripe Checkout для подписок вам нужно включить вебхук customer.subscription.created в вашей панели управления Stripe. Этот вебхук создаст запись подписки в вашей базе данных и сохранит всю необходимую информацию о подписке.

Вы также можете использовать Stripe Checkout для инициирования подписок. После определения вашей подписки с использованием методов построения подписок Cashier, вы можете вызвать метод checkout. Когда клиент посещает этот маршрут, его перенаправляют на страницу Checkout Stripe:

use Illuminate\Http\Request;
 
Route::get('/subscription-checkout', function (Request $request) {
return $request->user()
->newSubscription('default', 'price_monthly')
->checkout();
});

Точно так же, как с проверкой продуктов, вы можете настроить URL успеха и отмены:

use Illuminate\Http\Request;
 
Route::get('/subscription-checkout', function (Request $request) {
return $request->user()
->newSubscription('default', 'price_monthly')
->checkout([
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});

Конечно же, вы также можете включить промокоды для оформления заказа подписки:

use Illuminate\Http\Request;
 
Route::get('/subscription-checkout', function (Request $request) {
return $request->user()
->newSubscription('default', 'price_monthly')
->allowPromotionCodes()
->checkout();
});

Внимание К сожалению, Stripe Checkout не поддерживает все параметры выставления счетов для подписок при создании подписок. Использование метода anchorBillingCycleOn в построителе подписок, установка поведения прораты или установка поведения оплаты не будет иметь никакого эффекта в сессиях Stripe Checkout. Пожалуйста, ознакомьтесь с документацией API-сессии Stripe Checkout, чтобы ознакомиться с доступными параметрами.

Stripe Checkout и пробные периоды

Конечно, вы можете определить пробный период при создании подписки, которая будет завершена с использованием Stripe Checkout:

$checkout = Auth::user()->newSubscription('default', 'price_monthly')
->trialDays(3)
->checkout();

Однако пробный период должен быть не менее 48 часов, что является минимальным количеством времени пробного периода, поддерживаемым Stripe Checkout.

Подписки и вебхуки

Помните, что Stripe и Cashier обновляют статусы подписок через вебхуки, поэтому существует возможность того, что подписка может еще не быть активной, когда клиент возвращается в приложение после ввода своих платежных данных. Для обработки этого сценария вы можете вывести сообщение, информирующее пользователя о том, что его платеж или подписка ожидает подтверждения.

Сбор идентификаторов налогоплательщика

Checkout также поддерживает сбор налогового идентификационного номера клиента. Чтобы включить это в сеансе оформления заказа, вызовите метод collectTaxIds при создании сессии:

$checkout = $user->collectTaxIds()->checkout('price_tshirt');

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

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

Гостевая проверка

С помощью метода Checkout::guest вы можете начать сеансы оформления заказа для гостей вашего приложения, у которых нет "аккаунта":

use Illuminate\Http\Request;
use Laravel\Cashier\Checkout;
 
Route::get('/product-checkout', function (Request $request) {
return Checkout::guest()->create('price_tshirt', [
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});

Точно так же, как и при создании сеансов оформления заказа для существующих пользователей, вы можете использовать дополнительные методы, доступные в экземпляре Laravel\Cashier\CheckoutBuilder, чтобы настроить сеанс гостевого оформления заказа:

use Illuminate\Http\Request;
use Laravel\Cashier\Checkout;
 
Route::get('/product-checkout', function (Request $request) {
return Checkout::guest()
->withPromotionCode('promo-code')
->create('price_tshirt', [
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});

После завершения оформления заказа гостем Stripe может отправить событие вебхука checkout.session.completed, поэтому убедитесь настроить ваш вебхук Stripe для отправки этого события в ваше приложение. После включения вебхука в панели управления Stripe вы можете обработать вебхук с помощью Cashier. Объект, содержащийся в теле вебхука, будет представлять собой объект checkout, который вы можете изучить для выполнения заказа вашего клиента.

Обработка неудачных платежей

Иногда платежи за подписки или одноразовые платежи могут не проходить успешно. Когда это происходит, Cashier вызывает исключение Laravel\Cashier\Exceptions\IncompletePayment, которое информирует вас о том, что это произошло. Поймав это исключение, у вас есть два варианта дальнейших действий.

Во-первых, вы можете перенаправить своего клиента на специальную страницу подтверждения платежа, которая включена в Cashier. Эта страница уже имеет связанный именованный маршрут, который зарегистрирован через службу Cashier. Так что вы можете поймать исключение IncompletePayment и перенаправить пользователя на страницу подтверждения платежа:

use Laravel\Cashier\Exceptions\IncompletePayment;
 
try {
$subscription = $user->newSubscription('default', 'price_monthly')
->create($paymentMethod);
} catch (IncompletePayment $exception) {
return redirect()->route(
'cashier.payment',
[$exception->payment->id, 'redirect' => route('home')]
);
}

На странице подтверждения платежа клиенту предложат ввести свои данные кредитной карты еще раз и выполнить любые дополнительные действия, требуемые Stripe, такие как подтверждение "3D Secure". После подтверждения платежа пользователь будет перенаправлен на URL, указанный в параметре redirect выше. При перенаправлении в URL добавятся переменные строки запроса message (строка) и success (целое число). Страница оплаты в настоящее время поддерживает следующие типы платежных методов:

  • Credit Cards
  • Alipay
  • Bancontact
  • BECS Direct Debit
  • EPS
  • Giropay
  • iDEAL
  • SEPA Direct Debit

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

Исключения платежа могут быть сгенерированы для следующих методов: charge, invoiceFor и invoice для моделей, использующих трейт Billable. При взаимодействии с подписками метод create в строителе подписок, а также методы incrementAndInvoice и swapAndInvoice в моделях Subscription и SubscriptionItem могут сгенерировать исключения о не завершенном платеже.

Определить, есть ли у существующей подписки не завершенный платеж, можно с использованием метода hasIncompletePayment на модели, для которой используется трейт Billable, или экземпляре подписки:

if ($user->hasIncompletePayment('default')) {
// ...
}
 
if ($user->subscription('default')->hasIncompletePayment()) {
// ...
}

Вы можете определить конкретный статус не завершенного платежа, изучив свойство payment на экземпляре исключения:

use Laravel\Cashier\Exceptions\IncompletePayment;
 
try {
$user->charge(1000, 'pm_card_threeDSecure2Required');
} catch (IncompletePayment $exception) {
// Получение статуса платежа...
$exception->payment->status;
 
// Проверка конкретных условий...
if ($exception->payment->requiresPaymentMethod()) {
// ...
} elseif ($exception->payment->requiresConfirmation()) {
// ...
}
}

Подтверждение платежей

Некоторые методы оплаты требуют дополнительных данных для подтверждения платежей. Например, методы оплаты SEPA требуют дополнительных данных "мандата" в процессе оплаты. Вы можете предоставить эти данные Cashier, используя метод withPaymentConfirmationOptions:

$subscription->withPaymentConfirmationOptions([
'mandate_data' => '...',
])->swap('price_xxx');

Вы можете ознакомиться с документацией Stripe API, чтобы просмотреть все опции, принимаемые при подтверждении платежей.

Усиленная аутентификация пользователя

Если ваш бизнес или один из ваших клиентов находится в Европе, вам нужно соблюдать правила сильной аутентификации клиента (Strong Customer Authentication, SCA) Евросоюза. Эти правила были введены в сентябре 2019 года Европейским союзом для предотвращения мошенничества при оплате. К счастью, Stripe и Cashier готовы к созданию приложений, совместимых с SCA.

Внимание Прежде чем начать, ознакомьтесь с руководством Stripe по PSD2 и SCA а также с документацией по новым API SCA.

Платежи, требующие дополнительного подтверждения

Правила SCA часто требуют дополнительной проверки для подтверждения и обработки платежа. Когда это происходит, Cashier генерирует исключение Laravel\Cashier\Exceptions\IncompletePayment, которое информирует вас о необходимости дополнительной проверки. Дополнительную информацию о том, как обрабатывать эти исключения, можно найти в документации по обработке неудавшихся платежей.

Экраны подтверждения платежа, представленные Stripe или Cashier, могут быть настроены под конкретный банк или поток платежей карты и могут включать дополнительное подтверждение карты, временное небольшое списание, отдельную аутентификацию устройства или другие формы проверки.

Состояние не завершено и просрочено

Когда для платежа требуется дополнительное подтверждение, подписка останется в состоянии incomplete или past_due, как указано в ее столбце базы данных stripe_status. Cashier автоматически активирует подписку клиента, как только подтверждение платежа завершится, и ваше приложение будет уведомлено Stripe через вебхук о его завершении.

Дополнительную информацию о состояниях incomplete и past_due можно найти в нашей дополнительной документации по этим состояниям.

Уведомления о платежах вне сеанса

Поскольку правила SCA требуют, чтобы клиенты время от времени подтверждали свои платежные данные, даже если их подписка активна, Cashier может отправлять уведомление клиенту, когда требуется подтверждение платежа вне сеанса. Например, это может произойти при продлении подписки. Уведомление об оплате Cashier можно включить, установив переменную окружения CASHIER_PAYMENT_NOTIFICATION в класс уведомления. По умолчанию это уведомление отключено. Конечно, Cashier включает класс уведомления, который вы можете использовать для этой цели, но вы можете предоставить свой собственный класс уведомления, если это необходимо:

CASHIER_PAYMENT_NOTIFICATION=Laravel\Cashier\Notifications\ConfirmPayment

Чтобы удостовериться, что уведомления о подтверждении платежа вне сеанса доставляются, убедитесь, что настроены вебхуки Stripe для вашего приложения и вебхук invoice.payment_action_required включен в вашей панели управления Stripe. Кроме того, ваша модель Billable также должна использовать трейт Illuminate\Notifications\Notifiable Laravel.

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

Stripe SDK

Многие объекты Cashier представляют собой оболочки вокруг объектов Stripe SDK. Если вы хотите взаимодействовать с объектами Stripe напрямую, вы можете удобно получить их, используя метод asStripe:

$stripeSubscription = $subscription->asStripeSubscription();
 
$stripeSubscription->application_fee_percent = 5;
 
$stripeSubscription->save();

Вы также можете использовать метод updateStripeSubscription для обновления подписки Stripe напрямую:

$subscription->updateStripeSubscription(['application_fee_percent' => 5]);

Если вы хотите использовать клиент Stripe\StripeClient напрямую, вы можете вызвать метод stripe из класса Cashier. Например, вы можете использовать этот метод, чтобы получить экземпляр StripeClient и получить список цен из вашей учетной записи Stripe:

use Laravel\Cashier\Cashier;
 
$prices = Cashier::stripe()->prices->all();

Тестирование

При тестировании приложения, использующего Cashier, вы можете мокировать фактические HTTP-запросы к API Stripe; однако это требует частичной реализации собственного поведения Cashier. Поэтому мы рекомендуем разрешить вашим тестам обращаться к фактическому API Stripe. Хотя это медленнее, это обеспечивает большую уверенность в том, что ваше приложение работает как ожидается, и любые медленные тесты можно разместить в их собственной группе тестирования PHPUnit.

При тестировании помните, что у Cashier уже есть отличный набор тестов, поэтому вам следует сосредоточиться только на тестировании потока подписки и оплаты вашего собственного приложения, а не на всем под lying Cashier.

Для начала добавьте testing версию вашего секрета Stripe в ваш файл phpunit.xml:

<env name="STRIPE_SECRET" value="sk_test_<your-key>"/>

Теперь, когда вы взаимодействуете с Cashier во время тестирования, он будет отправлять фактические API-запросы в ваше тестовое окружение Stripe. Для удобства предварительно заполните свой тестовый аккаунт Stripe подписками / ценами, которые вы можете использовать во время тестирования.

Примечание Чтобы протестировать различные сценарии выставления счетов, такие как отказы и ошибки кредитных карт, вы можете использовать обширный набор номеров карт и токенов для тестирования, предоставленных Stripe.