Laravel Cashier (Paddle)
簡介
本文件適用於 Cashier Paddle 2.x 與 Paddle Billing 的整合。如果您仍在使用 Paddle Classic,則應使用 Cashier Paddle 1.x。
Laravel Cashier Paddle 提供了一個富有表現力、流暢的介面,用於 Paddle 的 訂閱計費服務。它可以處理您所畏懼的幾乎所有樣板訂閱計費程式碼。除了基本的訂閱管理外,Cashier 還可以處理:交換訂閱、訂閱「數量」、暫停訂閱、取消寬限期等等。
在深入研究 Cashier Paddle 之前,我們建議您也查看 Paddle 的 概念指南 和 API 文件。
升級 Cashier
升級到新版本的 Cashier 時,請務必仔細查看升級指南。
安裝
首先,使用 Composer 套件管理器安裝適用於 Paddle 的 Cashier 套件
composer require laravel/cashier-paddle
接下來,您應該使用 vendor:publish
Artisan 命令發佈 Cashier 遷移檔案
php artisan vendor:publish --tag="cashier-migrations"
然後,您應該執行應用程式的資料庫遷移。Cashier 遷移將建立一個新的 customers
資料表。此外,還將建立新的 subscriptions
和 subscription_items
資料表,以儲存您客戶的所有訂閱。最後,將建立新的 transactions
資料表,以儲存與您的客戶相關的所有 Paddle 交易
php artisan migrate
為確保 Cashier 能正確處理所有 Paddle 事件,請記得設定 Cashier 的 Webhook 處理。
Paddle 沙箱
在本地和暫存開發期間,您應該註冊一個 Paddle 沙箱帳戶。此帳戶將為您提供一個沙箱環境,以測試和開發您的應用程式,而無需進行實際付款。您可以使用 Paddle 的測試卡號來模擬各種付款情境。
使用 Paddle 沙箱環境時,您應該在應用程式的 .env
檔案中將 PADDLE_SANDBOX
環境變數設定為 true
PADDLE_SANDBOX=true
完成應用程式的開發後,您可以申請一個 Paddle 廠商帳戶。在您的應用程式投入生產之前,Paddle 需要批准您的應用程式網域。
設定
可計費模型
在使用 Cashier 之前,您必須將 Billable
trait 新增到您的使用者模型定義中。此 trait 提供各種方法,讓您執行常見的計費任務,例如建立訂閱和更新付款方式資訊
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 金鑰
接下來,您應該在應用程式的 .env
檔案中設定您的 Paddle 金鑰。您可以從 Paddle 控制面板中擷取您的 Paddle API 金鑰
PADDLE_CLIENT_SIDE_TOKEN=your-paddle-client-side-tokenPADDLE_API_KEY=your-paddle-api-keyPADDLE_RETAIN_KEY=your-paddle-retain-keyPADDLE_WEBHOOK_SECRET="your-paddle-webhook-secret"PADDLE_SANDBOX=true
當您使用Paddle 的沙箱環境時,PADDLE_SANDBOX
環境變數應設定為 true
。如果您要將應用程式部署到生產環境並使用 Paddle 的即時廠商環境,則 PADDLE_SANDBOX
變數應設定為 false
。
PADDLE_RETAIN_KEY
是可選的,只有在您將 Paddle 與 Retain 搭配使用時才應設定。
Paddle JS
Paddle 依靠自己的 JavaScript 程式庫來啟動 Paddle 結帳小工具。您可以將 @paddleJS
Blade 指令放置在應用程式版面配置的結束 </head>
標籤之前,來載入 JavaScript 程式庫
<head> ... @paddleJS</head>
貨幣設定
您可以指定一個地區設定,用於格式化要在發票上顯示的貨幣值。在內部,Cashier 使用 PHP 的 NumberFormatter
類別 來設定貨幣地區設定
CASHIER_CURRENCY_LOCALE=nl_BE
為了使用 en
以外的地區設定,請確保您的伺服器上已安裝並設定 ext-intl
PHP 擴充功能。
覆寫預設模型
您可以自由擴充 Cashier 內部使用的模型,方法是定義自己的模型並擴充相應的 Cashier 模型
use Laravel\Paddle\Subscription as CashierSubscription; class Subscription extends CashierSubscription{ // ...}
定義您的模型後,您可以透過 Laravel\Paddle\Cashier
類別指示 Cashier 使用您的自訂模型。通常,您應該在應用程式的 App\Providers\AppServiceProvider
類別的 boot
方法中告知 Cashier 關於您的自訂模型
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 Checkout 之前,您應該在 Paddle 儀表板中定義具有固定價格的產品。此外,您應該設定 Paddle 的 Webhook 處理。
透過您的應用程式提供產品和訂閱計費可能會令人卻步。但是,由於 Cashier 和 Paddle 的 Checkout Overlay,您可以輕鬆建立現代、強大的付款整合。
若要向客戶收取非定期、單次收費產品的費用,我們將利用 Cashier 使用 Paddle 的 Checkout Overlay 向客戶收費,他們將在其中提供其付款詳細資訊並確認購買。透過 Checkout Overlay 完成付款後,客戶將被重新導向至您在應用程式中選擇的成功網址
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 時,「價格」指的是 特定產品所定義的價格。
如有必要,checkout
方法會自動在 Paddle 中建立一個客戶,並將該 Paddle 客戶記錄連結到您應用程式資料庫中對應的使用者。在完成結帳流程後,客戶將被重新導向至專用的成功頁面,您可以在該頁面上向客戶顯示訊息。
在 buy
視圖中,我們將包含一個按鈕來顯示結帳覆蓋層。paddle-button
Blade 組件已包含在 Cashier Paddle 中;但是,您也可以手動渲染覆蓋層結帳。
<x-paddle-button :checkout="$checkout" class="px-8 py-4"> Buy Product</x-paddle-button>
提供元數據給 Paddle 結帳
在銷售產品時,通常會透過您應用程式所定義的 Cart
和 Order
模型來追蹤已完成的訂單和購買的產品。當將客戶重新導向至 Paddle 的結帳覆蓋層以完成購買時,您可能需要提供現有的訂單識別碼,以便在客戶重新導向回您的應用程式時,將完成的購買與對應的訂單關聯起來。
為了實現這一點,您可以向 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
方法。當然,您的應用程式負責在客戶加入這些商品時,將這些商品與「購物車」或訂單關聯起來。我們也透過 customData
方法將訂單 ID 提供給 Paddle 結帳覆蓋層。
當然,您可能需要在客戶完成結帳流程後,將訂單標記為「完成」。為了實現這一點,您可以監聽 Paddle 發送的 webhook,以及 Cashier 通過事件發出的 webhook,以便將訂單資訊儲存在您的資料庫中。
首先,監聽 Cashier 發送的 TransactionCompleted
事件。通常,您應該在應用程式的 AppServiceProvider
的 boot
方法中註冊事件監聽器。
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
監聽器可能如下所示
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 儀表板中定義具有固定價格的產品。此外,您應該設定 Paddle 的 Webhook 處理。
透過您的應用程式提供產品和訂閱計費可能會令人卻步。但是,由於 Cashier 和 Paddle 的 Checkout Overlay,您可以輕鬆建立現代、強大的付款整合。
要了解如何使用 Cashier 和 Paddle 的結帳覆蓋層銷售訂閱,讓我們考慮一個簡單的訂閱服務情境,其中包含基本的每月 (price_basic_monthly
) 和每年 (price_basic_yearly
) 方案。這兩個價格可以在我們的 Paddle 儀表板中歸類為「基本」產品 (pro_basic
)。此外,我們的訂閱服務可能還會提供一個專家方案,即 pro_expert
。
首先,讓我們了解客戶如何訂閱我們的服務。當然,您可以想像客戶可能會在我們應用程式的定價頁面上點擊「訂閱」按鈕以選擇基本方案。此按鈕將會針對他們選擇的方案叫用 Paddle 結帳覆蓋層。首先,讓我們透過 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
視圖中,我們將包含一個按鈕來顯示結帳覆蓋層。paddle-button
Blade 組件已包含在 Cashier Paddle 中;但是,您也可以手動渲染覆蓋層結帳。
<x-paddle-button :checkout="$checkout" class="px-8 py-4"> Subscribe</x-paddle-button>
現在,當點擊「訂閱」按鈕時,客戶將能夠輸入他們的付款詳細資訊並啟動他們的訂閱。為了知道他們的訂閱何時真正開始(因為某些付款方式需要幾秒鐘才能處理),您也應該設定 Cashier 的 webhook 處理。
既然客戶可以開始訂閱,我們需要限制我們應用程式的某些部分,以便只有訂閱的使用者才能存取這些部分。當然,我們始終可以透過 Cashier 的 Billable
trait 所提供的 subscribed
方法來判斷使用者目前的訂閱狀態。
@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
建立訂閱中間件
為了方便起見,您可能希望建立一個中間件,以判斷傳入的請求是否來自訂閱的使用者。一旦定義了這個中間件,您就可以輕鬆地將其分配給路由,以防止未訂閱的使用者存取該路由。
<?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); }}
一旦定義了中間件,您就可以將其分配給路由
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 的 webhook 處理,Cashier 就會透過檢查來自 Paddle 的傳入 webhook,自動保持您應用程式中與 Cashier 相關的資料庫表格同步。因此,例如,當您透過 Paddle 的儀表板取消客戶的訂閱時,Cashier 將收到相應的 webhook,並在您應用程式的資料庫中將訂閱標記為「已取消」。
結帳 Session
大多數向客戶收費的操作都是透過 Paddle 的 結帳覆蓋層小工具或使用內嵌結帳來使用「結帳」。
在使用 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 組件。您可以將結帳流程作為「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 將向您的應用程式發送 webhook,以便您可以在您應用程式的資料庫中正確更新訂閱狀態。因此,您必須正確設定 webhook,以適應來自 Paddle 的狀態變更。
在訂閱狀態變更後,接收相應 webhook 的延遲通常很小,但您應該在您的應用程式中考慮到這一點,因為您的使用者的訂閱可能在完成結帳後不會立即可用。
手動渲染覆蓋層結帳
您也可以在不使用 Laravel 的內建 Blade 組件的情況下手動渲染覆蓋層結帳。首先,產生結帳流程,如先前範例所示。
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 的「覆蓋層」樣式結帳小工具,Paddle 也提供內嵌顯示小工具的選項。雖然這種方法不允許您調整結帳的任何 HTML 欄位,但它允許您將小工具嵌入您的應用程式中。
為了讓您輕鬆開始使用內嵌結帳,Cashier 包含一個 paddle-checkout
Blade 組件。首先,您應該產生結帳流程
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 的 內嵌結帳指南和可用的結帳設定。
手動渲染內嵌結帳
您也可以在不使用 Laravel 的內建 Blade 組件的情況下手動渲染內嵌結帳。首先,產生結帳流程,如先前範例所示
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]);});
然後,您可以將結帳流程提供給 Paddle 按鈕或 內嵌結帳 Blade 組件。
價格預覽
Paddle 允許您自訂每種貨幣的價格,基本上允許您為不同的國家/地區設定不同的價格。Cashier Paddle 允許您使用 previewPrices
方法檢索所有這些價格。此方法接受您希望檢索價格的價格 ID
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>
如需更多資訊,請查看 Paddle 有關價格預覽的 API 文件。
客戶價格預覽
如果使用者已經是客戶,並且您想要顯示適用於該客戶的價格,您可以透過直接從客戶實例檢索價格來執行此操作
use App\Models\User; $prices = User::find(1)->previewPrices(['pri_123', 'pri_456']);
在內部,Cashier 將使用使用者的客戶 ID 來檢索其貨幣的價格。因此,例如,居住在美國的使用者將看到以美元計價的價格,而居住在比利時的使用者將看到以歐元計價的價格。如果找不到匹配的貨幣,將使用產品的預設貨幣。您可以在 Paddle 控制面板中自訂產品或訂閱方案的所有價格。
折扣
您也可以選擇在折扣後顯示價格。當呼叫 previewPrices
方法時,您可以透過 discount_id
選項提供折扣 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 允許您在建立結帳流程時為您的客戶定義一些有用的預設值。設定這些預設值可讓您預先填入客戶的電子郵件地址和姓名,以便他們可以立即跳到結帳小工具的付款部分。您可以透過覆寫您的可計費模型上的以下方法來設定這些預設值
/** * 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 檢索客戶。此方法將傳回可計費模型的實例
use Laravel\Paddle\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,使用者在成功完成結帳後將被重新導向到該 URL。傳遞給 subscribe
方法的第二個參數應為訂閱的內部「類型」。如果您的應用程式僅提供單一訂閱,您可以將其稱為 default
或 primary
。此訂閱類型僅供內部應用程式使用,不應向使用者顯示。此外,它不應包含空格,且在建立訂閱後永遠不應更改。
您還可以透過 customData
方法提供關於訂閱的自訂中繼資料陣列。
$checkout = $request->user()->subscribe($premium = 12345, 'default') ->customData(['key' => 'value']) ->returnTo(route('home'));
建立訂閱結帳會期後,可以將結帳會期提供給 Cashier Paddle 隨附的 paddle-button
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
。
if ($user->subscribed()) { // ...}
如果您的應用程式提供多個訂閱,您可以在呼叫 subscribed
方法時指定訂閱。
if ($user->subscribed('default')) { // ...}
subscribed
方法也非常適合作為路由中介軟體,允許您根據使用者的訂閱狀態篩選對路由和控制器的存取。
<?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
方法。此方法對於確定您是否應該向使用者顯示他們仍在試用期的警告非常有用。
if ($user->subscription()->onTrial()) { // ...}
subscribedToPrice
方法可用於根據給定的 Paddle 價格 ID 確定使用者是否已訂閱給定的方案。在此範例中,我們將確定使用者的 default
訂閱是否已有效訂閱每月價格。
if ($user->subscribedToPrice($monthly = 'pri_123', 'default')) { // ...}
recurring
方法可用於確定使用者目前是否處於有效訂閱狀態,且不再處於試用期或寬限期。
if ($user->subscription()->recurring()) { // ...}
已取消的訂閱狀態
若要確定使用者是否曾經是有效訂閱者但已取消訂閱,您可以使用 canceled
方法。
if ($user->subscription()->canceled()) { // ...}
您也可以確定使用者是否已取消訂閱,但仍處於「寬限期」,直到訂閱完全到期。例如,如果使用者在 3 月 5 日取消原定於 3 月 10 日到期的訂閱,則使用者在 3 月 10 日之前處於「寬限期」。此外,subscribed
方法在此期間仍會傳回 true
。
if ($user->subscription()->onGracePeriod()) { // ...}
逾期狀態
如果訂閱付款失敗,它將被標記為 past_due
。當您的訂閱處於此狀態時,它將不會處於活動狀態,直到客戶更新其付款資訊。您可以使用訂閱實例上的 pastDue
方法來確定訂閱是否已逾期。
if ($user->subscription()->pastDue()) { // ...}
當訂閱逾期時,您應該指示使用者更新其付款資訊。
如果您希望訂閱在 past_due
時仍被視為有效,您可以使用 Cashier 提供的 keepPastDueSubscriptionsActive
方法。通常,此方法應在您的 AppServiceProvider
的 register
方法中呼叫。
use Laravel\Paddle\Cashier; /** * Register any application services. */public function register(): void{ Cashier::keepPastDueSubscriptionsActive();}
當訂閱處於 past_due
狀態時,除非更新付款資訊,否則無法變更。因此,當訂閱處於 past_due
狀態時,swap
和 updateQuantity
方法將會擲回例外狀況。
訂閱範圍
大多數訂閱狀態也可用作查詢範圍,以便您可以輕鬆查詢資料庫中處於給定狀態的訂閱。
// Get all valid subscriptions...$subscriptions = Subscription::query()->valid()->get(); // Get all of the canceled subscriptions for a user...$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
方法時,您必須提供一個或多個價格 ID。
// 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
方法。
$response = $user->subscription()->chargeAndInvoice('pri_123');
更新付款資訊
Paddle 始終為每個訂閱儲存一種付款方式。如果您想更新訂閱的預設付款方式,您應該使用訂閱模型上的 redirectToUpdatePaymentMethod
方法將您的客戶重新導向到 Paddle 的託管付款方式更新頁面。
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
方法。
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(); // 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
方法設定特定數量。
$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
。例如,如果客戶在 3 月 1 日暫停訂閱,但訂閱未排定在 3 月 5 日之前再次續訂,則 paused
方法將繼續傳回 false
,直到 3 月 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
。例如,如果客戶在 3 月 1 日取消訂閱,但該訂閱原定於 3 月 5 日結束,則 subscribed
方法將繼續回傳 true
,直到 3 月 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 將此類型的試用稱為「通用試用」,因為它未附加到任何現有的訂閱。如果當前日期未超過 trial_ends_at
的值,則 User
實例上的 onTrial
方法將回傳 true
。
if ($user->onTrial()) { // User is within their trial period...}
一旦您準備好為使用者建立實際的訂閱,您可以像平常一樣使用 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()) { // User is within their "generic" trial period...}
延長或啟用試用
您可以透過調用 extendTrial
方法並指定試用期應結束的時間點,來延長訂閱上現有的試用期。
$user->subscription()->extendTrial(now()->addDays(5));
或者,您可以透過在訂閱上調用 activate
方法來結束其試用期,立即啟用訂閱。
$user->subscription()->activate();
處理 Paddle Webhook
Paddle 可以透過 webhook 通知您的應用程式各種事件。預設情況下,指向 Cashier 的 webhook 控制器的路由由 Cashier 服務提供者註冊。此控制器將處理所有傳入的 webhook 請求。
預設情況下,此控制器將自動處理過多付款失敗的訂閱取消、訂閱更新和付款方式變更;然而,正如我們很快將發現的那樣,您可以擴展此控制器來處理任何您喜歡的 Paddle webhook 事件。
為了確保您的應用程式可以處理 Paddle webhook,請務必在 Paddle 控制面板中設定 webhook URL。預設情況下,Cashier 的 webhook 控制器會回應 /paddle/webhook
URL 路徑。您應該在 Paddle 控制面板中啟用的所有 webhook 的完整列表如下:
- 客戶已更新
- 交易已完成
- 交易已更新
- 訂閱已建立
- 訂閱已更新
- 訂閱已暫停
- 訂閱已取消
請確保使用 Cashier 隨附的webhook 簽名驗證中介軟體來保護傳入的請求。
Webhook 和 CSRF 保護
由於 Paddle webhook 需要繞過 Laravel 的CSRF 保護,因此您應確保 Laravel 不會嘗試驗證傳入的 Paddle webhook 的 CSRF 權杖。為了實現此目的,您應該在應用程式的 bootstrap/app.php
檔案中排除 paddle/*
的 CSRF 保護。
->withMiddleware(function (Middleware $middleware) { $middleware->validateCsrfTokens(except: [ 'paddle/*', ]);})
Webhook 和本地開發
為了讓 Paddle 能夠在本地開發期間將 webhook 發送到您的應用程式,您需要透過網站共用服務(例如 Ngrok 或 Expose)公開您的應用程式。如果您使用 Laravel Sail 在本地開發您的應用程式,您可以使用 Sail 的網站共用命令。
定義 Webhook 事件處理常式
Cashier 會自動處理付款失敗的訂閱取消和其他常見的 Paddle webhook。然而,如果您有其他想要處理的 webhook 事件,您可以透過監聽 Cashier 發送的以下事件來處理:
-
Laravel\Paddle\Events\WebhookReceived
-
Laravel\Paddle\Events\WebhookHandled
這兩個事件都包含 Paddle webhook 的完整 payload。例如,如果您想處理 transaction.billed
webhook,您可以註冊一個監聽器來處理該事件。
<?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 的完整 payload 之外,它們還包含用於處理 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 相符。
CASHIER_WEBHOOK=https://example.com/my-paddle-webhook-url
驗證 Webhook 簽名
為了保護您的 webhook 安全,您可以使用Paddle 的 webhook 簽名。為了方便起見,Cashier 會自動包含一個中介軟體,用於驗證傳入的 Paddle webhook 請求是否有效。
若要啟用 webhook 驗證,請確保在應用程式的 .env
檔案中定義了 PADDLE_WEBHOOK_SECRET
環境變數。webhook 密鑰可以從您的 Paddle 帳戶儀表板中檢索。
單次收費
產品收費
如果您想為客戶發起產品購買,您可以使用可計費模型實例上的 checkout
方法來產生購買的結帳流程。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 提供的 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 購買款項,您可以使用 Cashier\Paddle\Transaction
模型上的 refund
方法。此方法將原因作為第一個參數,並將一個或多個要退款的價格 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(); // Credit a specific line item fully...$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
的實例;但是,當 webhook 尚未同步交易時,lastPayment
將回傳 null
,而當帳單週期已結束時(例如當訂閱已取消時),nextPayment
將回傳 null
。
Next payment: {{ $nextPayment->amount() }} due on {{ $nextPayment->date()->format('d/m/Y') }}
測試
在測試期間,您應該手動測試您的帳單流程,以確保您的整合按預期工作。
對於自動化測試,包括在 CI 環境中執行的測試,您可以使用 Laravel 的 HTTP 客戶端來偽造對 Paddle 的 HTTP 呼叫。儘管這不會測試來自 Paddle 的實際回應,但它提供了一種在不實際呼叫 Paddle API 的情況下測試您的應用程式的方法。