- Введение
- Обновление Cashier
- Установка
- Настройка
- Быстрый старт
- Сессии оформления заказа
- Предварительный просмотр цен
- Клиенты
- Подписки
- Пробные периоды подписок
- Обработка вебхуков Paddle
- Одноразовые списания
- Транзакции
- Тестирование
#Введение
Эта документация предназначена для интеграции Cashier Paddle 2.x с Paddle Billing. Если вы всё ещё используете Paddle Classic, вам следует использовать Cashier Paddle 1.x.
Laravel Cashier Paddle предоставляет выразительный, удобный интерфейс для сервисов подписочного биллинга Paddle. Он обрабатывает почти весь рутинный код для подписочного биллинга, который вам не хочется писать. Помимо базового управления подписками, Cashier поддерживает: смену подписок, "количество" подписок, приостановку подписок, льготные периоды отмены и многое другое.
Перед тем как углубляться в Cashier Paddle, рекомендуем ознакомиться с концептуальными руководствами и API документацией Paddle.
#Обновление Cashier
При обновлении до новой версии Cashier важно внимательно изучить руководство по обновлению.
#Установка
Сначала установите пакет Cashier для Paddle с помощью менеджера пакетов Composer:
composer require laravel/cashier-paddle
Далее опубликуйте миграции Cashier с помощью Artisan-команды vendor:publish:
php artisan vendor:publish --tag="cashier-migrations"
Затем выполните миграции базы данных вашего приложения. Миграции Cashier создадут новую таблицу customers. Кроме того, будут созданы таблицы subscriptions и subscription_items для хранения всех подписок ваших клиентов. Наконец, будет создана таблица transactions для хранения всех транзакций Paddle, связанных с вашими клиентами:
php artisan migrate
Чтобы Cashier корректно обрабатывал все события Paddle, не забудьте настроить обработку вебхуков Cashier.
#Песочница Paddle
Во время локальной и промежуточной разработки следует зарегистрировать аккаунт в Paddle Sandbox. Этот аккаунт предоставит вам изолированную среду для тестирования и разработки приложений без реальных платежей. Вы можете использовать тестовые номера карт Paddle для имитации различных сценариев оплаты.
При использовании среды Paddle Sandbox установите переменную окружения PADDLE_SANDBOX в значение true в файле .env вашего приложения:
PADDLE_SANDBOX=true
После завершения разработки вы можете подать заявку на аккаунт продавца Paddle. Перед запуском приложения в продакшен Paddle должен одобрить домен вашего приложения.
#Настройка
#Модель Billable
Перед использованием 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_CLIENT_SIDE_TOKEN=your-paddle-client-side-token
PADDLE_API_KEY=your-paddle-api-key
PADDLE_RETAIN_KEY=your-paddle-retain-key
PADDLE_WEBHOOK_SECRET="your-paddle-webhook-secret"
PADDLE_SANDBOX=true
Переменная окружения PADDLE_SANDBOX должна быть установлена в true, если вы используете песочницу Paddle. Если вы разворачиваете приложение в продакшен и используете живую среду Paddle, установите PADDLE_SANDBOX в false.
Переменная PADDLE_RETAIN_KEY является необязательной и должна быть установлена только если вы используете Paddle с Retain.
#Paddle JS
Paddle использует собственную JavaScript-библиотеку для инициализации виджета оформления заказа. Вы можете подключить эту библиотеку, разместив директиву Blade @paddleJS непосредственно перед закрывающим тегом </head> в вашем шаблоне:
<head>
...
@paddleJS
</head>
#Настройка валюты
Вы можете указать локаль, которая будет использоваться при форматировании денежных значений для отображения в счетах. Внутри Cashier использует класс PHP NumberFormatter для установки локали валюты:
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. Обычно это делается в методе boot класса App\Providers\AppServiceProvider вашего приложения:
use App\Models\Cashier\Subscription;
use App\Models\Cashier\Transaction;
/**
* Инициализация сервисов приложения.
*/
public function boot(): void
{
Cashier::useSubscriptionModel(Subscription::class);
Cashier::useTransactionModel(Transaction::class);
}
#Быстрый старт
#Продажа продуктов
Перед использованием Paddle Checkout необходимо определить продукты с фиксированными ценами в вашей панели Paddle. Также следует настроить обработку вебхуков Paddle.
Предложение биллинга продуктов и подписок через ваше приложение может показаться сложным. Однако благодаря Cashier и Checkout Overlay Paddle вы можете легко создать современные и надёжные платежные интеграции.
Для списания средств с клиентов за одноразовые продукты мы используем Cashier для создания платежа через Checkout Overlay Paddle, где клиент вводит данные оплаты и подтверждает покупку. После успешной оплаты через Checkout Overlay клиент будет перенаправлен на URL успеха в вашем приложении:
use Illuminate\Http\Request;
Route::get('/buy', function (Request $request) {
$checkout = $request->user()->checkout('pri_deluxe_album')
->returnTo(route('dashboard'));
return view('buy', ['checkout' => $checkout]);
})->name('checkout');
Как видно из примера, мы используем метод checkout Cashier для создания объекта оформления заказа, который покажет клиенту Paddle Checkout Overlay для заданного "идентификатора цены". В Paddle "цены" — это определённые цены для конкретных продуктов.
При необходимости метод checkout автоматически создаст клиента в Paddle и свяжет эту запись с соответствующим пользователем в базе данных вашего приложения. После завершения сессии оформления заказа клиент будет перенаправлен на специальную страницу успеха, где вы можете показать информационное сообщение.
В представлении buy мы добавим кнопку для отображения Checkout Overlay. Компонент Blade paddle-button включён в Cashier Paddle, но вы также можете вручную отрисовать оверлей оформления заказа:
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Buy Product
</x-paddle-button>
#Передача метаданных в Paddle Checkout
При продаже продуктов часто нужно отслеживать завершённые заказы и купленные товары через модели Cart и Order, определённые в вашем приложении. При перенаправлении клиентов в Paddle Checkout Overlay для завершения покупки может потребоваться передать идентификатор существующего заказа, чтобы связать завершённую покупку с соответствующим заказом после возврата клиента в приложение.
Для этого можно передать массив пользовательских данных в метод checkout. Допустим, в нашем приложении создаётся ожидающий Order при начале процесса оформления заказа пользователем. Помните, что модели Cart и Order в этом примере иллюстративны и не предоставляются Cashier. Вы можете реализовать эти концепции в соответствии с потребностями вашего приложения:
use App\Models\Cart;
use App\Models\Order;
use Illuminate\Http\Request;
Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) {
$order = Order::create([
'cart_id' => $cart->id,
'price_ids' => $cart->price_ids,
'status' => 'incomplete',
]);
$checkout = $request->user()->checkout($order->price_ids)
->customData(['order_id' => $order->id]);
return view('billing', ['checkout' => $checkout]);
})->name('checkout');
Как видно из примера, при начале оформления заказа мы передаём все связанные с корзиной / заказом идентификаторы цен Paddle в метод checkout. Конечно, ваше приложение отвечает за связывание этих элементов с "корзиной" или заказом по мере добавления клиентом. Также мы передаём ID заказа в Paddle Checkout Overlay через метод customData.
Вероятно, вы захотите пометить заказ как "завершённый" после окончания оформления. Для этого можно слушать вебхуки, отправляемые Paddle и транслируемые через события Cashier, чтобы сохранять информацию о заказах в базе данных.
Для начала слушайте событие TransactionCompleted, отправляемое Cashier. Обычно слушатель регистрируется в методе boot одного из сервис-провайдеров вашего приложения:
use App\Listeners\CompleteOrder;
use Illuminate\Support\Facades\Event;
use Laravel\Paddle\Events\TransactionCompleted;
/**
* Инициализация сервисов приложения.
*/
public function boot(): void
{
Event::listen(TransactionCompleted::class, CompleteOrder::class);
}
В этом примере слушатель CompleteOrder может выглядеть так:
namespace App\Listeners;
use App\Models\Order;
use Laravel\Cashier\Cashier;
use Laravel\Cashier\Events\TransactionCompleted;
class CompleteOrder
{
/**
* Обработка входящего события вебхука Cashier.
*/
public function handle(TransactionCompleted $event): void
{
$orderId = $event->payload['data']['custom_data']['order_id'] ?? null;
$order = Order::findOrFail($orderId);
$order->update(['status' => 'completed']);
}
}
Для получения дополнительной информации обратитесь к документации Paddle о данных, содержащихся в событии transaction.completed.
#Продажа подписок
Перед использованием Paddle Checkout необходимо определить продукты с фиксированными ценами в вашей панели Paddle. Также следует настроить обработку вебхуков Paddle.
Предложение биллинга продуктов и подписок через ваше приложение может показаться сложным. Однако благодаря Cashier и Checkout Overlay Paddle вы можете легко создать современные и надёжные платежные интеграции.
Чтобы узнать, как продавать подписки с помощью Cashier и Paddle Checkout Overlay, рассмотрим простой сценарий сервиса подписки с базовым ежемесячным (price_basic_monthly) и годовым (price_basic_yearly) тарифами. Эти две цены могут быть сгруппированы под продуктом "Basic" (pro_basic) в вашей панели Paddle. Кроме того, сервис может предлагать тариф Expert как pro_expert.
Сначала рассмотрим, как клиент может подписаться на сервис. Представим, что клиент нажимает кнопку "подписаться" для базового плана на странице тарифов вашего приложения. Эта кнопка вызовет Paddle Checkout Overlay для выбранного плана. Для начала создадим сессию оформления заказа через метод checkout:
use Illuminate\Http\Request;
Route::get('/subscribe', function (Request $request) {
$checkout = $request->user()->checkout('price_basic_monthly')
->returnTo(route('dashboard'));
return view('subscribe', ['checkout' => $checkout]);
})->name('subscribe');
В представлении subscribe мы добавим кнопку для отображения Checkout Overlay. Компонент Blade paddle-button включён в Cashier Paddle, но вы также можете вручную отрисовать оверлей оформления заказа:
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Subscribe
</x-paddle-button>
Теперь, когда кнопка "Подписаться" нажата, клиент сможет ввести данные оплаты и начать подписку. Чтобы узнать, когда подписка действительно началась (так как некоторые способы оплаты требуют времени на обработку), следует также настроить обработку вебхуков Cashier.
Теперь, когда клиенты могут начать подписки, нужно ограничить доступ к определённым частям приложения только для подписанных пользователей. Конечно, текущий статус подписки пользователя можно проверить с помощью метода subscribed, предоставляемого трейтом Billable Cashier:
@if ($user->subscribed())
<p>You are subscribed.</p>
@endif
Мы даже можем легко проверить, подписан ли пользователь на конкретный продукт или цену:
@if ($user->subscribedToProduct('pro_basic'))
<p>You are subscribed to our Basic product.</p>
@endif
@if ($user->subscribedToPrice('price_basic_monthly'))
<p>You are subscribed to our monthly Basic plan.</p>
@endif
#Создание middleware для проверки подписки
Для удобства вы можете создать middleware, который проверяет, подписан ли пользователь, отправляющий запрос. После определения middleware вы можете назначить его маршруту, чтобы запретить доступ неподписанным пользователям:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class Subscribed
{
/**
* Обработка входящего запроса.
*/
public function handle(Request $request, Closure $next): Response
{
if (! $request->user()?->subscribed()) {
// Перенаправить пользователя на страницу биллинга с предложением подписаться...
return redirect('/subscribe');
}
return $next($request);
}
}
После определения middleware вы можете назначить его маршруту:
use App\Http\Middleware\Subscribed;
Route::get('/dashboard', function () {
// ...
})->middleware([Subscribed::class]);
#Позволить клиентам управлять своим тарифным планом
Клиенты могут захотеть сменить тарифный план на другой продукт или "уровень". В нашем примере выше мы хотим позволить клиенту перейти с ежемесячной подписки на годовую. Для этого нужно реализовать кнопку, ведущую на следующий маршрут:
use Illuminate\Http\Request;
Route::put('/subscription/{price}/swap', function (Request $request, $price) {
$user->subscription()->swap($price); // В этом примере "$price" — "price_basic_yearly".
return redirect()->route('dashboard');
})->name('subscription.swap');
Кроме смены тарифов, нужно позволить клиентам отменять подписку. Аналогично смене тарифов, предоставьте кнопку, ведущую на следующий маршрут:
use Illuminate\Http\Request;
Route::put('/subscription/cancel', function (Request $request, $price) {
$user->subscription()->cancel();
return redirect()->route('dashboard');
})->name('subscription.cancel');
Теперь ваша подписка будет отменена в конце текущего расчетного периода.
Если вы настроили обработку вебхуков Cashier, он автоматически будет синхронизировать таблицы базы данных, связанные с Cashier, анализируя входящие вебхуки от Paddle. Например, если вы отмените подписку клиента через панель Paddle, Cashier получит соответствующий вебхук и отметит подписку как "отменённую" в базе данных вашего приложения.
#Сессии оформления заказа
Большинство операций по выставлению счетов клиентам выполняется через "чекауты" с использованием виджета Paddle Checkout Overlay или встроенного оформления заказа.
Перед обработкой платежей через Paddle следует определить ссылку на оплату по умолчанию в настройках оформления заказа вашей панели Paddle.
#Оверлей оформления заказа
Перед отображением виджета Checkout Overlay необходимо создать сессию оформления заказа с помощью Cashier. Сессия оформления заказа сообщает виджету, какую операцию биллинга нужно выполнить:
use Illuminate\Http\Request;
Route::get('/buy', function (Request $request) {
$checkout = $user->checkout('pri_34567')
->returnTo(route('dashboard'));
return view('billing', ['checkout' => $checkout]);
});
Cashier включает компонент Blade paddle-button Blade component. Вы можете передать сессию оформления заказа этому компоненту как "prop". При нажатии на кнопку будет отображён виджет оформления заказа Paddle:
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Subscribe
</x-paddle-button>
По умолчанию виджет отображается с использованием стилей по умолчанию Paddle. Вы можете настроить виджет, добавив поддерживаемые Paddle атрибуты, например атрибут data-theme='light' к компоненту:
<x-paddle-button :url="$payLink" class="px-8 py-4" data-theme="light">
Subscribe
</x-paddle-button>
Виджет оформления заказа Paddle работает асинхронно. После создания подписки в виджете Paddle отправит вашему приложению вебхук, чтобы вы могли корректно обновить состояние подписки в базе данных. Поэтому важно правильно настроить вебхуки для обработки изменений состояния от Paddle.
После изменения состояния подписки задержка получения соответствующего вебхука обычно минимальна, но в вашем приложении следует учитывать, что подписка пользователя может быть недоступна сразу после завершения оформления.
#Вручную отрисовать оверлей оформления заказа
Вы также можете вручную отрисовать оверлей оформления заказа без использования встроенных компонентов Blade Laravel. Для начала создайте сессию оформления заказа как показано в предыдущих примерах:
use Illuminate\Http\Request;
Route::get('/buy', function (Request $request) {
$checkout = $user->checkout('pri_34567')
->returnTo(route('dashboard'));
return view('billing', ['checkout' => $checkout]);
});
Далее вы можете использовать Paddle.js для инициализации оформления заказа. В этом примере мы создадим ссылку с классом paddle_button. Paddle.js обнаружит этот класс и отобразит оверлей оформления заказа при клике на ссылку:
<?php
$items = $checkout->getItems();
$customer = $checkout->getCustomer();
$custom = $checkout->getCustomData();
?>
<a
href='#!'
class='paddle_button'
data-items='{!! json_encode($items) !!}'
@if ($customer) data-customer-id='{{ $customer->paddle_id }}' @endif
@if ($custom) data-custom-data='{{ json_encode($custom) }}' @endif
@if ($returnUrl = $checkout->getReturnUrl()) data-success-url='{{ $returnUrl }}' @endif
>
Buy Product
</a>
#Встроенное оформление заказа
Если вы не хотите использовать виджет оформления заказа в стиле "оверлей", Paddle также предоставляет возможность встроенного отображения виджета. Хотя этот способ не позволяет изменять HTML-поля оформления заказа, он позволяет встроить виджет непосредственно в ваше приложение.
Для удобства Cashier включает компонент Blade paddle-checkout. Для начала следует создать сессию оформления заказа:
use Illuminate\Http\Request;
Route::get('/buy', function (Request $request) {
$checkout = $user->checkout('pri_34567')
->returnTo(route('dashboard'));
return view('billing', ['checkout' => $checkout]);
});
Затем вы можете передать сессию оформления заказа в атрибут checkout компонента:
<x-paddle-checkout :checkout="$checkout" class="w-full" />
Чтобы изменить высоту встроенного компонента оформления заказа, передайте атрибут height в компонент Blade:
<x-paddle-checkout :checkout="$checkout" class="w-full" height="500" />
Обратитесь к руководству Paddle по встроенному оформлению заказа и доступным настройкам оформления заказа для подробностей о настройках встроенного оформления заказа.
#Вручную отрисовать встроенное оформление заказа
Вы также можете вручную отрисовать встроенное оформление заказа без использования встроенных компонентов Blade Laravel. Для начала создайте сессию оформления заказа как показано в предыдущих примерах:
use Illuminate\Http\Request;
Route::get('/buy', function (Request $request) {
$checkout = $user->checkout('pri_34567')
->returnTo(route('dashboard'));
return view('billing', ['checkout' => $checkout]);
});
Далее вы можете использовать Paddle.js для инициализации оформления заказа. В этом примере мы покажем использование Alpine.js, но вы можете адаптировать пример под ваш фронтенд-стек:
<?php
$options = $checkout->options();
$options['settings']['frameTarget'] = 'paddle-checkout';
$options['settings']['frameInitialHeight'] = 366;
?>
<div class="paddle-checkout" x-data="{}" x-init="
Paddle.Checkout.open(@json($options));
">
</div>
#Оформление заказа гостем
Иногда нужно создать сессию оформления заказа для пользователей, которым не требуется аккаунт в вашем приложении. Для этого используйте метод guest:
use Illuminate\Http\Request;
use Laravel\Paddle\Checkout;
Route::get('/buy', function (Request $request) {
$checkout = Checkout::guest('pri_34567')
->returnTo(route('home'));
return view('billing', ['checkout' => $checkout]);
});
Затем вы можете передать сессию оформления заказа в компоненты Blade Paddle button или inline checkout.
#Предварительный просмотр цен
Paddle позволяет настраивать цены по валютам, фактически давая возможность устанавливать разные цены для разных стран. Cashier Paddle позволяет получить все эти цены с помощью метода previewPrices. Этот метод принимает идентификаторы цен, для которых вы хотите получить значения:
use Laravel\Paddle\Cashier;
$prices = Cashier::previewPrices(['pri_123', 'pri_456']);
Валюта будет определена на основе IP-адреса запроса; однако вы можете опционально указать конкретную страну для получения цен:
use Laravel\Paddle\Cashier;
$prices = Cashier::productPrices(['pri_123', 'pri_456'], ['address' => [
'country_code' => 'BE',
'postal_code' => '1234',
]]);
После получения цен вы можете отображать их любым удобным способом:
<ul>
@foreach ($prices as $price)
<li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
@endforeach
</ul>
Вы также можете отдельно отображать итоговую сумму и сумму налога:
<ul>
@foreach ($prices as $price)
<li>{{ $price->product_title }} - {{ $price->subtotal() }} (+ {{ $price->tax() }} tax)</li>
@endforeach
</ul>
Для дополнительной информации смотрите документацию Paddle по предварительному просмотру цен.
#Предварительный просмотр цен для клиентов
Если пользователь уже является клиентом и вы хотите показать цены, которые применяются к этому клиенту, вы можете получить цены напрямую из экземпляра клиента:
use App\Models\User;
$prices = User::find(1)->previewPrices(['pri_123', 'pri_456']);
Внутри Cashier использует идентификатор клиента пользователя для получения цен в его валюте. Например, пользователь из США увидит цены в долларах США, а пользователь из Бельгии — в евро. Если подходящая валюта не найдена, будет использована валюта по умолчанию для продукта. Вы можете настроить все цены продукта или плана подписки в панели управления Paddle.
#Скидки
Вы также можете отображать цены с учётом скидки. При вызове метода previewPrices укажите идентификатор скидки через опцию discount_id:
use Laravel\Paddle\Cashier;
$prices = Cashier::previewPrices(['pri_123', 'pri_456'], [
'discount_id' => 'dsc_123'
]);
Затем отобразите рассчитанные цены:
<ul>
@foreach ($prices as $price)
<li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
@endforeach
</ul>
#Клиенты
#Значения по умолчанию для клиентов
Cashier позволяет задать полезные значения по умолчанию для клиентов при создании сессий оформления заказа. Установка этих значений позволяет предварительно заполнить адрес электронной почты и имя клиента, чтобы он мог сразу перейти к оплате в виджете оформления заказа. Вы можете задать эти значения, переопределив следующие методы в вашей модели с биллингом:
/**
* Получить имя клиента для ассоциации с Paddle.
*/
public function paddleName(): string|null
{
return $this->name;
}
/**
* Получить адрес электронной почты клиента для ассоциации с Paddle.
*/
public function paddleEmail(): string|null
{
return $this->email;
}
Эти значения по умолчанию будут использоваться для всех действий в Cashier, которые создают сессию оформления заказа.
#Получение клиентов
Вы можете получить клиента по его Paddle Customer ID с помощью метода Cashier::findBillable. Этот метод вернёт экземпляр модели с биллингом:
use Laravel\Cashier\Cashier;
$user = Cashier::findBillable($customerId);
#Создание клиентов
Иногда может потребоваться создать клиента Paddle без начала подписки. Это можно сделать с помощью метода createAsCustomer:
$customer = $user->createAsCustomer();
Возвращается экземпляр Laravel\Paddle\Customer. После создания клиента в Paddle вы можете начать подписку позже. Вы можете передать необязательный массив $options для передачи дополнительных параметров создания клиента, поддерживаемых Paddle API:
$customer = $user->createAsCustomer($options);
#Подписки
#Создание подписок
Чтобы создать подписку, сначала получите экземпляр вашей модели с биллингом из базы данных, обычно это будет экземпляр App\Models\User. После получения модели вы можете использовать метод subscribe для создания сессии оформления заказа модели:
use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$checkout = $request->user()->subscribe($premium = 12345, 'default')
->returnTo(route('home'));
return view('billing', ['checkout' => $checkout]);
});
Первый аргумент метода subscribe — это конкретная цена, на которую подписывается пользователь. Это значение должно соответствовать идентификатору цены в Paddle. Метод returnTo принимает URL, на который будет перенаправлен пользователь после успешного завершения оформления заказа. Второй аргумент метода subscribe — внутренний «тип» подписки. Если ваше приложение предлагает только одну подписку, вы можете назвать его default или primary. Этот тип подписки предназначен только для внутреннего использования в приложении и не должен отображаться пользователям. Кроме того, он не должен содержать пробелов и не должен изменяться после создания подписки.
Вы также можете передать массив пользовательских метаданных о подписке с помощью метода customData:
$checkout = $request->user()->subscribe($premium = 12345, 'default')
->customData(['key' => 'value'])
->returnTo(route('home'));
После создания сессии оформления подписки её можно передать в Blade-компонент paddle-button, который входит в состав Cashier Paddle:
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Subscribe
</x-paddle-button>
После завершения оформления заказа пользователем Paddle отправит webhook subscription_created. Cashier получит этот webhook и настроит подписку для вашего клиента. Чтобы убедиться, что все вебхуки корректно принимаются и обрабатываются вашим приложением, убедитесь, что вы правильно настроили обработку вебхуков.
#Проверка статуса подписки
После подписки пользователя на ваше приложение вы можете проверить статус его подписки с помощью различных удобных методов. Метод subscribed возвращает true, если у пользователя есть действующая подписка, даже если она находится в пробном периоде:
if ($user->subscribed()) {
// ...
}
Если ваше приложение предлагает несколько подписок, вы можете указать конкретную подписку при вызове метода subscribed:
if ($user->subscribed('default')) {
// ...
}
Метод subscribed также отлично подходит для использования в middleware маршрутов, позволяя фильтровать доступ к маршрутам и контроллерам на основе статуса подписки пользователя:
<?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()) {
// Этот пользователь не является платящим клиентом...
return redirect('billing');
}
return $next($request);
}
}
Если вы хотите определить, находится ли пользователь ещё в пробном периоде, используйте метод onTrial. Этот метод полезен для отображения предупреждения пользователю о том, что он всё ещё в пробном периоде:
if ($user->subscription()->onTrial()) {
// ...
}
Метод subscribedToPrice позволяет определить, подписан ли пользователь на конкретный план по заданному Paddle price ID. В этом примере мы проверим, активна ли у пользователя подписка default на месячный тариф:
if ($user->subscribedToPrice($monthly = 'pri_123', 'default')) {
// ...
}
Метод recurring позволяет определить, активна ли у пользователя подписка и не находится ли он в пробном или льготном периоде:
if ($user->subscription()->recurring()) {
// ...
}
#Статус отменённой подписки
Чтобы определить, был ли пользователь активным подписчиком, но отменил подписку, используйте метод canceled:
if ($user->subscription()->canceled()) {
// ...
}
Вы также можете определить, отменил ли пользователь подписку, но всё ещё находится в «льготном периоде» до полного окончания подписки. Например, если пользователь отменил подписку 5 марта, которая должна была закончиться 10 марта, он находится в льготном периоде до 10 марта. При этом метод subscribed будет возвращать true:
if ($user->subscription()->onGracePeriod()) {
// ...
}
#Статус просрочки платежа
Если платеж по подписке не прошёл, она будет помечена как past_due. В этом состоянии подписка не активна, пока клиент не обновит платёжную информацию. Вы можете проверить статус просрочки с помощью метода pastDue у экземпляра подписки:
if ($user->subscription()->pastDue()) {
// ...
}
При просрочке платежа следует попросить пользователя обновить платёжную информацию.
Если вы хотите, чтобы подписки считались действительными даже в состоянии past_due, используйте метод keepPastDueSubscriptionsActive, предоставляемый Cashier. Обычно этот метод вызывается в методе register вашего AppServiceProvider:
use Laravel\Paddle\Cashier;
/**
* Зарегистрировать сервисы приложения.
*/
public function register(): void
{
Cashier::keepPastDueSubscriptionsActive();
}
Когда подписка находится в состоянии past_due, её нельзя изменить до обновления платёжной информации. Поэтому методы swap и updateQuantity выбросят исключение, если подписка в состоянии past_due.
#Скоупы подписок
Большинство состояний подписок доступны как query scopes, что позволяет легко выполнять запросы к базе данных для подписок в определённом состоянии:
// Получить все действующие подписки...
$subscriptions = Subscription::query()->valid()->get();
// Получить все отменённые подписки пользователя...
$subscriptions = $user->subscriptions()->canceled()->get();
Полный список доступных скоупов приведён ниже:
Subscription::query()->valid();
Subscription::query()->onTrial();
Subscription::query()->expiredTrial();
Subscription::query()->notOnTrial();
Subscription::query()->active();
Subscription::query()->recurring();
Subscription::query()->pastDue();
Subscription::query()->paused();
Subscription::query()->notPaused();
Subscription::query()->onPausedGracePeriod();
Subscription::query()->notOnPausedGracePeriod();
Subscription::query()->canceled();
Subscription::query()->notCanceled();
Subscription::query()->onGracePeriod();
Subscription::query()->notOnGracePeriod();
#Одноразовые списания по подписке
Одноразовые списания по подписке позволяют взимать с подписчиков единовременную плату поверх их подписки. При вызове метода charge необходимо указать один или несколько идентификаторов цен:
// Списать одну цену...
$response = $user->subscription()->charge('pri_123');
// Списать несколько цен одновременно...
$response = $user->subscription()->charge(['pri_123', 'pri_456']);
Метод charge не спишет деньги с клиента сразу, а сделает это при следующем платёжном периоде подписки. Если вы хотите списать средства немедленно, используйте метод chargeAndInvoice:
$response = $user->subscription()->chargeAndInvoice('pri_123');
#Обновление платёжной информации
Paddle всегда сохраняет платёжный метод для каждой подписки. Чтобы обновить платёжный метод по умолчанию для подписки, перенаправьте клиента на страницу обновления платёжного метода Paddle с помощью метода redirectToUpdatePaymentMethod у модели подписки:
use Illuminate\Http\Request;
Route::get('/update-payment-method', function (Request $request) {
$user = $request->user();
return $user->subscription()->redirectToUpdatePaymentMethod();
});
После обновления информации пользователем Paddle отправит webhook subscription_updated, и данные подписки будут обновлены в базе данных вашего приложения.
#Смена планов
После подписки пользователь может захотеть сменить план подписки. Для обновления плана передайте идентификатор цены Paddle в метод swap подписки:
use App\Models\User;
$user = User::find(1);
$user->subscription()->swap($premium = 'pri_456');
Если вы хотите сменить план и сразу выставить счёт, не дожидаясь следующего платёжного цикла, используйте метод swapAndInvoice:
$user = User::find(1);
$user->subscription()->swapAndInvoice($premium = 'pri_456');
#Пропорциональные расчёты (prorations)
По умолчанию Paddle делает пропорциональные расчёты при смене планов. Метод noProrate позволяет обновить подписку без пропорциональных расчётов:
$user->subscription('default')->noProrate()->swap($premium = 'pri_456');
Если вы хотите отключить пропорциональные расчёты и сразу выставить счёт клиенту, используйте swapAndInvoice вместе с noProrate:
$user->subscription('default')->noProrate()->swapAndInvoice($premium = 'pri_456');
Или, чтобы не выставлять счёт клиенту за смену подписки, используйте метод doNotBill:
$user->subscription('default')->doNotBill()->swap($premium = 'pri_456');
Для дополнительной информации о политике пропорциональных расчётов Paddle смотрите документацию по proration.
#Количество подписок
Иногда подписки зависят от «количества». Например, приложение для управления проектами может брать $10 в месяц за каждый проект. Для удобного увеличения или уменьшения количества используйте методы incrementQuantity и decrementQuantity:
$user = User::find(1);
$user->subscription()->incrementQuantity();
// Увеличить текущее количество подписки на пять...
$user->subscription()->incrementQuantity(5);
$user->subscription()->decrementQuantity();
// Уменьшить текущее количество подписки на пять...
$user->subscription()->decrementQuantity(5);
Также можно установить конкретное количество с помощью метода updateQuantity:
$user->subscription()->updateQuantity(10);
Метод noProrate позволяет обновить количество подписки без пропорциональных расчётов:
$user->subscription()->noProrate()->updateQuantity(10);
#Количество для подписок с несколькими продуктами
Если ваша подписка является подпиской с несколькими продуктами, передайте ID цены, количество которой хотите изменить, вторым аргументом в методы увеличения/уменьшения количества:
$user->subscription()->incrementQuantity(1, 'price_chat');
#Подписки с несколькими продуктами
Подписки с несколькими продуктами позволяют назначать несколько продуктов для одной подписки. Например, представьте, что вы создаёте приложение для службы поддержки с базовой подпиской за $10 в месяц и дополнительным продуктом «живой чат» за $15 в месяц.
При создании сессий оформления подписки вы можете указать несколько продуктов, передав массив цен в первый аргумент метода subscribe:
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$checkout = $request->user()->subscribe([
'price_monthly',
'price_chat',
]);
return view('billing', ['checkout' => $checkout]);
});
В приведённом примере у клиента будет две цены, прикреплённые к подписке default. Обе цены будут списываться по своим платёжным периодам. При необходимости вы можете передать ассоциативный массив ключ/значение, чтобы указать конкретное количество для каждой цены:
$user = User::find(1);
$checkout = $user->subscribe('default', ['price_monthly', 'price_chat' => 5]);
Если вы хотите добавить новую цену к существующей подписке, используйте метод swap подписки. При вызове swap также укажите текущие цены и количества подписки:
$user = User::find(1);
$user->subscription()->swap(['price_chat', 'price_original' => 2]);
Пример выше добавит новую цену, но клиент не будет за неё платить до следующего платёжного цикла. Если хотите выставить счёт сразу, используйте метод swapAndInvoice:
$user->subscription()->swapAndInvoice(['price_chat', 'price_original' => 2]);
Вы можете удалить цены из подписки, используя метод swap и опуская цену, которую хотите убрать:
$user->subscription()->swap(['price_original' => 2]);
Нельзя удалить последнюю цену из подписки. В этом случае следует просто отменить подписку.
#Несколько подписок
Paddle позволяет вашим клиентам иметь несколько подписок одновременно. Например, вы можете управлять спортзалом, который предлагает подписку на плавание и подписку на силовые тренировки, каждая из которых имеет свою цену. Клиенты могут подписаться на одну или обе подписки.
При создании подписок в вашем приложении вы можете передать тип подписки вторым аргументом в метод subscribe. Тип может быть любой строкой, обозначающей тип подписки, которую пользователь оформляет:
use Illuminate\Http\Request;
Route::post('/swimming/subscribe', function (Request $request) {
$checkout = $request->user()->subscribe($swimmingMonthly = 'pri_123', 'swimming');
return view('billing', ['checkout' => $checkout]);
});
В этом примере мы оформили месячную подписку на плавание для клиента. Позже он может захотеть перейти на годовую подписку. Для изменения подписки мы просто меняем цену у подписки типа swimming:
$user->subscription('swimming')->swap($swimmingYearly = 'pri_456');
Конечно, вы также можете полностью отменить подписку:
$user->subscription('swimming')->cancel();
#Приостановка подписок
Чтобы приостановить подписку, вызовите метод pause у подписки пользователя:
$user->subscription()->pause();
При приостановке подписки Cashier автоматически установит в базе данных значение paused_at. Этот столбец используется для определения момента, когда метод paused начнёт возвращать true. Например, если клиент приостановил подписку 1 марта, но следующая оплата была запланирована на 5 марта, метод paused будет возвращать false до 5 марта. Это связано с тем, что обычно пользователю разрешается пользоваться приложением до конца оплаченного периода.
По умолчанию приостановка происходит с начала следующего платёжного периода, чтобы клиент мог использовать оплаченный период. Если хотите приостановить подписку немедленно, используйте метод pauseNow:
$user->subscription()->pauseNow();
С помощью метода pauseUntil можно приостановить подписку до определённого момента времени:
$user->subscription()->pauseUntil(now()->addMonth());
Или используйте метод pauseNowUntil, чтобы немедленно приостановить подписку до указанного времени:
$user->subscription()->pauseNowUntil(now()->addMonth());
Вы можете определить, приостановил ли пользователь подписку и находится ли он в «льготном периоде» приостановки, используя метод onPausedGracePeriod:
if ($user->subscription()->onPausedGracePeriod()) {
// ...
}
Чтобы возобновить приостановленную подписку, вызовите метод resume у подписки:
$user->subscription()->resume();
Подписка не может быть изменена, пока она приостановлена. Чтобы сменить план или обновить количество, сначала возобновите подписку.
#Отмена подписок
Чтобы отменить подписку, вызовите метод cancel у подписки пользователя:
$user->subscription()->cancel();
При отмене подписки Cashier автоматически установит в базе данных значение ends_at. Этот столбец используется для определения момента, когда метод subscribed начнёт возвращать false. Например, если клиент отменил подписку 1 марта, но она должна была закончиться 5 марта, метод subscribed будет возвращать true до 5 марта. Это сделано потому, что обычно пользователю разрешается пользоваться приложением до конца оплаченного периода.
Вы можете определить, отменил ли пользователь подписку, но всё ещё находится в «льготном периоде», используя метод onGracePeriod:
if ($user->subscription()->onGracePeriod()) {
// ...
}
Если вы хотите отменить подписку немедленно, вызовите метод cancelNow у подписки:
$user->subscription()->cancelNow();
Чтобы остановить отмену подписки в её льготном периоде, вызовите метод stopCancelation:
$user->subscription()->stopCancelation();
Подписки Paddle нельзя возобновить после отмены. Если клиент хочет возобновить подписку, ему нужно оформить новую.
#Пробные периоды подписок
#С предварительным вводом платёжного метода
Если вы хотите предложить пробный период клиентам, при этом сразу собирая информацию о платёжном методе, установите время пробного периода в панели Paddle для цены, на которую подписывается клиент. Затем создайте сессию оформления заказа как обычно:
use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$checkout = $request->user()->subscribe('pri_monthly')
->returnTo(route('home'));
return view('billing', ['checkout' => $checkout]);
});
Когда ваше приложение получит событие subscription_created, Cashier установит дату окончания пробного периода в записи подписки в базе данных и укажет Paddle не начинать списания до этой даты.
Если подписка клиента не будет отменена до окончания пробного периода, с него будет списана плата сразу после его окончания. Поэтому обязательно уведомляйте пользователей о дате окончания пробного периода.
Вы можете определить, находится ли пользователь в пробном периоде, используя либо метод onTrial у экземпляра пользователя, либо метод onTrial у экземпляра подписки. Два примера ниже эквивалентны:
if ($user->onTrial()) {
// ...
}
if ($user->subscription()->onTrial()) {
// ...
}
Чтобы определить, истёк ли текущий пробный период, вы можете использовать методы hasExpiredTrial:
if ($user->hasExpiredTrial()) {
// ...
}
if ($user->subscription()->hasExpiredTrial()) {
// ...
}
Чтобы проверить пробный период для конкретного типа подписки, передайте тип в методы onTrial или hasExpiredTrial:
if ($user->onTrial('default')) {
// ...
}
if ($user->hasExpiredTrial('default')) {
// ...
}
#Без предварительного ввода платёжного метода
Если вы хотите предложить пробный период без предварительного сбора информации о платёжном методе пользователя, вы можете установить значение столбца trial_ends_at в записи клиента, связанной с вашим пользователем, на желаемую дату окончания пробного периода. Обычно это делается во время регистрации пользователя:
use App\Models\User;
$user = User::create([
// ...
]);
$user->createAsCustomer([
'trial_ends_at' => now()->addDays(10)
]);
Cashier называет такой тип пробного периода «общим пробным периодом» (generic trial), так как он не привязан к какой-либо подписке. Метод onTrial у экземпляра User вернёт true, если текущая дата не превышает значение trial_ends_at:
if ($user->onTrial()) {
// Пользователь находится в пределах пробного периода...
}
Когда вы будете готовы создать реальную подписку для пользователя, вы можете использовать метод subscribe как обычно:
use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$checkout = $user->subscribe('pri_monthly')
->returnTo(route('home'));
return view('billing', ['checkout' => $checkout]);
});
Чтобы получить дату окончания пробного периода пользователя, вы можете использовать метод trialEndsAt. Этот метод вернёт экземпляр Carbon с датой, если пользователь находится на пробном периоде, или null, если нет. Также можно передать необязательный параметр с типом подписки, если нужно получить дату окончания пробного периода для конкретной подписки, отличной от стандартной:
if ($user->onTrial('default')) {
$trialEndsAt = $user->trialEndsAt();
}
Если вы хотите точно узнать, что пользователь находится в пределах своего «общего» пробного периода и ещё не создал реальную подписку, используйте метод onGenericTrial:
if ($user->onGenericTrial()) {
// Пользователь находится в пределах своего "общего" пробного периода...
}
#Продление или активация пробного периода
Вы можете продлить существующий пробный период подписки, вызвав метод extendTrial и указав момент времени, когда пробный период должен закончиться:
$user->subsription()->extendTrial(now()->addDays(5));
Или вы можете немедленно активировать подписку, завершив её пробный период, вызвав метод activate у подписки:
$user->subscription()->activate();
#Обработка вебхуков Paddle
Paddle может уведомлять ваше приложение о различных событиях через вебхуки. По умолчанию маршрут, указывающий на контроллер вебхуков Cashier, регистрируется провайдером сервиса Cashier. Этот контроллер обрабатывает все входящие запросы вебхуков.
По умолчанию этот контроллер автоматически обрабатывает отмену подписок с большим количеством неудачных платежей, обновления подписок и изменения платёжных методов; однако, как мы скоро увидим, вы можете расширить этот контроллер для обработки любых событий вебхуков Paddle.
Чтобы ваше приложение могло обрабатывать вебхуки Paddle, обязательно настройте URL вебхука в панели управления Paddle. По умолчанию контроллер вебхуков Cashier отвечает на путь URL /paddle/webhook. Полный список вебхуков, которые следует включить в панели Paddle:
- Customer Updated
- Transaction Completed
- Transaction Updated
- Subscription Created
- Subscription Updated
- Subscription Paused
- Subscription Canceled
Обязательно защитите входящие запросы с помощью встроенного в Cashier middleware для проверки подписи вебхуков.
#Вебхуки и защита от CSRF
Поскольку вебхуки Paddle должны обходить защиту Laravel от CSRF, обязательно добавьте URI в исключения в вашем middleware App\Http\Middleware\VerifyCsrfToken или выведите маршрут из группы middleware web:
protected $except = [
'paddle/*',
];
#Вебхуки и локальная разработка
Чтобы Paddle мог отправлять вебхуки вашему приложению во время локальной разработки, необходимо открыть доступ к вашему приложению через сервисы шаринга сайтов, такие как Ngrok или Expose. Если вы разрабатываете приложение локально с помощью Laravel Sail, вы можете использовать команду шаринга сайта Sail.
#Определение обработчиков событий вебхуков
Cashier автоматически обрабатывает отмену подписок при неудачных платежах и другие распространённые вебхуки Paddle. Однако, если у вас есть дополнительные события вебхуков, которые вы хотите обработать, вы можете слушать следующие события, которые генерирует Cashier:
Laravel\Paddle\Events\WebhookReceivedLaravel\Paddle\Events\WebhookHandled
Оба события содержат полный полезный нагрузочный пакет (payload) вебхука Paddle. Например, если вы хотите обработать вебхук transaction_billed, вы можете зарегистрировать listener, который будет обрабатывать это событие:
<?php
namespace App\Listeners;
use Laravel\Paddle\Events\WebhookReceived;
class PaddleEventListener
{
/**
* Обработка полученных вебхуков Paddle.
*/
public function handle(WebhookReceived $event): void
{
if ($event->payload['alert_name'] === 'transaction_billed') {
// Обработать входящее событие...
}
}
}
После определения listener вы можете зарегистрировать его в 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 также генерирует события, посвящённые конкретному типу полученного вебхука. Помимо полного payload от Paddle, они содержат соответствующие модели, которые использовались для обработки вебхука, такие как billable модель, подписка или квитанция:
Laravel\Paddle\Events\CustomerUpdatedLaravel\Paddle\Events\TransactionCompletedLaravel\Paddle\Events\TransactionUpdatedLaravel\Paddle\Events\SubscriptionCreatedLaravel\Paddle\Events\SubscriptionUpdatedLaravel\Paddle\Events\SubscriptionPausedLaravel\Paddle\Events\SubscriptionCanceled
Вы также можете переопределить стандартный встроенный маршрут вебхуков, определив переменную окружения CASHIER_WEBHOOK в файле .env вашего приложения. Это значение должно быть полным URL вашего маршрута вебхуков и должно совпадать с URL, установленным в панели управления Paddle:
CASHIER_WEBHOOK=https://example.com/my-paddle-webhook-url
#Проверка подписей вебхуков
Для защиты ваших вебхуков вы можете использовать подписи вебхуков Paddle. Для удобства Cashier автоматически включает middleware, который проверяет валидность входящего запроса вебхука Paddle.
Чтобы включить проверку вебхуков, убедитесь, что переменная окружения PADDLE_WEBHOOK_SECRET определена в файле .env вашего приложения. Секрет вебхука можно получить в панели управления вашим аккаунтом Paddle.
#Одноразовые платежи
#Оплата продуктов
Если вы хотите инициировать покупку продукта для клиента, вы можете использовать метод checkout у экземпляра billable модели для создания сессии оформления покупки. Метод checkout принимает один или несколько ID цен. При необходимости можно использовать ассоциативный массив для указания количества приобретаемого продукта:
use Illuminate\Http\Request;
Route::get('/buy', function (Request $request) {
$checkout = $request->user()->checkout(['pri_tshirt', 'pri_socks' => 5]);
return view('buy', ['checkout' => $checkout]);
});
После создания сессии оформления покупки вы можете использовать предоставленный Cashier Blade-компонент paddle-button, чтобы позволить пользователю просмотреть виджет оформления Paddle и завершить покупку:
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Buy
</x-paddle-button>
Сессия оформления имеет метод customData, который позволяет передать любые пользовательские данные для создания транзакции. Пожалуйста, ознакомьтесь с документацией Paddle, чтобы узнать больше о доступных опциях при передаче пользовательских данных:
$checkout = $user->checkout('pri_tshirt')
->customData([
'custom_option' => $value,
]);
#Возврат средств по транзакциям
Возврат средств по транзакциям возвращает сумму на платёжный метод клиента, который использовался при покупке. Если вам нужно вернуть покупку Paddle, вы можете использовать метод refund у модели Cashier\Paddle\Transaction. Этот метод принимает причину возврата в первом аргументе, один или несколько ID цен для возврата с необязательными суммами в виде ассоциативного массива. Транзакции для конкретной billable модели можно получить с помощью метода transactions.
Например, предположим, что мы хотим вернуть конкретную транзакцию для цен pri_123 и pri_456. Мы хотим полностью вернуть pri_123, но только частично вернуть два доллара для pri_456:
use App\Models\User;
$user = User::find(1);
$transaction = $user->transactions()->first();
$response = $transaction->refund('Accidental charge', [
'pri_123', // Полностью вернуть эту цену...
'pri_456' => 200, // Частично вернуть эту цену...
]);
Пример выше возвращает конкретные позиции в транзакции. Если вы хотите вернуть всю транзакцию целиком, просто укажите причину:
$response = $transaction->refund('Accidental charge');
Для получения дополнительной информации о возвратах обратитесь к документации Paddle по возвратам.
Возвраты всегда должны быть одобрены Paddle перед полной обработкой.
#Начисление средств по транзакциям
Так же, как и возвраты, вы можете начислять средства по транзакциям. Начисление средств добавляет деньги на баланс клиента, чтобы они могли быть использованы для будущих покупок. Начисление средств возможно только для транзакций с ручным сбором, а не для автоматически списываемых транзакций (например, подписок), так как Paddle автоматически обрабатывает кредиты по подпискам:
$transaction = $user->transactions()->first();
// Полностью начислить конкретную позицию...
$response = $transaction->credit('Compensation', 'pri_123');
Для дополнительной информации смотрите документацию Paddle по начислениям.
Начисления могут применяться только к транзакциям с ручным сбором. Автоматически списываемые транзакции кредитуются Paddle самостоятельно.
#Транзакции
Вы можете легко получить массив транзакций billable модели через свойство transactions:
use App\Models\User;
$user = User::find(1);
$transactions = $user->transactions;
Транзакции представляют собой платежи за ваши продукты и покупки и сопровождаются счетами. В базе данных вашего приложения хранятся только завершённые транзакции.
При выводе списка транзакций для клиента вы можете использовать методы экземпляра транзакции для отображения соответствующей информации о платеже. Например, вы можете вывести все транзакции в таблице, позволяя пользователю легко скачать любой из счетов:
<table>
@foreach ($transactions as $transaction)
<tr>
<td>{{ $transaction->billed_at->toFormattedDateString() }}</td>
<td>{{ $transaction->total() }}</td>
<td>{{ $transaction->tax() }}</td>
<td><a href="{{ route('download-invoice', $transaction->id) }}" target="_blank">Download</a></td>
</tr>
@endforeach
</table>
Маршрут download-invoice может выглядеть следующим образом:
use Illuminate\Http\Request;
use Laravel\Cashier\Transaction;
Route::get('/download-invoice/{transaction}', function (Request $request, Transaction $transaction) {
return $transaction->redirectToInvoicePdf();
})->name('download-invoice');
#Прошлые и предстоящие платежи
Вы можете использовать методы lastPayment и nextPayment для получения и отображения прошлых или предстоящих платежей клиента по повторяющимся подпискам:
use App\Models\User;
$user = User::find(1);
$subscription = $user->subscription();
$lastPayment = $subscription->lastPayment();
$nextPayment = $subscription->nextPayment();
Оба метода возвращают экземпляр Laravel\Paddle\Payment; однако lastPayment вернёт null, если транзакции ещё не были синхронизированы вебхуками, а nextPayment вернёт null, если расчётный период завершён (например, когда подписка отменена):
Next payment: {{ $nextPayment->amount() }} due on {{ $nextPayment->date()->format('d/m/Y') }}
#Тестирование
Во время тестирования рекомендуется вручную проверить процесс выставления счетов, чтобы убедиться, что интеграция работает как ожидается.
Для автоматизированных тестов, включая те, что выполняются в CI-среде, вы можете использовать HTTP Client Laravel для имитации HTTP-запросов к Paddle. Хотя это не проверяет реальные ответы Paddle, такой подход позволяет тестировать ваше приложение без фактических вызовов API Paddle.