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

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

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

Введение

Внимание На данный момент Cashier Paddle поддерживает только Paddle Classic, который недоступен новым клиентам Paddle, если вы не свяжетесь со службой поддержки Paddle.

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

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

Обновление Cashier

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

Установка

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

composer require laravel/cashier-paddle

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

Песочница Paddle

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

При использовании среды Paddle Sandbox вы должны установить переменную окружения PADDLE_SANDBOX в true в файле .env вашего приложения:

PADDLE_SANDBOX=true

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

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

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

php artisan migrate

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

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

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

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

Настройка

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

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

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

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

use Illuminate\Database\Eloquent\Model;
use Laravel\Paddle\Billable;
class Team extends Model
{
use Billable;
}

API-ключи

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

PADDLE_VENDOR_ID=your-paddle-vendor-id
PADDLE_VENDOR_AUTH_CODE=your-paddle-vendor-auth-code
PADDLE_PUBLIC_KEY="your-paddle-public-key"
PADDLE_SANDBOX=true

Переменная окружения PADDLE_SANDBOX должна быть установлена в true, когда вы используете среду Paddle Sandbox. Переменная PADDLE_SANDBOX должна быть установлена в false, если вы развертываете приложение в продакшене и используете рабочую среду Paddle.

Paddle JS

Paddle использует свою собственную библиотеку JavaScript для инициализации виджета оформления заказа Paddle. Вы можете загрузить библиотеку JavaScript, разместив директиву Blade @paddleJS прямо перед закрывающим тегом </head> макета вашего приложения:

<head>
...
@paddleJS
</head>

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

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

CASHIER_CURRENCY=EUR

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

CASHIER_CURRENCY_LOCALE=nl_BE

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

Переопределение моделей по умолчанию

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

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

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

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

Основные концепции

Платежные ссылки

У Paddle нет обширного API CRUD для выполнения изменений состояния подписки. Поэтому большинство взаимодействий с Paddle выполняются через виджет оформления заказа. Прежде чем мы сможем отобразить виджет оформления заказа, нам нужно сгенерировать "платную ссылку" с помощью Cashier. "Платная ссылка" сообщит виджету оформления заказа о операции выставления счета, которую мы хотим выполнить:

use App\Models\User;
use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$payLink = $request->user()->newSubscription('default', $premium = 34567)
->returnTo(route('home'))
->create();
return view('billing', ['payLink' => $payLink]);
});

Cashier включает компонент Blade paddle-button. Мы можем передать URL платной ссылки в этот компонент как "проп". При нажатии этой кнопки отобразится виджет оформления заказа Paddle:

<x-paddle-button :url="$payLink" class="px-8 py-4">
Подписаться
</x-paddle-button>

По умолчанию это отобразит кнопку со стандартным стилем Paddle. Вы можете удалить весь стиль Paddle, добавив атрибут data-theme="none" к компоненту:

<x-paddle-button :url="$payLink" class="px-8 py-4" data-theme="none">
Подписаться
</x-paddle-button>

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

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

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

Ручное создание платежных ссылок

Вы также можете вручную отобразить платную ссылку, не используя встроенные компоненты Blade Laravel. Для начала сгенерируйте URL платной ссылки, как показано в предыдущих примерах:

$payLink = $request->user()->newSubscription('default', $premium = 34567)
->returnTo(route('home'))
->create();

Затем просто прикрепите URL платной ссылки к элементу a в вашем HTML:

<a href="#!" class="ml-4 paddle_button" data-override="{{ $payLink }}">
Оплата через Paddle
</a>

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

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

Встроенная оплата

Если вы не хотите использовать "наложение" стиля виджета оформления заказа Paddle, Paddle также предоставляет возможность отображения виджета встроенным образом. Хотя такой подход не позволяет вам настраивать поля HTML оформления заказа, он позволяет встроить виджет в ваше приложение.

Для удобства начала использования встроенного оформления заказа, Cashier включает компонент Blade paddle-checkout. Для начала вам следует сгенерировать платную ссылку и передать платную ссылку в атрибут override компонента:

<x-paddle-checkout :override="$payLink" class="w-full" />

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

<x-paddle-checkout :override="$payLink" class="w-full" height="500" />

Встроенная оплата без платежных ссылок

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

@php
$options = [
'product' => $productId,
'title' => 'Название продукта',
];
@endphp
 
<x-paddle-checkout :options="$options" class="w-full" />

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

Внимание Если вы хотите также использовать опцию passthrough при указании пользовательских параметров, вы должны предоставить ключ / значение в виде массива в качестве ее значения. Cashier автоматически обработает преобразование массива в строку JSON. Кроме того, опция customer_id для прохода зарезервирована для внутреннего использования Cashier.

Ручное создание встроенной оплаты

Вы также можете вручную отобразить встроенное оформление заказа, не используя встроенные компоненты Blade Laravel. Для начала сгенерируйте URL платной ссылки как показано в предыдущих примерах.

Затем вы можете использовать Paddle.js для инициализации оформления заказа. Чтобы сделать этот пример простым, мы продемонстрируем это с использованием Alpine.js; однако вы свободны адаптировать этот пример под свой стек фронтенда:

<div class="paddle-checkout" x-data="{}" x-init="
Paddle.Checkout.open({
override: {{ $payLink }},
method: 'inline',
frameTarget: 'paddle-checkout',
frameInitialHeight: 366,
frameStyle: 'width: 100%; background-color: transparent; border: none;'
});
">
</div>

Идентификация пользователя

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

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

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

$subscription = $user->subscription('default');
$subscription->paddleEmail();
$subscription->paymentMethod();
$subscription->cardBrand();
$subscription->cardLastFour();
$subscription->cardExpirationDate();

В настоящее время нет способа изменить адрес электронной почты пользователя через API Paddle. Когда пользователь хочет обновить свой адрес электронной почты в Paddle, единственный способ сделать это - связаться с службой поддержки Paddle. При общении с Paddle им необходимо предоставить значение paddleEmail подписки для помощи Paddle в обновлении правильного пользователя.

Цены

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

use Laravel\Paddle\Cashier;
$prices = Cashier::productPrices([123, 456]);

Валюта будет определена на основе IP-адреса запроса; однако вы можете дополнительно указать конкретную страну для получения цен:

use Laravel\Paddle\Cashier;
$prices = Cashier::productPrices([123, 456], ['customer_country' => 'BE']);

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

<ul>
@foreach ($prices as $price)
<li>{{ $price->product_title }} - {{ $price->price()->gross() }}</li>
@endforeach
</ul>

Вы также можете отобразить чистую цену (без налога) и отобразить сумму налога отдельно:

<ul>
@foreach ($prices as $price)
<li>{{ $price->product_title }} - {{ $price->price()->net() }} (+ {{ $price->price()->tax() }} tax)</li>
@endforeach
</ul>

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

<ul>
@foreach ($prices as $price)
<li>{{ $price->product_title }} - Initial: {{ $price->initialPrice()->gross() }} - Recurring: {{ $price->recurringPrice()->gross() }}</li>
@endforeach
</ul>

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

Клиенты

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

use App\Models\User;
$prices = User::find(1)->productPrices([123, 456]);

Внутренне Cashier будет использовать метод paddleCountry пользователя, чтобы извлечь цены в его валюте. Так, например, пользователь, проживающий в Соединенных Штатах, увидит цены в USD, в то время как пользователь в Бельгии увидит цены в EUR. Если не удается найти соответствующую валюту, будет использоваться валюта продукта по умолчанию. Вы можете настроить все цены продукта или плана подписки в панели управления Paddle.

Купоны

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

use Laravel\Paddle\Cashier;
$prices = Cashier::productPrices([123, 456], [
'coupons' => 'SUMMERSALE,20PERCENTOFF'
]);

Затем отобразите рассчитанные цены, используя метод price:

<ul>
@foreach ($prices as $price)
<li>{{ $price->product_title }} - {{ $price->price()->gross() }}</li>
@endforeach
</ul>

Вы можете отобразить первоначально указанные цены (без учета скидок по купону) с использованием метода listPrice:

<ul>
@foreach ($prices as $price)
<li>{{ $price->product_title }} - {{ $price->listPrice()->gross() }}</li>
@endforeach
</ul>

Внимание При использовании API цен Paddle разрешено применять купоны только к одноразовым товарам и не к подписочным планам.

Клиенты

Настройки клиента

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

/**
* Получает электронный адрес клиента для связи с Paddle.
*/
public function paddleEmail(): string|null
{
return $this->email;
}
 
/**
* Получает страну клиента для связи с Paddle.
*
* Должен быть двухбуквенным кодом. См. ссылку ниже для поддерживаемых стран.
*
* @link https://developer.paddle.com/reference/platform-parameters/supported-countries
*/
public function paddleCountry(): string|null
{
// ...
}
 
/**
* Получает почтовый код клиента для связи с Paddle.
*
* См. ссылку ниже для стран, где это требуется.
*
* @link https://developer.paddle.com/reference/platform-parameters/supported-countries#countries-requiring-postcode
*/
public function paddlePostcode(): string|null
{
// ...
}

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

Подписки

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

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

use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$payLink = $request->user()->newSubscription('default', $premium = 12345)
->returnTo(route('home'))
->create();
return view('billing', ['payLink' => $payLink]);
});

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

Метод create создаст платную ссылку, которую вы можете использовать для создания кнопки оплаты. Кнопку оплаты можно создать с использованием компонента Blade paddle-button Blade component, который включен в Cashier Paddle:

<x-paddle-button :url="$payLink" class="px-8 py-4">
Подписаться
</x-paddle-button>

После завершения пользователем оформления заказа, будет отправлен webhook subscription_created из Paddle. Cashier получит этот webhook и настроит подписку для вашего клиента. Чтобы убедиться, что все webhook-ы правильно получаются и обрабатываются вашим приложением, убедитесь, что вы правильно настроили обработку webhook-ов.

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

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

$payLink = $user->newSubscription('default', $monthly = 12345)
->returnTo(route('home'))
->create([
'vat_number' => $vatNumber,
]);

Купоны

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

$payLink = $user->newSubscription('default', $monthly = 12345)
->returnTo(route('home'))
->withCoupon('code')
->create();

Метаданные

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

$payLink = $user->newSubscription('default', $monthly = 12345)
->returnTo(route('home'))
->withMetadata(['key' => 'value'])
->create();

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

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

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

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()) {
// ...
}

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

if ($user->subscribedToPlan($monthly = 12345, 'default')) {
// ...
}

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

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

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

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

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

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

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

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

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

Просроченный статус

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

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

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

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

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

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

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

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

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

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

Subscription::query()->active();
Subscription::query()->onTrial();
Subscription::query()->notOnTrial();
Subscription::query()->pastDue();
Subscription::query()->recurring();
Subscription::query()->ended();
Subscription::query()->paused();
Subscription::query()->notPaused();
Subscription::query()->onPausedGracePeriod();
Subscription::query()->notOnPausedGracePeriod();
Subscription::query()->cancelled();
Subscription::query()->notCancelled();
Subscription::query()->onGracePeriod();
Subscription::query()->notOnGracePeriod();

Единичные платежи за подписку

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

$response = $user->subscription('default')->charge(12.99, 'Support Add-on');

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

Обновление информации о платеже

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

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

Затем вы можете использовать сгенерированный URL в сочетании с предоставленным Cashier компонентом Blade paddle-button, чтобы позволить пользователю запустить виджет Paddle и обновить свою информацию об оплате:

<x-paddle-button :url="$updateUrl" class="px-8 py-4">
Update Card
</x-paddle-button>

Когда пользователь закончит обновление своей информации, будет отправлен webhook subscription_updated от Paddle, и данные подписки будут обновлены в базе данных вашего приложения.

Смена тарифного плана

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

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

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

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

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

Проработка

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

$user->subscription('default')->noProrate()->swap($premium = 34567);

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

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

$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);

Модификаторы подписок

Модификаторы подписок позволяют вам реализовать метрическую тарификацию или расширять подписки с дополнениями.

Например, вы можете предложить дополнительную услугу "Премиум-поддержка" в дополнение к вашей стандартной подписке. Вы можете создать такой модификатор следующим образом:

$modifier = $user->subscription('default')->newModifier(12.99)->create();

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

$modifier = $user->subscription('default')->newModifier(12.99)
->description('modify_10x/cashier-paddle.code_test_5')
->create();

Чтобы проиллюстрировать, как реализовать метрическую тарификацию с использованием модификаторов, представьте, что ваше приложение взимает плату за каждое отправленное пользователем SMS-сообщение. Сначала вам нужно создать план на $0 в вашей панели управления Paddle. После того как пользователь подписался на этот план, вы можете добавить модификаторы, представляющие каждый отдельный платеж, к подписке:

$modifier = $user->subscription('default')->newModifier(0.99)
->description('modify_10x/cashier-paddle.code_test_6')
->oneTime()
->create();

Как видите, мы вызвали метод oneTime при создании этого модификатора. Этот метод обеспечит, что модификатор будет взиматься только один раз и не будет повторяться в каждом биллинговом интервале.

Получение модификаторов

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

$modifiers = $user->subscription('default')->modifiers();
 
foreach ($modifiers as $modifier) {
$modifier->amount(); // $0.99
$modifier->description; // Новое текстовое сообщение
}

Удаление модификаторов

Модификаторы можно удалить, вызвав метод delete на экземпляре Laravel\Paddle\Modifier:

$modifier->delete();

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

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

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

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

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

$user->subscription('swimming')->swap($swimmingYearly = 34567);

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

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

Приостановка подписок

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

С методом оплаты в начале

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

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

use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$payLink = $request->user()->newSubscription('default', $monthly = 12345)
->returnTo(route('home'))
->trialDays(10)
->create();
return view('billing', ['payLink' => $payLink]);
});

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

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

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

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

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

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

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

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

Без метода оплаты в начале

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

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

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

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

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

use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$payLink = $user->newSubscription('default', $monthly = 12345)
->returnTo(route('home'))
->create();
return view('billing', ['payLink' => $payLink]);
});

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

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

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

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

Внимание Нет способа продлить или изменить пробный период подписки Paddle после ее создания.

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

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

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

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

  • Подписка создана
  • Подписка обновлена
  • Подписка отменена
  • Оплата прошла успешно
  • Успешная оплата подписки

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

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

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

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

Вебхуки и локальная разработка

Чтобы Paddle могла отправлять вебхуки вашему приложению во время локальной разработки, вам нужно предоставить доступ к вашему приложению с использованием сервиса обмена сайтами, такого как Ngrok или Expose. Если вы разрабатываете ваше приложение локально с использованием Laravel Sail, вы можете использовать команду обмена сайтом Sail.

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

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

  • Laravel\Paddle\Events\WebhookReceived
  • Laravel\Paddle\Events\WebhookHandled

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

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

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

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

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

  • Laravel\Paddle\Events\PaymentSucceeded
  • Laravel\Paddle\Events\SubscriptionPaymentSucceeded
  • Laravel\Paddle\Events\SubscriptionCreated
  • Laravel\Paddle\Events\SubscriptionUpdated
  • Laravel\Paddle\Events\SubscriptionCancelled

Вы также можете переопределить встроенный маршрут вебхука, задав переменную среды CASHIER_WEBHOOK в файле .env вашего приложения. Значение должно быть полным URL вашего маршрута вебхука и должно соответствовать URL, установленному в вашей панели управления Paddle:

CASHIER_WEBHOOK=https://example.com/my-paddle-webhook-url

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

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

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

Единичные платежи

Простая оплата

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

use Illuminate\Http\Request;
Route::get('/store', function (Request $request) {
return view('store', [
'payLink' => $user->charge(12.99, 'Action Figure')
]);
});

После создания платежной ссылки вы можете использовать предоставленный Cashier компонент Blade paddle-button, чтобы позволить пользователю инициировать виджет Paddle и завершить платеж:

<x-paddle-button :url="$payLink" class="px-8 py-4">
Купить
</x-paddle-button>

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

$payLink = $user->charge(12.99, 'Action Figure', [
'custom_option' => $value,
]);

Платежи выполняются в валюте, указанной в параметре конфигурации cashier.currency. По умолчанию это установлено в USD. Вы можете изменить валюту по умолчанию, определив переменную среды CASHIER_CURRENCY в файле .env вашего приложения:

CASHIER_CURRENCY=EUR

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

$payLink = $user->charge([
'USD:19.99',
'EUR:15.99',
], 'Action Figure');

Оплата продуктов

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

use Illuminate\Http\Request;
Route::get('/store', function (Request $request) {
return view('store', [
'payLink' => $request->user()->chargeProduct($productId = 123)
]);
});

Затем вы можете предоставить платежную ссылку компоненту Blade paddle-button, чтобы позволить пользователю инициировать виджет Paddle и завершить платеж:

<x-paddle-button :url="$payLink" class="px-8 py-4">
Купить
</x-paddle-button>

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

$payLink = $user->chargeProduct($productId, [
'custom_option' => $value,
]);

Возврат заказов

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

use App\Models\User;
$user = User::find(1);
$receipt = $user->receipts()->first();
$refundRequestId = $user->refund($receipt->order_id);

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

$receipt = $user->receipts()->first();
 
$refundRequestId = $user->refund(
$receipt->order_id, 5.00, 'Неиспользованное время продукта'
);

Примечание Вы можете использовать $refundRequestId в качестве ссылки для возврата при обращении в службу поддержки Paddle.

Квитанции

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

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

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

<table>
@foreach ($receipts as $receipt)
<tr>
<td>{{ $receipt->paid_at->toFormattedDateString() }}</td>
<td>{{ $receipt->amount() }}</td>
<td><a href="{{ $receipt->receipt_url }}" target="_blank">Скачать</a></td>
</tr>
@endforeach
</table>

Прошлые и предстоящие платежи

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

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

Оба эти метода вернут экземпляр Laravel\Paddle\Payment; однако nextPayment вернет null, когда цикл выставления счета завершится (например, когда подписка будет отменена):

Next payment: {{ $nextPayment->amount() }} due on {{ $nextPayment->date()->format('d/m/Y') }}

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

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

В качестве альтернативы вы можете выполнять более точную настройку, прослушивая событие Paddle subscription_payment_failed через событие WebhookReceived, отправляемое Cashier. Вы также должны убедиться, что включена опция "Платеж по подписке не выполнен" в настройках вебхука вашей панели управления Paddle:

<?php
 
namespace App\Listeners;
 
use Laravel\Paddle\Events\WebhookReceived;
 
class PaddleEventListener
{
/**
* Обрабатывает полученные вебхуки Paddle.
*/
public function handle(WebhookReceived $event): void
{
if ($event->payload['alert_name'] === 'subscription_payment_failed') {
// Обрабатывает неудачную оплату подписки...
}
}
}

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

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

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

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

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