Laravel 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;
}

Якщо у вас є платні сутності, які не є користувачами, ви також можете додати trait до цих класів:

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

Ключі API

Далі, вам слід налаштувати ваші Paddle ключі у файлі .env вашого застосунку. Ви можете отримати ваші Paddle API ключі з панелі керування 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, коли ви використовуєте середовище Sandbox від Paddle. Змінну PADDLE_SANDBOX слід встановити на false, якщо ви розгортаєте свій застосунок у продакшн і використовуєте живе середовище постачальника від Paddle.

PADDLE_RETAIN_KEY є необов'язковим і повинен бути встановлений лише якщо ви використовуєте Paddle з Retain.

Paddle JS

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

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

Конфігурація Валюти

Ви можете вказати локаль, яка буде використовуватися при форматуванні грошових значень для відображення на рахунках. Внутрішньо, 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\Subscription;
use App\Models\Cashier\Transaction;
 
/**
* Ініціалізувати будь-які сервіси застосунку.
*/
public function boot(): void
{
Cashier::useSubscriptionModel(Subscription::class);
Cashier::useTransactionModel(Transaction::class);
}

Швидкий старт

Продаж продуктів

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

Пропонування виставлення рахунків за продукти та підписки через ваш застосунок може бути лякаючим. Однак, завдяки Cashier та Paddle's Checkout Overlay, ви можете легко створювати сучасні, надійні інтеграції платежів.

Щоб стягувати плату з клієнтів за одноразові продукти, ми будемо використовувати Cashier для стягнення плати з клієнтів за допомогою Paddle's Checkout Overlay, де вони нададуть свої платіжні дані та підтвердять покупку. Після того, як платіж буде здійснено через 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');

Як ви можете бачити у наведеному вище прикладі, ми будемо використовувати наданий Cashier метод checkout для створення об'єкта оформлення замовлення, щоб представити клієнту Paddle Checkout Overlay для заданого "ідентифікатора ціни". При використанні Paddle, "ціни" відносяться до визначених цін для конкретних продуктів.

Якщо необхідно, метод checkout автоматично створить клієнта в Paddle і зв'яже цей запис клієнта Paddle з відповідним користувачем у базі даних вашого застосунку. Після завершення сесії оформлення замовлення клієнт буде перенаправлений на спеціальну сторінку успіху, де ви можете відобразити інформаційне повідомлення для клієнта.

У buy представленні ми включимо кнопку для відображення накладення оформлення замовлення. Компонент 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's 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 вашого застосунку в AppServiceProvider:

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\Paddle\Cashier;
use Laravel\Paddle\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 та Paddle's Checkout Overlay, ви можете легко створювати сучасні, надійні інтеграції платежів.

Щоб дізнатися, як продавати підписки за допомогою Cashier та Paddle's 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; однак, ви також можете вручну відобразити overlay checkout:

<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); // With "$price" being "price_basic_yearly" for this example.
 
    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 автоматично підтримуватиме синхронізацію таблиць бази даних вашого застосунку, пов'язаних з Cashier, перевіряючи вхідні вебхуки від Paddle. Наприклад, коли ви скасовуєте підписку клієнта через панель керування Paddle, Cashier отримає відповідний вебхук і позначить підписку як "скасовану" в базі даних вашого застосунку.

Сесії оформлення замовлення

Більшість операцій з виставлення рахунків клієнтам виконуються за допомогою "checkouts" через віджет Checkout Overlay або за допомогою вбудованого оформлення замовлення.

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

Перекриття Оформлення

Перш ніж відобразити віджет накладання оформлення замовлення, ви повинні створити сесію оформлення замовлення за допомогою 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 включає компонент paddle-button Blade. Ви можете передати сесію оформлення замовлення цьому компоненту як "властивість". Потім, коли ця кнопка буде натиснута, відобразиться віджет оформлення замовлення Paddle:

<x-paddle-button :checkout="$checkout" class="px-8 py-4">
    Subscribe
</x-paddle-button>

За замовчуванням це відобразить віджет, використовуючи стандартний стиль Paddle. Ви можете налаштувати віджет, додавши атрибути, підтримувані Paddle, такі як атрибут data-theme='light' до компонента:

<x-paddle-button :checkout="$checkout" 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 у стилі "overlay", 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 з Inline Checkout та доступних налаштувань оформлення замовлення для отримання додаткової інформації про параметри налаштування inline checkout.

Ручне Відображення Вбудованої Оплати

Ви також можете вручну відобразити вбудовану форму оплати без використання вбудованих компонентів 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 або вбудованого оформлення.

Попередній перегляд цін

Paddle дозволяє налаштовувати ціни для кожної валюти, фактично дозволяючи вам встановлювати різні ціни для різних країн. Cashier Paddle дозволяє отримати всі ці ціни за допомогою методу previewPrices. Цей метод приймає ідентифікатори цін, для яких ви хочете отримати ціни:

use Laravel\Paddle\Cashier;
 
$prices = Cashier::previewPrices(['pri_123', 'pri_456']);

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

use Laravel\Paddle\Cashier;
 
$prices = Cashier::previewPrices(['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['name'] }} - {{ $price->subtotal() }} (+ {{ $price->tax() }} tax)</li>
    @endforeach
</ul>

Для отримання додаткової інформації, ознайомтеся з документацією API Paddle щодо попереднього перегляду цін.

Попередній перегляд цін для клієнтів

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

use App\Models\User;
 
$prices = User::find(1)->previewPrices(['pri_123', 'pri_456']);

Внутрішньо, Cashier використовуватиме ідентифікатор клієнта користувача для отримання цін у їхній валюті. Наприклад, користувач, що проживає в Сполучених Штатах, побачить ціни в доларах США, тоді як користувач у Бельгії побачить ціни в євро. Якщо відповідну валюту не вдасться знайти, буде використана валюта за замовчуванням для продукту. Ви можете налаштувати всі ціни продукту або плану підписки в панелі керування Paddle.

Знижки

Ви також можете вибрати відображення цін після знижки. Викликаючи метод previewPrices, ви надаєте ID знижки через опцію 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\Paddle\Cashier;
 
$user = Cashier::findBillable($customerId);

Створення Клієнтів

Іноді ви можете захотіти створити клієнта Paddle без початку підписки. Ви можете зробити це за допомогою методу createAsCustomer:

$customer = $user->createAsCustomer();

Екземпляр Laravel\Paddle\Customer повертається. Після того, як клієнт був створений у Paddle, ви можете розпочати підписку пізніше. Ви можете надати необов'язковий масив $options, щоб передати будь-які додаткові параметри створення клієнта, які підтримуються API Paddle:

$customer = $user->createAsCustomer($options);

Підписки

Створення Підписок

Щоб створити підписку, спочатку отримайте екземпляр вашої моделі для виставлення рахунків з вашої бази даних, яка зазвичай буде екземпляром App\Models\User. Після того, як ви отримали екземпляр моделі, ви можете використовувати метод subscribe для створення сесії оформлення замовлення моделі:

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

Перший аргумент, переданий методу subscribe, є конкретною ціною, на яку користувач підписується. Це значення повинно відповідати ідентифікатору ціни в Paddle. Метод returnTo приймає URL, на який ваш користувач буде перенаправлений після успішного завершення оформлення замовлення. Другий аргумент, переданий методу subscribe, повинен бути внутрішнім "типом" підписки. Якщо ваш застосунок пропонує лише одну підписку, ви можете назвати її default або primary. Цей тип підписки призначений лише для внутрішнього використання в застосунку і не повинен відображатися користувачам. Крім того, він не повинен містити пробілів і ніколи не повинен змінюватися після створення підписки.

Ви також можете надати масив користувацьких метаданих щодо підписки, використовуючи метод customData:

$checkout = $request->user()->subscribe($premium = 'pri_123', 'default')
    ->customData(['key' => 'value'])
    ->returnTo(route('home'));

Після створення сесії оформлення підписки, сесію оформлення можна надати компоненту paddle-button Blade, який включено в Cashier Paddle:

<x-paddle-button :checkout="$checkout" class="px-8 py-4">
    Subscribe
</x-paddle-button>

Після того, як користувач завершив оформлення замовлення, з Paddle буде надіслано webhook subscription_created. Cashier отримає цей webhook і налаштує підписку для вашого клієнта. Щоб переконатися, що всі webhooks належним чином отримані та оброблені вашим застосунком, переконайтеся, що ви правильно налаштували обробку 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()) {
// This user is not a paying customer...
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.

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

Більшість станів підписки також доступні як області запиту, щоб ви могли легко виконувати запити до вашої бази даних для підписок, які знаходяться в заданому стані:

// Отримати всі дійсні підписки...
$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');

Пропорційні нарахування

За замовчуванням, 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, будь ласка, зверніться до документації про пропорційний розрахунок Paddle.

Кількість підписок

Іноді підписки залежать від "кількості". Наприклад, застосунок для управління проектами може стягувати $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 ціни, кількість якої ви бажаєте збільшити або зменшити, як другий аргумент до методів increment / decrement:

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

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

Підписка з декількома продуктами дозволяє призначати декілька продуктів для виставлення рахунків до однієї підписки. Наприклад, уявіть, що ви створюєте "helpdesk" застосунок для обслуговування клієнтів, який має базову ціну підписки $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 екземпляра користувача:

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

Щоб визначити, чи закінчився існуючий пробний період, ви можете використовувати методи hasExpiredTrial:

if ($user->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 = $request->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->subscription()->extendTrial(now()->addDays(5));

Або ви можете негайно активувати підписку, завершивши її пробний період, викликавши метод activate на підписці:

$user->subscription()->activate();

Обробка Paddle Webhooks

Paddle може повідомляти ваш застосунок про різноманітні події через вебхуки. За замовчуванням, маршрут, що вказує на контролер вебхуків Cashier, реєструється сервіс-провайдером Cashier. Цей контролер оброблятиме всі вхідні запити вебхуків.

За замовчуванням цей контролер автоматично оброблятиме скасування підписок, які мають занадто багато невдалих платежів, оновлення підписок та зміни способу оплати; однак, як ми незабаром дізнаємося, ви можете розширити цей контролер для обробки будь-якої події Paddle webhook, яка вам подобається.

Щоб переконатися, що ваш застосунок може обробляти вебхуки 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 повинні обходити CSRF-захист Laravel, ви повинні переконатися, що Laravel не намагається перевірити CSRF-токен для вхідних вебхуків Paddle. Щоб досягти цього, ви повинні виключити paddle/* з CSRF-захисту у файлі bootstrap/app.php вашого застосунку:

->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: [
        'paddle/*',
    ]);
})

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

Щоб Paddle міг надсилати вебхуки вашого застосунку під час локальної розробки, вам потрібно відкрити ваш застосунок через сервіс спільного доступу до сайту, такий як Ngrok або Expose. Якщо ви розробляєте ваш застосунок локально, використовуючи Laravel Sail, ви можете скористатися командою спільного доступу до сайту Sail.

Визначення Обробників Подій Webhook

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

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

Обидві події містять повний вміст вебхука Paddle. Наприклад, якщо ви бажаєте обробити вебхук transaction.billed, ви можете зареєструвати слухача, який обробить цю подію:

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

Cashier також генерує події, присвячені типу отриманого вебхука. Окрім повного вмісту від Paddle, вони також містять відповідні моделі, які були використані для обробки вебхука, такі як модель, що підлягає оплаті, підписка або квитанція:

  • Laravel\Paddle\Events\CustomerUpdated
  • Laravel\Paddle\Events\TransactionCompleted
  • Laravel\Paddle\Events\TransactionUpdated
  • Laravel\Paddle\Events\SubscriptionCreated
  • Laravel\Paddle\Events\SubscriptionUpdated
  • Laravel\Paddle\Events\SubscriptionPaused
  • Laravel\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 на екземплярі моделі, що підлягає оплаті, щоб створити сесію оформлення замовлення для покупки. Метод checkout приймає один або кілька ідентифікаторів цін. Якщо необхідно, можна використовувати асоціативний масив для вказівки кількості продукту, що купується:

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 paddle-button компонент Blade, щоб дозволити користувачу переглянути віджет оформлення замовлення 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 цін для повернення з необов'язковими сумами у вигляді асоціативного масиву. Ви можете отримати транзакції для даної моделі, що підлягає оплаті, використовуючи метод 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', // Fully refund this price...
    'pri_456' => 200, // Only partially refund this price...
]);

Приклад вище повертає кошти за конкретні позиції в транзакції. Якщо ви хочете повернути кошти за всю транзакцію, просто вкажіть причину:

$response = $transaction->refund('Accidental charge');

Для отримання додаткової інформації про повернення коштів, будь ласка, зверніться до документації з повернення коштів Paddle.

Повернення завжди повинні бути затверджені Paddle перед повним обробленням.

Транзакції з нарахуванням

Як і повернення коштів, ви також можете кредитувати транзакції. Кредитування транзакцій додасть кошти до балансу клієнта, щоб їх можна було використати для майбутніх покупок. Кредитування транзакцій може бути здійснено лише для транзакцій, зібраних вручну, а не для автоматично зібраних транзакцій (наприклад, підписок), оскільки Paddle автоматично обробляє кредити за підписками:

$transaction = $user->transactions()->first();
 
// Кредитувати конкретний пункт повністю...
$response = $transaction->credit('Compensation', 'pri_123');

Для отримання додаткової інформації дивіться документацію Paddle щодо нарахування кредитів.

Кредити можуть бути застосовані лише для транзакцій, зібраних вручну. Автоматично зібрані транзакції кредитуються самим Paddle.

Транзакції

Ви можете легко отримати масив транзакцій моделі, що підлягає оплаті, через властивість 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\Paddle\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-клієнт Laravel для імітації HTTP-викликів, зроблених до Paddle. Хоча це не тестує фактичні відповіді від Paddle, це надає спосіб тестувати ваш застосунок без фактичного виклику API Paddle.