Skip to content

Laravel Cashier (Paddle)

介紹

⚠️ 警告

此文件適用於 Cashier Paddle 2.x 與 Paddle Billing 的整合。如果您仍在使用 Paddle Classic,應使用 Cashier Paddle 1.x

Laravel Cashier PaddlePaddle 的訂閱計費服務提供了一個富有表達力且流暢的介面。它處理了您所厭惡的幾乎所有樣板訂閱計費程式碼。除了基本的訂閱管理外,Cashier 還能處理:更換訂閱、訂閱「數量」、訂閱暫停、取消寬限期等等。

在深入了解 Cashier Paddle 之前,我們建議您也查閱 Paddle 的概念指南API 文件

升級 Cashier

升級到新版本的 Cashier 時,務必仔細查閱升級指南

安裝

首先,使用 Composer 套件管理工具安裝 Cashier for Paddle 套件:

shell
composer require laravel/cashier-paddle

接著,您應該使用 vendor:publish Artisan 指令發布 Cashier 遷移檔:

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

然後,您應該執行應用程式的資料庫遷移。Cashier 遷移將會建立一個新的 customers 資料表。此外,還將建立新的 subscriptionssubscription_items 資料表,以儲存您所有客戶的訂閱。最後,將會建立一個新的 transactions 資料表,以儲存與您的客戶相關的所有 Paddle 交易:

shell
php artisan migrate

⚠️ 警告

為確保 Cashier 正確處理所有 Paddle 事件,請記得設定 Cashier 的 Webhook 處理

Paddle Sandbox

在本地及測試環境開發期間,您應該註冊一個 Paddle Sandbox 帳號。這個帳號將提供您一個沙盒環境,用於測試和開發您的應用程式,而無需進行實際付款。您可以使用 Paddle 的測試卡號來模擬各種付款情境。

當使用 Paddle Sandbox 環境時,您應在應用程式的 .env 檔案中將 PADDLE_SANDBOX 環境變數設定為 true

ini
PADDLE_SANDBOX=true

完成應用程式開發後,您可以申請一個 Paddle 供應商帳號。在您的應用程式部署到正式環境之前,Paddle 需要批准您應用程式的網域。

設定

可計費模型

在使用 Cashier 之前,您必須將 Billable Trait 加入到您的使用者模型定義中。此 Trait 提供了各種方法,允許您執行常見的計費任務,例如建立訂閱和更新付款方式資訊:

php
use Laravel\Paddle\Billable;

class User extends Authenticatable
{
    use Billable;
}

如果您的可計費實體不是使用者,您也可以將此 Trait 加入到這些類別中:

php
use Illuminate\Database\Eloquent\Model;
use Laravel\Paddle\Billable;

class Team extends Model
{
    use Billable;
}

API 金鑰

接著,您應該在應用程式的 .env 檔案中設定您的 Paddle 金鑰。您可以從 Paddle 控制面板中擷取您的 Paddle API 金鑰:

ini
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 環境時,PADDLE_SANDBOX 環境變數應設定為 true。如果您將應用程式部署到正式環境並使用 Paddle 的正式供應商環境時,PADDLE_SANDBOX 變數應設定為 false

PADDLE_RETAIN_KEY 是可選的,並且僅在您將 Paddle 與 Retain 搭配使用時才應設定。

Paddle JS

Paddle 依賴其自己的 JavaScript 函式庫來啟動 Paddle 結帳小工具。您可以透過將 @paddleJS Blade 指令放置在應用程式佈局的 </head> 結束標籤前來載入 JavaScript 函式庫:

blade
<head>
    ...

    @paddleJS
</head>

貨幣設定

您可以指定一個語系,用於格式化發票上顯示的貨幣值。在內部,Cashier 利用 PHP 的 NumberFormatter 類別來設定貨幣語系:

ini
CASHIER_CURRENCY_LOCALE=nl_BE

⚠️ 警告

若要使用 en 以外的語系,請確保您的伺服器已安裝並設定 ext-intl PHP 擴充功能。

覆寫預設模型

您可以透過定義自己的模型並擴充對應的 Cashier 模型,來自由地擴充 Cashier 內部使用的模型:

php
use Laravel\Paddle\Subscription as CashierSubscription;

class Subscription extends CashierSubscription
{
    // ...
}

定義模型後,您可以透過 Laravel\Paddle\Cashier 類別指示 Cashier 使用您的自訂模型。通常,您應該在應用程式 App\Providers\AppServiceProvider 類別的 boot 方法中告知 Cashier 您的自訂模型:

php
use App\Models\Cashier\Subscription;
use App\Models\Cashier\Transaction;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Cashier::useSubscriptionModel(Subscription::class);
    Cashier::useTransactionModel(Transaction::class);
}

快速入門

銷售產品

📌 備註

在使用 Paddle 結帳之前,您應該在 Paddle 儀表板中定義具有固定價格的產品。此外,您應該設定 Paddle 的 webhook 處理

透過您的應用程式提供產品和訂閱計費可能令人望而生畏。然而,由於 Cashier 和 Paddle 的結帳浮動視窗,您可以輕鬆建構現代化、強大的支付整合。

若要向客戶收取非週期性、單次收費的產品費用,我們將利用 Cashier 透過 Paddle 的結帳浮動視窗向客戶收費,客戶將在此處提供他們的付款詳細資訊並確認購買。一旦透過結帳浮動視窗完成付款,客戶將會被重新導向到您應用程式中選擇的成功網址:

php
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 結帳浮動視窗。當使用 Paddle 時,「價格 (prices)」是指為特定產品定義的價格

如有必要,checkout 方法將自動在 Paddle 中建立客戶,並將該 Paddle 客戶記錄連接到您應用程式資料庫中對應的使用者。完成結帳會話後,客戶將被重新導向到專門的成功頁面,您可以在其中向客戶顯示資訊性訊息。

buy 視圖中,我們將包含一個按鈕來顯示結帳浮動視窗。paddle-button Blade 元件已包含在 Cashier Paddle 中;然而,您也可以手動渲染浮動視窗結帳

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

提供中繼資料給 Paddle 結帳

銷售產品時,通常會透過您應用程式定義的 CartOrder 模型來追蹤已完成的訂單和購買的產品。當將客戶重新導向到 Paddle 的結帳浮動視窗以完成購買時,您可能需要提供一個現有的訂單識別符,以便在客戶被重新導向回您的應用程式時,將已完成的購買與對應的訂單關聯起來。

為了實現這一點,您可以向 checkout 方法提供一個自訂資料陣列。讓我們想像一下,當使用者開始結帳流程時,我們的應用程式中會建立一個待處理的 Order。請記住,此範例中的 CartOrder 模型僅為說明用途,並非由 Cashier 提供。您可以根據自己應用程式的需求自由實作這些概念:

php
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 方法。當然,您的應用程式負責在客戶新增項目時將這些項目與「購物車」或訂單關聯起來。我們還透過 customData 方法將訂單 ID 提供給 Paddle 結帳浮動視窗。

當然,一旦客戶完成結帳流程,您可能會希望將訂單標記為「完成」。為此,您可以監聽 Paddle 派發的 webhook 以及 Cashier 透過事件觸發的內容,以將訂單資訊儲存在您的資料庫中。

要開始,請監聽 Cashier 派發的 TransactionCompleted 事件。通常,您應該在應用程式的 AppServiceProviderboot 方法中註冊事件監聽器:

php
use App\Listeners\CompleteOrder;
use Illuminate\Support\Facades\Event;
use Laravel\Paddle\Events\TransactionCompleted;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Event::listen(TransactionCompleted::class, CompleteOrder::class);
}

在此範例中,CompleteOrder 監聽器可能如下所示:

php
namespace App\Listeners;

use App\Models\Order;
use Laravel\Paddle\Cashier;
use Laravel\Paddle\Events\TransactionCompleted;

class CompleteOrder
{
    /**
     * Handle the incoming Cashier webhook event.
     */
    public function handle(TransactionCompleted $event): void
    {
        $orderId = $event->payload['data']['custom_data']['order_id'] ?? null;

        $order = Order::findOrFail($orderId);

        $order->update(['status' => 'completed']);
    }
}

有關 transaction.completed 事件包含的資料 的更多資訊,請參閱 Paddle 的文件。

銷售訂閱

📌 備註

在使用 Paddle Checkout 之前,您應該在您的 Paddle 管理後台中定義固定價格的 Products。此外,您應該設定 Paddle 的 webhook 處理

透過您的應用程式提供產品和訂閱計費可能令人望而卻步。然而,由於 Cashier 和 Paddle 的浮動視窗結帳 (Checkout Overlay),您可以輕鬆建立現代化、穩健的支付整合。

為了了解如何使用 Cashier 和 Paddle 的浮動視窗結帳來銷售訂閱,讓我們考慮一個簡單的訂閱服務場景,該服務提供基本月度方案 (price_basic_monthly) 和年度方案 (price_basic_yearly)。這兩種價格可以在我們的 Paddle 管理後台中歸類為一個「基本」產品 (pro_basic)。此外,我們的訂閱服務可能還提供一個「專家」方案,名稱為 pro_expert

首先,讓我們看看客戶如何訂閱我們的服務。當然,您可以想像客戶可能會在我們應用程式的定價頁面上點擊基本方案的「訂閱」按鈕。此按鈕將為其選擇的方案啟用 Paddle 浮動視窗結帳。要開始使用,我們將透過 checkout 方法啟動一個結帳會話:

php
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 視圖中,我們將包含一個按鈕來顯示結帳浮動視窗。paddle-button Blade Component 已包含在 Cashier Paddle 中;但是,您也可以手動渲染浮動視窗結帳

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

現在,當點擊訂閱按鈕時,客戶將能夠輸入他們的付款詳細資料並啟動訂閱。要了解他們的訂閱實際何時開始(因為某些付款方式需要幾秒鐘處理),您還應該設定 Cashier 的 webhook 處理

既然客戶可以開始訂閱,我們需要限制應用程式的某些部分,以便只有訂閱用戶才能存取。當然,我們始終可以透過 Cashier 的 Billable trait 提供的 subscribed 方法來判斷用戶當前的訂閱狀態:

blade
@if ($user->subscribed())
    <p>You are subscribed.</p>
@endif

我們甚至可以輕鬆判斷用戶是否訂閱了特定的產品或價格:

blade
@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

建立訂閱中介層

為了方便起見,您可能希望建立一個中介層來判斷傳入的請求是否來自訂閱用戶。一旦定義了此中介層,您就可以輕鬆地將其指派給路由,以防止未訂閱的用戶存取該路由:

php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class Subscribed
{
    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next): Response
    {
        if (! $request->user()?->subscribed()) {
            // Redirect user to billing page and ask them to subscribe...
            return redirect('/subscribe');
        }

        return $next($request);
    }
}

一旦定義了中介層,您就可以將其指派給路由:

php
use App\Http\Middleware\Subscribed;

Route::get('/dashboard', function () {
    // ...
})->middleware([Subscribed::class]);

允許客戶管理其計費方案

當然,客戶可能希望將其訂閱方案更改為其他產品或「級別」。在我們上面的範例中,我們希望允許客戶將其方案從每月訂閱更改為每年訂閱。為此,您需要實作一個類似於按鈕的功能,該按鈕會導向以下路由:

php
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');

除了更換方案外,您還需要允許客戶取消訂閱。與更換方案類似,提供一個按鈕,導向以下路由:

php
use Illuminate\Http\Request;

Route::put('/subscription/cancel', function (Request $request, $price) {
    $user->subscription()->cancel();

    return redirect()->route('dashboard');
})->name('subscription.cancel');

這樣,您的訂閱將在其計費週期結束時取消。

📌 備註

只要您已設定 Cashier 的 webhook 處理,Cashier 將透過檢查來自 Paddle 的傳入 webhook,自動使您應用程式中與 Cashier 相關的資料庫表格保持同步。例如,當您透過 Paddle 的管理後台取消客戶的訂閱時,Cashier 將收到相應的 webhook,並在您應用程式的資料庫中將該訂閱標記為「已取消」。

結帳會話

大多數針對客戶的帳務操作都是透過 Paddle 的 Checkout Overlay 浮動視窗元件或利用 行內結帳來完成「結帳」。

在透過 Paddle 處理結帳付款之前,您應該在 Paddle 結帳設定資訊主頁中定義應用程式的預設付款連結

浮動視窗結帳

在顯示 Checkout Overlay 浮動視窗元件之前,您必須使用 Cashier 產生結帳會話。結帳會話將通知結帳元件應執行的帳務操作:

php
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 元件。您可以將結帳會話作為一個「屬性 (prop)」傳遞給這個元件。然後,當這個按鈕被點擊時,Paddle 的結帳元件將會顯示:

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

預設情況下,這將使用 Paddle 的預設樣式顯示元件。您可以透過向元件添加 Paddle 支援的屬性,例如 data-theme='light' 屬性來客製化元件:

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

Paddle 結帳元件是非同步的。一旦使用者在元件內建立訂閱,Paddle 將向您的應用程式發送 Webhook,以便您可以正確更新應用程式資料庫中的訂閱狀態。因此,務必正確設定 Webhook 以適應 Paddle 的狀態變更。

⚠️ 警告

訂閱狀態變更後,接收相應 Webhook 的延遲通常很小,但您應該在應用程式中考慮到這一點,因為您的使用者訂閱在完成結帳後可能不會立即可用。

手動渲染浮動視窗結帳

您也可以手動渲染浮動視窗結帳,而無需使用 Laravel 內建的 Blade 元件。要開始使用,請如前述範例所示產生結帳會話:

php
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 將偵測到此類別並在點擊連結時顯示浮動視窗結帳:

blade
<?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 的「浮動視窗」樣式結帳元件,Paddle 還提供了行內顯示元件的選項。雖然這種方法不允許您調整結帳的任何 HTML 欄位,但它允許您將元件嵌入到您的應用程式中。

為了讓您輕鬆開始使用行內結帳,Cashier 包含了一個 paddle-checkout Blade 元件。要開始使用,您應該產生結帳會話

php
use Illuminate\Http\Request;

Route::get('/buy', function (Request $request) {
    $checkout = $user->checkout('pri_34567')
        ->returnTo(route('dashboard'));

    return view('billing', ['checkout' => $checkout]);
});

然後,您可以將結帳會話傳遞給元件的 checkout 屬性:

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

若要調整行內結帳元件的高度,您可以將 height 屬性傳遞給 Blade 元件:

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

請查閱 Paddle 的行內結帳指南可用結帳設定,以獲取有關行內結帳客製化選項的更多詳細資訊。

手動渲染行內結帳

您也可以手動渲染行內結帳,而無需使用 Laravel 內建的 Blade 元件。要開始使用,請如前述範例所示產生結帳會話:

php
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 來演示;但是,您可以根據自己的前端堆疊自由修改此範例:

blade
<?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 方法:

php
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]);
});

然後,您可以將結帳會話提供給 Paddle 按鈕行內結帳Blade 元件。

價格預覽

Paddle 允許您根據貨幣自訂價格,這基本上意味著您可以為不同的國家設定不同的價格。Cashier Paddle 允許您使用 previewPrices 方法擷取所有這些價格。此方法接受您希望擷取價格的價格 ID:

php
use Laravel\Paddle\Cashier;

$prices = Cashier::previewPrices(['pri_123', 'pri_456']);

貨幣將根據請求的 IP 位址確定;但是,您可以選擇性地提供特定國家來擷取其價格:

php
use Laravel\Paddle\Cashier;

$prices = Cashier::previewPrices(['pri_123', 'pri_456'], ['address' => [
    'country_code' => 'BE',
    'postal_code' => '1234',
]]);

擷取價格後,您可以按照自己喜歡的方式顯示它們:

blade
<ul>
    @foreach ($prices as $price)
        <li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
    @endforeach
</ul>

您也可以分開顯示小計價格和稅額:

blade
<ul>
    @foreach ($prices as $price)
        <li>{{ $price->product['name'] }} - {{ $price->subtotal() }} (+ {{ $price->tax() }} tax)</li>
    @endforeach
</ul>

有關更多資訊,請查閱 Paddle 關於價格預覽的 API 文件

客戶價格預覽

如果使用者已經是客戶,並且您想要顯示適用於該客戶的價格,您可以直接從客戶實例中擷取價格:

php
use App\Models\User;

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

在內部,Cashier 將使用使用者的客戶 ID 來擷取其貨幣的價格。因此,舉例來說,居住在美國的使用者將看到美元價格,而居住在比利時的使用者將看到歐元價格。如果找不到匹配的貨幣,將使用產品的預設貨幣。您可以在 Paddle 控制面板中自訂產品或訂閱方案的所有價格。

折扣

您也可以選擇在套用折扣後顯示價格。呼叫 previewPrices 方法時,您可以透過 discount_id 選項提供折扣 ID:

php
use Laravel\Paddle\Cashier;

$prices = Cashier::previewPrices(['pri_123', 'pri_456'], [
    'discount_id' => 'dsc_123'
]);

然後,顯示計算後的價格:

blade
<ul>
    @foreach ($prices as $price)
        <li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
    @endforeach
</ul>

客戶

客戶預設值

Cashier 允許您在建立結帳會話時,為客戶定義一些實用的預設值。設定這些預設值可以讓您預先填寫客戶的電子郵件地址和姓名,以便他們可以立即進入結帳小工具的付款部分。您可以透過覆寫可計費模型上的以下方法來設定這些預設值:

php
/**
 * Get the customer's name to associate with Paddle.
 */
public function paddleName(): string|null
{
    return $this->name;
}

/**
 * Get the customer's email address to associate with Paddle.
 */
public function paddleEmail(): string|null
{
    return $this->email;
}

這些預設值將用於 Cashier 中所有生成結帳會話的操作。

擷取客戶

您可以使用 Cashier::findBillable 方法,透過客戶的 Paddle 客戶 ID 擷取客戶。此方法將返回可計費模型的一個實例:

php
use Laravel\Paddle\Cashier;

$user = Cashier::findBillable($customerId);

建立客戶

有時,您可能希望在不開始訂閱的情況下建立 Paddle 客戶。您可以透過使用 createAsCustomer 方法來完成此操作:

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

將返回 Laravel\Paddle\Customer 的一個實例。一旦客戶在 Paddle 中建立,您可以在稍後日期開始訂閱。您可以提供一個可選的 $options 陣列,以傳遞 Paddle API 支援的任何其他客戶建立參數

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

訂閱

建立訂閱

要建立訂閱,首先從資料庫中擷取你的可計費模型實例,這通常會是 App\Models\User 的實例。一旦你擷取了模型實例,你就可以使用 subscribe 方法來建立模型的結帳會話:

php
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,你的使用者在成功完成結帳後將會被重新導向到該 URL。傳遞給 subscribe 方法的第二個引數應該是訂閱的內部「類型」。如果你的應用程式只提供單一訂閱,你可能會將其稱為 defaultprimary。此訂閱類型僅供內部應用程式使用,不應向使用者顯示。此外,它不應包含空格,並且在建立訂閱後絕不應更改。

你也可以使用 customData 方法提供一個關於訂閱的自訂中繼資料陣列:

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

建立訂閱結帳會話後,該結帳會話可以提供給 Cashier Paddle 中包含的 paddle-button Blade 元件

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

在使用者完成結帳後,Paddle 將會發送一個 subscription_created webhook。Cashier 將會收到此 webhook 並為你的客戶設定訂閱。為了確保所有 webhook 都能被你的應用程式正確接收和處理,請確保你已正確設定 webhook 處理

檢查訂閱狀態

一旦使用者訂閱了你的應用程式,你就可以使用各種便捷的方法檢查他們的訂閱狀態。首先,subscribed 方法會在使用者擁有有效訂閱時返回 true,即使該訂閱目前處於試用期內:

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

如果你的應用程式提供多個訂閱,你可以在調用 subscribed 方法時指定訂閱:

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

subscribed 方法也是 路由中介層 的絕佳候選,讓你能夠根據使用者的訂閱狀態過濾路由和控制器的存取:

php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureUserIsSubscribed
{
    /**
     * Handle an incoming request.
     *
     * @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 方法。此方法對於判斷是否應向使用者顯示他們仍在試用期的警告很有用:

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

subscribedToPrice 方法可用於判斷使用者是否根據給定的 Paddle 價格 ID 訂閱了特定方案。在此範例中,我們將判斷使用者的 default 訂閱是否活躍訂閱了每月價格:

php
if ($user->subscribedToPrice($monthly = 'pri_123', 'default')) {
    // ...
}

recurring 方法可用於判斷使用者目前是否處於活躍訂閱狀態,且已不在試用期或寬限期內:

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

已取消的訂閱狀態

若要判斷使用者是否曾經是活躍訂閱者但已取消訂閱,你可以使用 canceled 方法:

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

你也可以判斷使用者是否已取消訂閱,但仍在他們的「寬限期」內直到訂閱完全過期。例如,如果使用者在 3 月 5 日取消了原定於 3 月 10 日到期的訂閱,那麼該使用者將在 3 月 10 日之前處於「寬限期」。此外,subscribed 方法在此期間仍將返回 true

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

逾期狀態

如果訂閱付款失敗,它將會被標記為 past_due (逾期)。當你的訂閱處於此狀態時,它將不會啟用,直到客戶更新了他們的付款資訊。你可以使用訂閱實例上的 pastDue 方法來判斷訂閱是否逾期:

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

當訂閱逾期時,你應該指示使用者更新他們的付款資訊

如果你希望訂閱在處於 past_due (逾期) 狀態時仍被視為有效,你可以使用 Cashier 提供的 keepPastDueSubscriptionsActive 方法。通常,此方法應在你的 AppServiceProviderregister 方法中呼叫:

php
use Laravel\Paddle\Cashier;

/**
 * Register any application services.
 */
public function register(): void
{
    Cashier::keepPastDueSubscriptionsActive();
}

⚠️ 警告

當訂閱處於 past_due 狀態時,在更新付款資訊之前無法更改。因此,當訂閱處於 past_due 狀態時,swapupdateQuantity 方法將拋出例外。

訂閱查詢範圍

大多數訂閱狀態也可用作查詢範圍,以便你可以輕鬆查詢資料庫中處於特定狀態的訂閱:

php
// Get all valid subscriptions...
$subscriptions = Subscription::query()->valid()->get();

// Get all of the canceled subscriptions for a user...
$subscriptions = $user->subscriptions()->canceled()->get();

可用的範圍完整列表如下:

php
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 方法時,您必須提供一個或多個價格 ID:

php
// Charge a single price...
$response = $user->subscription()->charge('pri_123');

// Charge multiple prices at once...
$response = $user->subscription()->charge(['pri_123', 'pri_456']);

charge 方法實際上不會向客戶收費,直到其訂閱的下一個計費週期。如果您想立即向客戶收費,可以使用 chargeAndInvoice 方法:

php
$response = $user->subscription()->chargeAndInvoice('pri_123');

更新付款資訊

Paddle 總是為每個訂閱儲存一種付款方式。如果您想更新訂閱的預設付款方式,您應該使用訂閱模型上的 redirectToUpdatePaymentMethod 方法,將您的客戶重新導向至 Paddle 託管的付款方式更新頁面:

php
use Illuminate\Http\Request;

Route::get('/update-payment-method', function (Request $request) {
    $user = $request->user();

    return $user->subscription()->redirectToUpdatePaymentMethod();
});

當使用者完成資訊更新後,會由 Paddle 派發一個 subscription_updated webhook,並且訂閱詳細資訊將在您應用程式的資料庫中更新。

變更方案

使用者訂閱了您的應用程式後,偶爾會想變更新的訂閱方案。若要更新使用者的訂閱方案,您應該將 Paddle 價格的識別碼傳遞給訂閱的 swap 方法:

php
use App\Models\User;

$user = User::find(1);

$user->subscription()->swap($premium = 'pri_456');

如果您想交換方案並立即向使用者開立帳單,而不是等待他們的下一個計費週期,您可以使用 swapAndInvoice 方法:

php
$user = User::find(1);

$user->subscription()->swapAndInvoice($premium = 'pri_456');

按比例收費

預設情況下,Paddle 會在方案之間交換時按比例收取費用。noProrate 方法可用於更新訂閱,但不按比例收取費用:

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

如果您想停用按比例收費並立即向客戶開立帳單,您可以將 swapAndInvoice 方法與 noProrate 結合使用:

php
$user->subscription('default')->noProrate()->swapAndInvoice($premium = 'pri_456');

或者,為了不向客戶收取訂閱變更費用,您可以利用 doNotBill 方法:

php
$user->subscription('default')->doNotBill()->swap($premium = 'pri_456');

有關 Paddle 按比例收費政策的更多資訊,請查閱 Paddle 的 按比例收費文件

訂閱數量

有時訂閱會受「數量」影響。例如,一個專案管理應用程式可能每個專案每月收取 10 美元。若要輕鬆增加或減少訂閱數量,請使用 incrementQuantitydecrementQuantity 方法:

php
$user = User::find(1);

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

// Add five to the subscription's current quantity...
$user->subscription()->incrementQuantity(5);

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

// Subtract five from the subscription's current quantity...
$user->subscription()->decrementQuantity(5);

另外,您可以使用 updateQuantity 方法設定特定的數量:

php
$user->subscription()->updateQuantity(10);

noProrate 方法可用於更新訂閱數量而不按比例收取費用:

php
$user->subscription()->noProrate()->updateQuantity(10);

多產品訂閱的數量

如果您的訂閱是多產品訂閱,您應該將您希望增加或減少數量的價格 ID 作為第二個參數傳遞給增加/減少方法:

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

多產品訂閱

多產品訂閱讓您可以將多個計費產品指派給單一訂閱。例如,假設您正在建立一個客戶服務「服務台」應用程式,其基本訂閱價格為每月 10 美元,但提供每月額外 15 美元的即時聊天附加產品。

建立訂閱結帳會話時,您可以將價格陣列作為 subscribe 方法的第一個參數傳遞,為給定的訂閱指定多個產品:

php
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 訂閱將附加兩個價格。這兩個價格將在其各自的計費週期中收費。如果有必要,您可以傳遞一個關聯陣列的鍵/值對,以指示每個價格的特定數量:

php
$user = User::find(1);

$checkout = $user->subscribe('default', ['price_monthly', 'price_chat' => 5]);

如果您想將另一個價格新增到現有訂閱中,您必須使用訂閱的 swap 方法。呼叫 swap 方法時,您也應該同時包含訂閱目前的價格和數量:

php
$user = User::find(1);

$user->subscription()->swap(['price_chat', 'price_original' => 2]);

上述範例將新增新價格,但客戶將不會在下一個計費週期之前收到其帳單。如果您想立即向客戶開立帳單,可以使用 swapAndInvoice 方法:

php
$user->subscription()->swapAndInvoice(['price_chat', 'price_original' => 2]);

您可以使用 swap 方法並省略您想移除的價格,從訂閱中移除價格:

php
$user->subscription()->swap(['price_original' => 2]);

⚠️ 警告

您不能移除訂閱上的最後一個價格。相反地,您應該直接取消訂閱。

多重訂閱

Paddle 允許您的客戶同時擁有多個訂閱。例如,您可能經營一個提供游泳訂閱和舉重訂閱的健身房,並且每個訂閱可能會有不同的定價。當然,客戶應該能夠訂閱其中一個或兩個方案。

當您的應用程式建立訂閱時,您可以將訂閱類型作為第二個參數傳遞給 subscribe 方法。該類型可以是代表使用者正在發起訂閱類型的任何字串:

php
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 訂閱上的價格:

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

當然,您也可以完全取消訂閱:

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

暫停訂閱

若要暫停訂閱,請呼叫使用者訂閱上的 pause 方法:

php
$user->subscription()->pause();

當訂閱被暫停時,Cashier 會自動在您的資料庫中設定 paused_at 欄位。此欄位用於判斷 paused 方法何時開始回傳 true。例如,如果客戶在 3 月 1 日暫停訂閱,但該訂閱原本預計在 3 月 5 日才會續訂,那麼 paused 方法將會繼續回傳 false 直到 3 月 5 日。這是因為使用者通常被允許繼續使用應用程式直到其計費週期的結束。

預設情況下,暫停會在下一個計費期間發生,因此客戶可以使用他們已支付的剩餘期限。如果您想立即暫停訂閱,可以使用 pauseNow 方法:

php
$user->subscription()->pauseNow();

使用 pauseUntil 方法,您可以將訂閱暫停直到特定時間點:

php
$user->subscription()->pauseUntil(now()->plus(months: 1));

或者,您可以使用 pauseNowUntil 方法立即暫停訂閱直到給定的時間點:

php
$user->subscription()->pauseNowUntil(now()->plus(months: 1));

您可以使用 onPausedGracePeriod 方法判斷使用者是否已暫停訂閱但仍處於「寬限期」:

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

若要恢復暫停的訂閱,您可以呼叫訂閱上的 resume 方法:

php
$user->subscription()->resume();

⚠️ 警告

訂閱在暫停期間無法修改。如果您想更換方案或更新數量,必須先恢復訂閱。

取消訂閱

若要取消訂閱,請呼叫使用者訂閱上的 cancel 方法:

php
$user->subscription()->cancel();

當訂閱被取消時,Cashier 會自動在您的資料庫中設定 ends_at 欄位。此欄位用於判斷 subscribed 方法何時開始回傳 false。例如,如果客戶在 3 月 1 日取消訂閱,但該訂閱原本預計在 3 月 5 日才會結束,那麼 subscribed 方法將會繼續回傳 true 直到 3 月 5 日。這是因為使用者通常被允許繼續使用應用程式直到其計費週期的結束。

您可以使用 onGracePeriod 方法判斷使用者是否已取消訂閱但仍處於「寬限期」:

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

如果您希望立即取消訂閱,可以呼叫訂閱上的 cancelNow 方法:

php
$user->subscription()->cancelNow();

若要阻止處於寬限期的訂閱被取消,您可以呼叫 stopCancelation 方法:

php
$user->subscription()->stopCancelation();

⚠️ 警告

Paddle 的訂閱在取消後無法恢復。如果您的客戶希望恢復其訂閱,他們將必須建立一個新的訂閱。

訂閱試用

預先提供付款方式

如果您希望向客戶提供試用期,同時仍預先收集付款方式資訊,您應該在 Paddle 儀表板中,針對客戶訂閱的價格設定試用時間。然後,像平常一樣啟動結帳會話:

php
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 方法來判斷使用者是否處於試用期內:

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

要判斷現有試用期是否已過期,您可以使用 hasExpiredTrial 方法:

php
if ($user->hasExpiredTrial()) {
    // ...
}

若要判斷使用者是否正在進行特定訂閱類型的試用,您可以將該類型提供給 onTrialhasExpiredTrial 方法:

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

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

無需預先提供付款方式

如果您希望在不預先收集使用者付款方式資訊的情況下提供試用期,您可以將與使用者關聯的客戶記錄上的 trial_ends_at 欄位設定為您期望的試用期結束日期。這通常在使用者註冊時完成:

php
use App\Models\User;

$user = User::create([
    // ...
]);

$user->createAsCustomer([
    'trial_ends_at' => now()->plus(days: 10)
]);

Cashier 將此類試用稱為「通用試用」,因為它未附加到任何現有訂閱。User 實例上的 onTrial 方法將在當前日期未超過 trial_ends_at 值時返回 true

php
if ($user->onTrial()) {
    // User is within their trial period...
}

一旦您準備好為使用者建立實際訂閱,您可以像往常一樣使用 subscribe 方法:

php
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。您也可以傳遞一個可選的訂閱類型參數,如果您希望取得除了預設訂閱之外的特定訂閱的試用期結束日期:

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

如果您希望明確知道使用者正處於其「通用」試用期內且尚未建立實際訂閱,您可以使用 onGenericTrial 方法:

php
if ($user->onGenericTrial()) {
    // User is within their "generic" trial period...
}

延長或啟用試用期

您可以透過呼叫 extendTrial 方法並指定試用期應結束的時間點來延長訂閱上的現有試用期:

php
$user->subscription()->extendTrial(now()->plus(days: 5));

或者,您可以透過在訂閱上呼叫 activate 方法來立即結束試用期並啟用訂閱:

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

處理 Paddle Webhook

Paddle 可以透過 webhooks 向您的應用程式發送各種事件通知。預設情況下,Cashier 服務提供者會註冊一個指向 Cashier webhook 控制器的路由。此控制器將處理所有傳入的 webhook 請求。

預設情況下,此控制器會自動處理因多次扣款失敗而取消的訂閱、訂閱更新以及付款方式變更;然而,正如我們即將發現的,您可以擴展此控制器來處理任何您喜歡的 Paddle webhook 事件。

為確保您的應用程式能夠處理 Paddle webhooks,請務必在 Paddle 控制面板中設定 webhook URL。預設情況下,Cashier 的 webhook 控制器會響應 /paddle/webhook URL 路徑。您應該在 Paddle 控制面板中啟用的所有 webhooks 的完整列表如下:

  • Customer Updated
  • Transaction Completed
  • Transaction Updated
  • Subscription Created
  • Subscription Updated
  • Subscription Paused
  • Subscription Canceled

⚠️ 警告

請務必使用 Cashier 內建的 webhook 簽章驗證中介層來保護傳入的請求。

Webhooks 與 CSRF 保護

由於 Paddle webhooks 需要繞過 Laravel 的 CSRF 保護,您應確保 Laravel 不會嘗試驗證傳入 Paddle webhook 的 CSRF token。為此,您應在應用程式的 bootstrap/app.php 檔案中將 paddle/* 從 CSRF 保護中排除:

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

Webhooks 與本地開發

為了讓 Paddle 在本地開發期間能夠向您的應用程式發送 webhooks,您需要透過網站共享服務(例如 NgrokExpose)公開您的應用程式。如果您正在使用 Laravel Sail 在本地開發您的應用程式,您可以使用 Sail 的 網站共享指令

定義 Webhook 事件處理器

Cashier 會自動處理因扣款失敗而取消的訂閱以及其他常見的 Paddle webhooks。但是,如果您有其他想要處理的 webhook 事件,您可以透過監聽 Cashier 派發的以下事件來實現:

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

這兩個事件都包含 Paddle webhook 的完整負載 (payload)。例如,如果您希望處理 transaction.billed webhook,您可以註冊一個 監聽器來處理該事件:

php
<?php

namespace App\Listeners;

use Laravel\Paddle\Events\WebhookReceived;

class PaddleEventListener
{
    /**
     * Handle received Paddle webhooks.
     */
    public function handle(WebhookReceived $event): void
    {
        if ($event->payload['event_type'] === 'transaction.billed') {
            // Handle the incoming event...
        }
    }
}

Cashier 也會發出專門針對接收到的 webhook 類型所設計的事件。除了來自 Paddle 的完整負載外,它們還包含用於處理 webhook 的相關模型,例如可計費模型、訂閱或收據:

  • 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

您也可以透過在應用程式的 .env 檔案中定義 CASHIER_WEBHOOK 環境變數來覆寫預設的內建 webhook 路由。此值應為您的 webhook 路由的完整 URL,並且需要與您在 Paddle 控制面板中設定的 URL 相符:

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

驗證 Webhook 簽章

為了保護您的 webhooks,您可以使用 Paddle 的 webhook 簽章。為方便起見,Cashier 自動包含一個中介層,用於驗證傳入的 Paddle webhook 請求是否有效。

要啟用 webhook 驗證,請確保在應用程式的 .env 檔案中定義了 PADDLE_WEBHOOK_SECRET 環境變數。webhook Secret 可以從您的 Paddle 帳戶儀表板中取得。

單次收費

產品收費

如果您想為客戶啟動產品購買,您可以使用可計費模型實例上的 checkout 方法來產生購買的結帳會話。checkout 方法接受一個或多個價格 ID。如有必要,可以使用關聯陣列來提供正在購買的產品數量:

php
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 結帳小工具並完成購買:

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

結帳會話有一個 customData 方法,允許您將您希望的任何自訂資料傳遞給底層的交易建立。請參閱 Paddle 文件,以了解更多關於傳遞自訂資料時可用的選項:

php
$checkout = $user->checkout('pri_tshirt')
    ->customData([
        'custom_option' => $value,
    ]);

交易退款

交易退款會將退款金額退還到您客戶在購買時使用的付款方式。如果您需要退款 Paddle 購買,您可以使用 Cashier\Paddle\Transaction 模型上的 refund 方法。此方法接受一個理由作為第一個引數,一個或多個價格 ID 以及可選金額的關聯陣列進行退款。您可以使用 transactions 方法擷取給定可計費模型的交易。

例如,假設我們要退款針對 pri_123pri_456 價格的特定交易。我們希望完全退款 pri_123,但只退款 pri_456 兩美元:

php
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...
]);

上面的範例退款交易中的特定商品。如果您想退款整個交易,只需提供一個理由:

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

有關退款的更多資訊,請查閱 Paddle 的退款文件

⚠️ 警告

退款在完全處理之前必須始終經過 Paddle 批准。

交易入帳

如同退款一樣,您也可以為交易入帳。交易入帳會將資金增加到客戶的餘額中,以便用於將來的購買。交易入帳只能用於手動收款的交易,而不能用於自動收款的交易(例如訂閱),因為 Paddle 會自動處理訂閱入帳:

php
$transaction = $user->transactions()->first();

// Credit a specific line item fully...
$response = $transaction->credit('Compensation', 'pri_123');

有關更多資訊,請參閱 Paddle 關於入帳的文件

⚠️ 警告

入帳只能應用於手動收款的交易。自動收款的交易由 Paddle 本身入帳。

交易

您可以透過 transactions 屬性輕鬆擷取可計費模型交易的陣列:

php
use App\Models\User;

$user = User::find(1);

$transactions = $user->transactions;

交易代表您產品和購買的付款,並附有發票。只有已完成的交易才會儲存在您應用程式的資料庫中。

當列出客戶的交易時,您可以使用交易實例的方法來顯示相關的付款資訊。例如,您可能希望在表格中列出所有交易,讓使用者可以輕鬆下載任何發票:

html
<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 路由可能看起來像這樣:

php
use Illuminate\Http\Request;
use Laravel\Paddle\Transaction;

Route::get('/download-invoice/{transaction}', function (Request $request, Transaction $transaction) {
    return $transaction->redirectToInvoicePdf();
})->name('download-invoice');

過去與即將到來的付款

您可以使用 lastPaymentnextPayment 方法來擷取並顯示客戶針對週期性訂閱的過去或即將到來的付款:

php
use App\Models\User;

$user = User::find(1);

$subscription = $user->subscription();

$lastPayment = $subscription->lastPayment();
$nextPayment = $subscription->nextPayment();

這兩個方法都將返回 Laravel\Paddle\Payment 的實例;然而,當交易尚未透過 Webhook 同步時,lastPayment 將返回 null,而當帳單週期結束時(例如訂閱已取消),nextPayment 將返回 null

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

測試

在測試時,您應該手動測試您的帳務流程,以確保您的整合按預期運作。

對於自動化測試,包括在 CI 環境中執行的測試,您可以使用 Laravel 的 HTTP Client 來模擬對 Paddle 的 HTTP 呼叫。儘管這不會測試 Paddle 的實際回應,但它確實提供了一種無需實際呼叫 Paddle API 即可測試您應用程式的方法。