コンテンツへスキップ

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パッケージをインストールします。

1composer require laravel/cashier-paddle

次に、vendor:publish Artisanコマンドを使用してCashierのマイグレーションファイルを公開する必要があります。

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

その後、アプリケーションのデータベースマイグレーションを実行してください。Cashierのマイグレーションにより、新しいcustomersテーブルが作成されます。さらに、顧客のすべてのサブスクリプションを保存するために、新しいsubscriptionsテーブルとsubscription_itemsテーブルが作成されます。最後に、顧客に関連するすべてのPaddle取引を保存するために、新しいtransactionsテーブルが作成されます。

1php artisan migrate

CashierがすべてのPaddleイベントを適切に処理できるように、CashierのWebhook処理を設定することを忘れないでください。

Paddleサンドボックス

ローカルおよびステージング開発中は、Paddleサンドボックスアカウントを登録する必要があります。このアカウントにより、実際の支払いを行わずにアプリケーションをテストおよび開発できるサンドボックス環境が提供されます。Paddleのテストカード番号を使用して、さまざまな支払いシナリオをシミュレートできます。

Paddleサンドボックス環境を使用する場合、アプリケーションの.envファイル内でPADDLE_SANDBOX環境変数をtrueに設定する必要があります。

1PADDLE_SANDBOX=true

アプリケーションの開発が完了したら、Paddleベンダーアカウントを申請することができます。アプリケーションが本番環境に配置される前に、Paddleはアプリケーションのドメインを承認する必要があります。

設定

Billableモデル

Cashierを使用する前に、ユーザーモデルの定義にBillableトレイトを追加する必要があります。このトレイトは、サブスクリプションの作成や支払い方法情報の更新など、一般的な課金タスクを実行できるさまざまなメソッドを提供します。

1use Laravel\Paddle\Billable;
2 
3class User extends Authenticatable
4{
5 use Billable;
6}

ユーザー以外の課金対象エンティティがある場合は、それらのクラスにもこのトレイトを追加できます。

1use Illuminate\Database\Eloquent\Model;
2use Laravel\Paddle\Billable;
3 
4class Team extends Model
5{
6 use Billable;
7}

APIキー

次に、アプリケーションの.envファイルでPaddleキーを設定する必要があります。Paddle APIキーはPaddleのコントロールパネルから取得できます。

1PADDLE_CLIENT_SIDE_TOKEN=your-paddle-client-side-token
2PADDLE_API_KEY=your-paddle-api-key
3PADDLE_RETAIN_KEY=your-paddle-retain-key
4PADDLE_WEBHOOK_SECRET="your-paddle-webhook-secret"
5PADDLE_SANDBOX=true

PADDLE_SANDBOX環境変数は、Paddleのサンドボックス環境を使用している場合にtrueに設定する必要があります。アプリケーションを本番環境にデプロイし、Paddleのライブベンダー環境を使用している場合は、PADDLE_SANDBOX変数をfalseに設定する必要があります。

PADDLE_RETAIN_KEYはオプションであり、PaddleをRetainと共に使用している場合にのみ設定してください。

Paddle JS

Paddleは、Paddleチェックアウトウィジェットを開始するために独自のJavaScriptライブラリに依存しています。アプリケーションレイアウトの閉じる</head>タグの直前に@paddleJS Bladeディレクティブを配置することで、JavaScriptライブラリを読み込むことができます。

1<head>
2 ...
3 
4 @paddleJS
5</head>

通貨設定

請求書に表示される金額のフォーマットに使用するロケールを指定できます。内部的に、CashierはPHPのNumberFormatterクラスを利用して通貨ロケールを設定します。

1CASHIER_CURRENCY_LOCALE=nl_BE

en以外のロケールを使用するには、サーバーにext-intl PHP拡張機能がインストールされ、設定されていることを確認してください。

デフォルトモデルのオーバーライド

独自のモデルを定義し、対応するCashierモデルを継承することで、Cashierが内部的に使用するモデルを自由に拡張できます。

1use Laravel\Paddle\Subscription as CashierSubscription;
2 
3class Subscription extends CashierSubscription
4{
5 // ...
6}

モデルを定義した後、Laravel\Paddle\Cashierクラスを介してカスタムモデルを使用するようCashierに指示できます。通常、アプリケーションのApp\Providers\AppServiceProviderクラスのbootメソッドでCashierにカスタムモデルについて通知する必要があります。

1use App\Models\Cashier\Subscription;
2use App\Models\Cashier\Transaction;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 Cashier::useSubscriptionModel(Subscription::class);
10 Cashier::useTransactionModel(Transaction::class);
11}

クイックスタート

製品の販売

Paddle Checkoutを利用する前に、Paddleダッシュボードで固定価格の製品を定義する必要があります。さらに、PaddleのWebhook処理を設定する必要があります。

アプリケーションを介して製品やサブスクリプションの課金を提供することは、気が引けるかもしれません。しかし、CashierとPaddleのチェックアウトオーバーレイのおかげで、最新で堅牢な支払いインテグレーションを簡単に構築できます。

継続的でない、単発課金の製品に対して顧客に請求するには、Cashierを利用してPaddleのチェックアウトオーバーレイで顧客に課金します。そこで顧客は支払い詳細を入力し、購入を確定します。チェックアウトオーバーレイを介して支払いが行われると、顧客はアプリケーション内で選択した成功URLにリダイレクトされます。

1use Illuminate\Http\Request;
2 
3Route::get('/buy', function (Request $request) {
4 $checkout = $request->user()->checkout('pri_deluxe_album')
5 ->returnTo(route('dashboard'));
6 
7 return view('buy', ['checkout' => $checkout]);
8})->name('checkout');

上記の例でわかるように、Cashierが提供するcheckoutメソッドを利用して、特定の「価格ID」に対して顧客にPaddleチェックアウトオーバーレイを提示するためのチェックアウトオブジェクトを作成します。Paddleを使用する場合、「価格」とは特定の製品に対して定義された価格を指します。

必要に応じて、checkoutメソッドは自動的にPaddleに顧客を作成し、そのPaddle顧客レコードをアプリケーションのデータベース内の対応するユーザーに接続します。チェックアウトセッションを完了した後、顧客は専用の成功ページにリダイレクトされ、そこで顧客に情報メッセージを表示できます。

buyビューでは、チェックアウトオーバーレイを表示するためのボタンを含めます。paddle-button BladeコンポーネントはCashier Paddleに含まれていますが、オーバーレイチェックアウトを手動でレンダリングすることもできます。

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

Paddleチェックアウトへのメタデータの提供

製品を販売する際、独自のアプリケーションで定義されたCartモデルやOrderモデルを介して、完了した注文や購入された製品を追跡するのが一般的です。顧客をPaddleのチェックアウトオーバーレイにリダイレクトして購入を完了させる際、顧客がアプリケーションにリダイレクトされたときに完了した購入を対応する注文に関連付けるために、既存の注文IDを提供する必要がある場合があります。

これを実現するために、checkoutメソッドにカスタムデータの配列を提供できます。ユーザーがチェックアウトプロセスを開始したときに、私たちのアプリケーション内で保留中のOrderが作成されると想像してみましょう。この例のCartモデルとOrderモデルは説明のためのものであり、Cashierによって提供されるものではないことに注意してください。これらの概念は、ご自身のアプリケーションのニーズに基づいて自由に実装できます。

1use App\Models\Cart;
2use App\Models\Order;
3use Illuminate\Http\Request;
4 
5Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) {
6 $order = Order::create([
7 'cart_id' => $cart->id,
8 'price_ids' => $cart->price_ids,
9 'status' => 'incomplete',
10 ]);
11 
12 $checkout = $request->user()->checkout($order->price_ids)
13 ->customData(['order_id' => $order->id]);
14 
15 return view('billing', ['checkout' => $checkout]);
16})->name('checkout');

上記の例でわかるように、ユーザーがチェックアウトプロセスを開始すると、カート/注文に関連するすべてのPaddle価格IDをcheckoutメソッドに提供します。もちろん、顧客がアイテムを追加する際に、これらのアイテムを「ショッピングカート」または注文に関連付けるのはアプリケーションの責任です。また、customDataメソッドを介して注文のIDをPaddleチェックアウトオーバーレイに提供します。

もちろん、顧客がチェックアウトプロセスを完了したら、注文を「完了」としてマークしたいでしょう。これを実現するために、Paddleからディスパッチされ、Cashierによってイベントとして発生させられるWebhookをリッスンして、注文情報をデータベースに保存できます。

始めるには、CashierによってディスパッチされるTransactionCompletedイベントをリッスンします。通常、イベントリスナーはアプリケーションのAppServiceProviderbootメソッドで登録する必要があります。

1use App\Listeners\CompleteOrder;
2use Illuminate\Support\Facades\Event;
3use Laravel\Paddle\Events\TransactionCompleted;
4 
5/**
6 * Bootstrap any application services.
7 */
8public function boot(): void
9{
10 Event::listen(TransactionCompleted::class, CompleteOrder::class);
11}

この例では、CompleteOrderリスナーは次のようになります。

1namespace App\Listeners;
2 
3use App\Models\Order;
4use Laravel\Paddle\Cashier;
5use Laravel\Paddle\Events\TransactionCompleted;
6 
7class CompleteOrder
8{
9 /**
10 * Handle the incoming Cashier webhook event.
11 */
12 public function handle(TransactionCompleted $event): void
13 {
14 $orderId = $event->payload['data']['custom_data']['order_id'] ?? null;
15 
16 $order = Order::findOrFail($orderId);
17 
18 $order->update(['status' => 'completed']);
19 }
20}

transaction.completedイベントに含まれるデータの詳細については、Paddleのドキュメントを参照してください:データに関する情報

サブスクリプションの販売

Paddle Checkoutを利用する前に、Paddleダッシュボードで固定価格の製品を定義する必要があります。さらに、PaddleのWebhook処理を設定する必要があります。

アプリケーションを介して製品やサブスクリプションの課金を提供することは、気が引けるかもしれません。しかし、CashierとPaddleのチェックアウトオーバーレイのおかげで、最新で堅牢な支払いインテグレーションを簡単に構築できます。

CashierとPaddleのチェックアウトオーバーレイを使用してサブスクリプションを販売する方法を学ぶために、基本的な月額(price_basic_monthly)と年額(price_basic_yearly)プランを持つサブスクリプションサービスの簡単なシナリオを考えてみましょう。これら2つの価格は、Paddleダッシュボードで「Basic」製品(pro_basic)の下にグループ化できます。さらに、私たちのサブスクリプションサービスは、pro_expertとしてExpertプランを提供するかもしれません。

まず、顧客が私たちのサービスに登録する方法を見てみましょう。もちろん、顧客がアプリケーションの価格ページでBasicプランの「登録」ボタンをクリックすることを想像できます。このボタンは、選択したプランのPaddleチェックアウトオーバーレイを呼び出します。始めるには、checkoutメソッドを介してチェックアウトセッションを開始しましょう。

1use Illuminate\Http\Request;
2 
3Route::get('/subscribe', function (Request $request) {
4 $checkout = $request->user()->checkout('price_basic_monthly')
5 ->returnTo(route('dashboard'));
6 
7 return view('subscribe', ['checkout' => $checkout]);
8})->name('subscribe');

subscribeビューでは、チェックアウトオーバーレイを表示するためのボタンを含めます。paddle-button BladeコンポーネントはCashier Paddleに含まれていますが、オーバーレイチェックアウトを手動でレンダリングすることもできます。

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

これで、「登録」ボタンがクリックされると、顧客は支払い詳細を入力し、サブスクリプションを開始できるようになります。サブスクリプションが実際にいつ開始されたかを知るために(一部の支払い方法は処理に数秒かかるため)、CashierのWebhook処理も設定する必要があります。

顧客がサブスクリプションを開始できるようになったので、アプリケーションの特定の部分を制限して、登録ユーザーのみがアクセスできるようにする必要があります。もちろん、CashierのBillableトレイトによって提供されるsubscribedメソッドを介して、ユーザーの現在のサブスクリプションステータスをいつでも判断できます。

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

ユーザーが特定の製品や価格に登録しているかどうかを簡単に判断することもできます。

1@if ($user->subscribedToProduct('pro_basic'))
2 <p>You are subscribed to our Basic product.</p>
3@endif
4 
5@if ($user->subscribedToPrice('price_basic_monthly'))
6 <p>You are subscribed to our monthly Basic plan.</p>
7@endif

登録済みミドルウェアの構築

便宜上、受信リクエストが登録ユーザーからのものかどうかを判断するミドルウェアを作成するとよいでしょう。このミドルウェアを定義したら、登録していないユーザーがルートにアクセスできないように、簡単にルートに割り当てることができます。

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class Subscribed
10{
11 /**
12 * Handle an incoming request.
13 */
14 public function handle(Request $request, Closure $next): Response
15 {
16 if (! $request->user()?->subscribed()) {
17 // Redirect user to billing page and ask them to subscribe...
18 return redirect('/subscribe');
19 }
20 
21 return $next($request);
22 }
23}

ミドルウェアが定義されたら、それをルートに割り当てることができます。

1use App\Http\Middleware\Subscribed;
2 
3Route::get('/dashboard', function () {
4 // ...
5})->middleware([Subscribed::class]);

顧客が課金プランを管理できるようにする

もちろん、顧客はサブスクリプションプランを別の製品や「ティア」に変更したいと思うかもしれません。上記の例では、顧客が月額サブスクリプションから年額サブスクリプションにプランを変更できるようにしたいでしょう。そのためには、以下のルートにつながるボタンのようなものを実装する必要があります。

1use Illuminate\Http\Request;
2 
3Route::put('/subscription/{price}/swap', function (Request $request, $price) {
4 $user->subscription()->swap($price); // With "$price" being "price_basic_yearly" for this example.
5 
6 return redirect()->route('dashboard');
7})->name('subscription.swap');

プランの変更だけでなく、顧客がサブスクリプションをキャンセルできるようにする必要もあります。プランの変更と同様に、次のルートにつながるボタンを提供してください。

1use Illuminate\Http\Request;
2 
3Route::put('/subscription/cancel', function (Request $request, $price) {
4 $user->subscription()->cancel();
5 
6 return redirect()->route('dashboard');
7})->name('subscription.cancel');

そして、あなたのサブスクリプションは請求期間の終わりにキャンセルされます。

CashierのWebhook処理を設定している限り、CashierはPaddleからの受信Webhookを検査することで、アプリケーションのCashier関連データベーステーブルを自動的に同期させます。したがって、たとえば、Paddleのダッシュボードを介して顧客のサブスクリプションをキャンセルすると、Cashierは対応するWebhookを受信し、アプリケーションのデータベースでサブスクリプションを「キャンセル済み」としてマークします。

チェックアウトセッション

顧客への課金操作のほとんどは、Paddleのチェックアウトオーバーレイウィジェットを介した「チェックアウト」を使用するか、インラインチェックアウトを利用して実行されます。

Paddleを使用してチェックアウト支払いを処理する前に、Paddleのチェックアウト設定ダッシュボードでアプリケーションのデフォルト支払いリンクを定義する必要があります。

オーバーレイチェックアウト

チェックアウトオーバーレイウィジェットを表示する前に、Cashierを使用してチェックアウトセッションを生成する必要があります。チェックアウトセッションは、実行すべき課金操作をチェックアウトウィジェットに通知します。

1use Illuminate\Http\Request;
2 
3Route::get('/buy', function (Request $request) {
4 $checkout = $user->checkout('pri_34567')
5 ->returnTo(route('dashboard'));
6 
7 return view('billing', ['checkout' => $checkout]);
8});

Cashierにはpaddle-button Bladeコンポーネントが含まれています。このコンポーネントにチェックアウトセッションを「プロップ」として渡すことができます。すると、このボタンがクリックされたときに、Paddleのチェックアウトウィジェットが表示されます。

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

デフォルトでは、これによりPaddleのデフォルトスタイルでウィジェットが表示されます。コンポーネントにdata-theme='light'属性のようなPaddleがサポートする属性を追加することで、ウィジェットをカスタマイズできます。

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

Paddleチェックアウトウィジェットは非同期です。ユーザーがウィジェット内でサブスクリプションを作成すると、PaddleはアプリケーションにWebhookを送信し、アプリケーションのデータベースでサブスクリプションの状態を適切に更新できるようにします。したがって、Paddleからの状態変化に対応するために、Webhookを適切に設定することが重要です。

サブスクリプションの状態が変更された後、対応するWebhookを受信するまでの遅延は通常ごくわずかですが、チェックアウト完了後すぐにユーザーのサブスクリプションが利用可能にならない可能性があることを考慮してアプリケーションで対応する必要があります。

オーバーレイチェックアウトの手動レンダリング

Laravelの組み込みBladeコンポーネントを使用せずに、オーバーレイチェックアウトを手動でレンダリングすることもできます。まず、以前の例で示したように、チェックアウトセッションを生成します。

1use Illuminate\Http\Request;
2 
3Route::get('/buy', function (Request $request) {
4 $checkout = $user->checkout('pri_34567')
5 ->returnTo(route('dashboard'));
6 
7 return view('billing', ['checkout' => $checkout]);
8});

次に、Paddle.jsを使用してチェックアウトを初期化できます。この例では、paddle_buttonクラスが割り当てられたリンクを作成します。Paddle.jsはこのクラスを検出し、リンクがクリックされるとオーバーレイチェックアウトを表示します。

1<?php
2$items = $checkout->getItems();
3$customer = $checkout->getCustomer();
4$custom = $checkout->getCustomData();
5?>
6 
7<a
8 href='#!'
9 class='paddle_button'
10 data-items='{!! json_encode($items) !!}'
11 @if ($customer) data-customer-id='{{ $customer->paddle_id }}' @endif
12 @if ($custom) data-custom-data='{{ json_encode($custom) }}' @endif
13 @if ($returnUrl = $checkout->getReturnUrl()) data-success-url='{{ $returnUrl }}' @endif
14>
15 Buy Product
16</a>

インラインチェックアウト

Paddleの「オーバーレイ」スタイルのチェックアウトウィジェットを利用したくない場合、Paddleはウィジェットをインラインで表示するオプションも提供しています。このアプローチではチェックアウトのHTMLフィールドを調整することはできませんが、ウィジェットをアプリケーション内に埋め込むことができます。

インラインチェックアウトを簡単に始められるように、Cashierにはpaddle-checkout Bladeコンポーネントが含まれています。まず、チェックアウトセッションを生成する必要があります。

1use Illuminate\Http\Request;
2 
3Route::get('/buy', function (Request $request) {
4 $checkout = $user->checkout('pri_34567')
5 ->returnTo(route('dashboard'));
6 
7 return view('billing', ['checkout' => $checkout]);
8});

次に、コンポーネントのcheckout属性にチェックアウトセッションを渡すことができます。

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

インラインチェックアウトコンポーネントの高さを調整するには、Bladeコンポーネントにheight属性を渡すことができます。

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

インラインチェックアウトのカスタマイズオプションに関する詳細については、Paddleのインラインチェックアウトに関するガイドおよび利用可能なチェックアウト設定を参照してください。

インラインチェックアウトの手動レンダリング

Laravelの組み込みBladeコンポーネントを使用せずに、インラインチェックアウトを手動でレンダリングすることもできます。まず、以前の例で示したようにチェックアウトセッションを生成します。

1use Illuminate\Http\Request;
2 
3Route::get('/buy', function (Request $request) {
4 $checkout = $user->checkout('pri_34567')
5 ->returnTo(route('dashboard'));
6 
7 return view('billing', ['checkout' => $checkout]);
8});

次に、Paddle.jsを使用してチェックアウトを初期化できます。この例では、Alpine.jsを使用してこれを示しますが、この例を独自のフロントエンドスタックに合わせて自由に変更できます。

1<?php
2$options = $checkout->options();
3 
4$options['settings']['frameTarget'] = 'paddle-checkout';
5$options['settings']['frameInitialHeight'] = 366;
6?>
7 
8<div class="paddle-checkout" x-data="{}" x-init="
9 Paddle.Checkout.open(@json($options));
10">
11</div>

ゲストチェックアウト

アプリケーションにアカウントを必要としないユーザーのために、チェックアウトセッションを作成する必要がある場合があります。そのためには、guestメソッドを使用できます。

1use Illuminate\Http\Request;
2use Laravel\Paddle\Checkout;
3 
4Route::get('/buy', function (Request $request) {
5 $checkout = Checkout::guest(['pri_34567'])
6 ->returnTo(route('home'));
7 
8 return view('billing', ['checkout' => $checkout]);
9});

その後、チェックアウトセッションをPaddleボタンまたはインラインチェックアウトのBladeコンポーネントに提供できます。

価格プレビュー

Paddleでは、通貨ごとに価格をカスタマイズすることができ、これにより異なる国に対して異なる価格を設定できます。Cashier Paddleでは、previewPricesメソッドを使用して、これらすべての価格を取得できます。このメソッドは、価格を取得したい価格IDを受け入れます。

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

通貨はリクエストのIPアドレスに基づいて決定されますが、オプションで特定の国を指定して価格を取得することもできます。

1use Laravel\Paddle\Cashier;
2 
3$prices = Cashier::previewPrices(['pri_123', 'pri_456'], ['address' => [
4 'country_code' => 'BE',
5 'postal_code' => '1234',
6]]);

価格を取得した後、好きなように表示できます。

1<ul>
2 @foreach ($prices as $price)
3 <li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
4 @endforeach
5</ul>

小計価格と税額を別々に表示することもできます。

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

詳細については、価格プレビューに関するPaddleのAPIドキュメントをご確認ください

顧客価格プレビュー

ユーザーがすでに顧客であり、その顧客に適用される価格を表示したい場合は、顧客インスタンスから直接価格を取得することで可能です。

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

内部的に、Cashierはユーザーの顧客IDを使用して、その通貨での価格を取得します。たとえば、米国に住んでいるユーザーは米ドルで価格が表示され、ベルギーに住んでいるユーザーはユーロで価格が表示されます。一致する通貨が見つからない場合は、製品のデフォルト通貨が使用されます。製品またはサブスクリプションプランのすべての価格は、Paddleのコントロールパネルでカスタマイズできます。

割引

割引後の価格を表示することも選択できます。previewPricesメソッドを呼び出す際に、discount_idオプションを介して割引IDを提供します。

1use Laravel\Paddle\Cashier;
2 
3$prices = Cashier::previewPrices(['pri_123', 'pri_456'], [
4 'discount_id' => 'dsc_123'
5]);

その後、計算された価格を表示します。

1<ul>
2 @foreach ($prices as $price)
3 <li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
4 @endforeach
5</ul>

顧客

顧客のデフォルト値

Cashierでは、チェックアウトセッションを作成する際に顧客の便利なデフォルト値を定義できます。これらのデフォルト値を設定すると、顧客のメールアドレスと名前を事前に入力できるため、すぐにチェックアウトウィジェットの支払い部分に進むことができます。これらのデフォルト値は、課金対象モデルで次のメソッドをオーバーライドすることで設定できます。

1/**
2 * Get the customer's name to associate with Paddle.
3 */
4public function paddleName(): string|null
5{
6 return $this->name;
7}
8 
9/**
10 * Get the customer's email address to associate with Paddle.
11 */
12public function paddleEmail(): string|null
13{
14 return $this->email;
15}

これらのデフォルト値は、チェックアウトセッションを生成するCashierのすべてのアクションで使用されます。

顧客の取得

Cashier::findBillableメソッドを使用して、Paddle顧客IDで顧客を取得できます。このメソッドは、課金対象モデルのインスタンスを返します。

1use Laravel\Paddle\Cashier;
2 
3$user = Cashier::findBillable($customerId);

顧客の作成

サブスクリプションを開始せずにPaddle顧客を作成したい場合があります。これはcreateAsCustomerメソッドを使用して実現できます。

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

Laravel\Paddle\Customerのインスタンスが返されます。Paddleで顧客が作成されたら、後日サブスクリプションを開始できます。オプションの$options配列を提供して、Paddle APIでサポートされている追加の顧客作成パラメータを渡すことができます。

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

サブスクリプション

サブスクリプションの作成

サブスクリプションを作成するには、まずデータベースから課金対象モデルのインスタンスを取得します。これは通常、App\Models\Userのインスタンスになります。モデルのインスタンスを取得したら、subscribeメソッドを使用してモデルのチェックアウトセッションを作成できます。

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

subscribeメソッドに与えられる最初の引数は、ユーザーが購読する特定の価格です。この値は、Paddleでの価格のIDに対応する必要があります。returnToメソッドは、ユーザーがチェックアウトを正常に完了した後にリダイレクトされるURLを受け入れます。subscribeメソッドに渡される2番目の引数は、サブスクリプションの内部的な「タイプ」であるべきです。アプリケーションが単一のサブスクリプションのみを提供する場合、これをdefaultまたはprimaryと呼ぶかもしれません。このサブスクリプションタイプは、内部的なアプリケーションの使用のみを目的としており、ユーザーに表示されることを意図していません。また、スペースを含んではならず、サブスクリプション作成後に変更してはなりません。

customDataメソッドを使用して、サブスクリプションに関するカスタムメタデータの配列を提供することもできます。

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

サブスクリプションのチェックアウトセッションが作成されたら、そのセッションをCashier Paddleに含まれるpaddle-button Bladeコンポーネントに提供できます。

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

ユーザーがチェックアウトを完了すると、Paddleからsubscription_created Webhookがディスパッチされます。CashierはこのWebhookを受信し、顧客のサブスクリプションを設定します。すべてのWebhookがアプリケーションで適切に受信され、処理されるようにするためには、Webhook処理を正しく設定していることを確認してください。

サブスクリプションステータスの確認

ユーザーがアプリケーションに登録すると、さまざまな便利なメソッドを使用してサブスクリプションのステータスを確認できます。まず、subscribedメソッドは、ユーザーが有効なサブスクリプションを持っている場合にtrueを返します。サブスクリプションが現在トライアル期間中であっても同様です。

1if ($user->subscribed()) {
2 // ...
3}

アプリケーションが複数のサブスクリプションを提供している場合、subscribedメソッドを呼び出す際にサブスクリプションを指定できます。

1if ($user->subscribed('default')) {
2 // ...
3}

subscribedメソッドは、ルートミドルウェアとしても非常に適しており、ユーザーのサブスクリプションステータスに基づいてルートやコントローラーへのアクセスをフィルタリングできます。

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class EnsureUserIsSubscribed
10{
11 /**
12 * Handle an incoming request.
13 *
14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
15 */
16 public function handle(Request $request, Closure $next): Response
17 {
18 if ($request->user() && ! $request->user()->subscribed()) {
19 // This user is not a paying customer...
20 return redirect('/billing');
21 }
22 
23 return $next($request);
24 }
25}

ユーザーがまだトライアル期間中かどうかを判断したい場合は、onTrialメソッドを使用できます。このメソッドは、ユーザーがまだトライアル期間中であることを警告表示すべきかどうかを判断するのに役立ちます。

1if ($user->subscription()->onTrial()) {
2 // ...
3}

subscribedToPriceメソッドは、指定されたPaddle価格IDに基づいてユーザーが特定のプランに登録しているかどうかを判断するために使用できます。この例では、ユーザーのdefaultサブスクリプションが月額価格に有効に登録されているかどうかを判断します。

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

recurringメソッドは、ユーザーが現在有効なサブスクリプションを利用しており、トライアル期間や猶予期間を過ぎているかどうかを判断するために使用できます。

1if ($user->subscription()->recurring()) {
2 // ...
3}

キャンセルされたサブスクリプションのステータス

ユーザーがかつてアクティブな購読者だったが、サブスクリプションをキャンセルしたかどうかを判断するには、canceledメソッドを使用できます。

1if ($user->subscription()->canceled()) {
2 // ...
3}

ユーザーがサブスクリプションをキャンセルしたが、サブスクリプションが完全に期限切れになるまで「猶予期間」にあるかどうかを判断することもできます。たとえば、ユーザーが3月10日に期限切れになる予定だったサブスクリプションを3月5日にキャンセルした場合、ユーザーは3月10日まで「猶予期間」にあります。また、この期間中、subscribedメソッドは引き続きtrueを返します。

1if ($user->subscription()->onGracePeriod()) {
2 // ...
3}

支払い遅延ステータス

サブスクリプションの支払いが失敗した場合、それはpast_dueとしてマークされます。サブスクリプションがこの状態にある間は、顧客が支払い情報を更新するまで有効になりません。サブスクリプションインスタンスのpastDueメソッドを使用して、サブスクリプションが支払い遅延状態にあるかどうかを判断できます。

1if ($user->subscription()->pastDue()) {
2 // ...
3}

サブスクリプションが支払い遅延の場合、ユーザーに支払い情報を更新するよう指示する必要があります。

サブスクリプションがpast_dueであっても引き続き有効と見なしたい場合は、Cashierが提供するkeepPastDueSubscriptionsActiveメソッドを使用できます。通常、このメソッドはAppServiceProviderregisterメソッドで呼び出すべきです。

1use Laravel\Paddle\Cashier;
2 
3/**
4 * Register any application services.
5 */
6public function register(): void
7{
8 Cashier::keepPastDueSubscriptionsActive();
9}

サブスクリプションがpast_due状態の場合、支払い情報が更新されるまで変更できません。そのため、サブスクリプションがpast_due状態のときにswapメソッドとupdateQuantityメソッドを使用すると例外がスローされます。

サブスクリプションスコープ

ほとんどのサブスクリプション状態はクエリスコープとしても利用できるため、特定の状態にあるサブスクリプションをデータベースから簡単にクエリできます。

1// Get all valid subscriptions...
2$subscriptions = Subscription::query()->valid()->get();
3 
4// Get all of the canceled subscriptions for a user...
5$subscriptions = $user->subscriptions()->canceled()->get();

利用可能なスコープの完全なリストは以下の通りです。

1Subscription::query()->valid();
2Subscription::query()->onTrial();
3Subscription::query()->expiredTrial();
4Subscription::query()->notOnTrial();
5Subscription::query()->active();
6Subscription::query()->recurring();
7Subscription::query()->pastDue();
8Subscription::query()->paused();
9Subscription::query()->notPaused();
10Subscription::query()->onPausedGracePeriod();
11Subscription::query()->notOnPausedGracePeriod();
12Subscription::query()->canceled();
13Subscription::query()->notCanceled();
14Subscription::query()->onGracePeriod();
15Subscription::query()->notOnGracePeriod();

サブスクリプションの単発課金

サブスクリプションの単発課金により、購読者にサブスクリプションに加えて一度限りの請求を行うことができます。chargeメソッドを呼び出す際には、1つまたは複数の価格IDを提供する必要があります。

1// Charge a single price...
2$response = $user->subscription()->charge('pri_123');
3 
4// Charge multiple prices at once...
5$response = $user->subscription()->charge(['pri_123', 'pri_456']);

chargeメソッドは、サブスクリプションの次の請求期間まで実際に顧客に請求しません。顧客にすぐに請求したい場合は、代わりにchargeAndInvoiceメソッドを使用できます。

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

支払い情報の更新

Paddleは常にサブスクリプションごとに支払い方法を保存します。サブスクリプションのデフォルトの支払い方法を更新したい場合は、サブスクリプションモデルのredirectToUpdatePaymentMethodメソッドを使用して、顧客をPaddleがホストする支払い方法更新ページにリダイレクトする必要があります。

1use Illuminate\Http\Request;
2 
3Route::get('/update-payment-method', function (Request $request) {
4 $user = $request->user();
5 
6 return $user->subscription()->redirectToUpdatePaymentMethod();
7});

ユーザーが情報の更新を完了すると、Paddleからsubscription_updated Webhookがディスパッチされ、アプリケーションのデータベースでサブスクリプションの詳細が更新されます。

プランの変更

ユーザーがアプリケーションに登録した後、新しいサブスクリプションプランに変更したい場合があります。ユーザーのサブスクリプションプランを更新するには、サブスクリプションのswapメソッドにPaddle価格のIDを渡す必要があります。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$user->subscription()->swap($premium = 'pri_456');

プランを変更し、次の請求サイクルを待たずにすぐにユーザーに請求したい場合は、swapAndInvoiceメソッドを使用できます。

1$user = User::find(1);
2 
3$user->subscription()->swapAndInvoice($premium = 'pri_456');

按分計算

デフォルトでは、Paddleはプラン間で切り替える際に料金を按分計算します。noProrateメソッドを使用して、料金を按分計算せずにサブスクリプションを更新できます。

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

按分計算を無効にして顧客に即時請求したい場合は、swapAndInvoiceメソッドとnoProrateを組み合わせて使用できます。

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

または、サブスクリプションの変更に対して顧客に請求しないようにするには、doNotBillメソッドを利用できます。

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

Paddleの按分計算ポリシーに関する詳細については、Paddleの按分計算のドキュメントを参照してください。

サブスクリプションの数量

サブスクリプションは「数量」に影響されることがあります。例えば、プロジェクト管理アプリケーションがプロジェクトごとに月額10ドルを請求する場合があります。サブスクリプションの数量を簡単に増減させるには、incrementQuantityメソッドとdecrementQuantityメソッドを使用します。

1$user = User::find(1);
2 
3$user->subscription()->incrementQuantity();
4 
5// Add five to the subscription's current quantity...
6$user->subscription()->incrementQuantity(5);
7 
8$user->subscription()->decrementQuantity();
9 
10// Subtract five from the subscription's current quantity...
11$user->subscription()->decrementQuantity(5);

あるいは、updateQuantityメソッドを使用して特定の数量を設定することもできます。

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

noProrateメソッドを使用して、料金を按分計算せずにサブスクリプションの数量を更新できます。

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

複数製品を持つサブスクリプションの数量

サブスクリプションが複数製品を持つサブスクリプションである場合、数量を増減させたい価格のIDを増減メソッドの2番目の引数として渡す必要があります。

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

複数製品を持つサブスクリプション

複数製品を持つサブスクリプションでは、単一のサブスクリプションに複数の課金製品を割り当てることができます。例えば、基本サブスクリプション価格が月額10ドルの顧客サービス「ヘルプデスク」アプリケーションを構築しており、追加で月額15ドルのライブチャットアドオン製品を提供していると想像してください。

サブスクリプションのチェックアウトセッションを作成する際、subscribeメソッドの最初の引数として価格の配列を渡すことで、特定のサブスクリプションに複数の製品を指定できます。

1use Illuminate\Http\Request;
2 
3Route::post('/user/subscribe', function (Request $request) {
4 $checkout = $request->user()->subscribe([
5 'price_monthly',
6 'price_chat',
7 ]);
8 
9 return view('billing', ['checkout' => $checkout]);
10});

上記の例では、顧客はdefaultサブスクリプションに2つの価格が関連付けられます。両方の価格は、それぞれの請求期間で請求されます。必要に応じて、各価格の特定の数量を示すためにキーと値のペアの連想配列を渡すことができます。

1$user = User::find(1);
2 
3$checkout = $user->subscribe('default', ['price_monthly', 'price_chat' => 5]);

既存のサブスクリプションに別の価格を追加したい場合は、サブスクリプションのswapメソッドを使用する必要があります。swapメソッドを呼び出す際には、サブスクリプションの現在の価格と数量も一緒に含める必要があります。

1$user = User::find(1);
2 
3$user->subscription()->swap(['price_chat', 'price_original' => 2]);

上記の例では新しい価格が追加されますが、顧客は次の請求サイクルまで請求されません。顧客にすぐに請求したい場合は、swapAndInvoiceメソッドを使用できます。

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

swapメソッドを使用し、削除したい価格を省略することで、サブスクリプションから価格を削除できます。

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

サブスクリプションの最後の価格は削除できません。代わりに、サブスクリプションをキャンセルする必要があります。

複数サブスクリプション

Paddleでは、顧客が複数のサブスクリプションを同時に持つことができます。例えば、スイミングのサブスクリプションとウェイトリフティングのサブスクリプションを提供するジムを運営しており、それぞれのサブスクリプションで価格が異なる場合があります。もちろん、顧客はどちらか一方または両方のプランに登録できるべきです。

アプリケーションがサブスクリプションを作成する際、subscribeメソッドの2番目の引数としてサブスクリプションのタイプを提供できます。タイプは、ユーザーが開始するサブスクリプションのタイプを表す任意の文字列にすることができます。

1use Illuminate\Http\Request;
2 
3Route::post('/swimming/subscribe', function (Request $request) {
4 $checkout = $request->user()->subscribe($swimmingMonthly = 'pri_123', 'swimming');
5 
6 return view('billing', ['checkout' => $checkout]);
7});

この例では、顧客のために月額のスイミングサブスクリプションを開始しました。しかし、後で年額サブスクリプションに切り替えたいと思うかもしれません。顧客のサブスクリプションを調整する際には、単にswimmingサブスクリプションの価格を交換するだけです。

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

もちろん、サブスクリプションを完全にキャンセルすることもできます。

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

サブスクリプションの一時停止

サブスクリプションを一時停止するには、ユーザーのサブスクリプションでpauseメソッドを呼び出します。

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

サブスクリプションが一時停止されると、Cashierは自動的にデータベースのpaused_atカラムを設定します。このカラムは、pausedメソッドがtrueを返し始めるタイミングを決定するために使用されます。たとえば、顧客が3月1日にサブスクリプションを一時停止したが、サブスクリプションの更新が3月5日まで予定されていなかった場合、pausedメソッドは3月5日までfalseを返し続けます。これは、ユーザーが通常、請求サイクルの終わりまでアプリケーションを使用し続けることが許可されているためです。

デフォルトでは、一時停止は次の請求間隔で行われるため、顧客は支払い済みの期間の残りの部分を使用できます。サブスクリプションをすぐに一時停止したい場合は、pauseNowメソッドを使用できます。

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

pauseUntilメソッドを使用すると、サブスクリプションを特定の時点まで一時停止できます。

1$user->subscription()->pauseUntil(now()->addMonth());

または、pauseNowUntilメソッドを使用して、サブスクリプションを特定の時点まで即座に一時停止することもできます。

1$user->subscription()->pauseNowUntil(now()->addMonth());

ユーザーがサブスクリプションを一時停止したが、まだ「猶予期間」中であるかどうかを判断するには、onPausedGracePeriodメソッドを使用できます。

1if ($user->subscription()->onPausedGracePeriod()) {
2 // ...
3}

一時停止中のサブスクリプションを再開するには、サブスクリプションでresumeメソッドを呼び出すことができます。

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

サブスクリプションは一時停止中は変更できません。別のプランに切り替えたり、数量を更新したりしたい場合は、まずサブスクリプションを再開する必要があります。

サブスクリプションのキャンセル

サブスクリプションをキャンセルするには、ユーザーのサブスクリプションでcancelメソッドを呼び出します。

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

サブスクリプションがキャンセルされると、Cashierは自動的にデータベースのends_atカラムを設定します。このカラムは、subscribedメソッドがfalseを返し始めるタイミングを決定するために使用されます。たとえば、顧客が3月1日にサブスクリプションをキャンセルしたが、サブスクリプションが3月5日まで終了する予定ではなかった場合、subscribedメソッドは3月5日までtrueを返し続けます。これは、ユーザーが通常、請求サイクルの終わりまでアプリケーションを使用し続けることが許可されているためです。

ユーザーがサブスクリプションをキャンセルしたが、まだ「猶予期間」中であるかどうかを判断するには、onGracePeriodメソッドを使用できます。

1if ($user->subscription()->onGracePeriod()) {
2 // ...
3}

サブスクリプションをすぐにキャンセルしたい場合は、サブスクリプションでcancelNowメソッドを呼び出すことができます。

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

猶予期間中のサブスクリプションのキャンセルを停止するには、stopCancelationメソッドを呼び出すことができます。

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

Paddleのサブスクリプションはキャンセル後に再開できません。顧客がサブスクリプションを再開したい場合は、新しいサブスクリプションを作成する必要があります。

サブスクリプションのトライアル

支払い方法を事前に登録

顧客にトライアル期間を提供しつつ、支払い方法情報を事前に収集したい場合は、Paddleダッシュボードで顧客が購読する価格にトライアル期間を設定する必要があります。その後、通常通りチェックアウトセッションを開始します。

1use Illuminate\Http\Request;
2 
3Route::get('/user/subscribe', function (Request $request) {
4 $checkout = $request->user()
5 ->subscribe('pri_monthly')
6 ->returnTo(route('home'));
7 
8 return view('billing', ['checkout' => $checkout]);
9});

アプリケーションがsubscription_createdイベントを受信すると、Cashierはアプリケーションのデータベース内のサブスクリプションレコードにトライアル期間の終了日を設定し、Paddleに対してこの日以降まで顧客への請求を開始しないよう指示します。

トライアル終了日までに顧客のサブスクリプションがキャンセルされない場合、トライアルが終了するとすぐに請求されますので、ユーザーにトライアル終了日を通知するようにしてください。

ユーザーインスタンスのonTrialメソッドを使用して、ユーザーがトライアル期間中かどうかを判断できます。

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

既存のトライアルが期限切れかどうかを判断するには、hasExpiredTrialメソッドを使用できます。

1if ($user->hasExpiredTrial()) {
2 // ...
3}

特定のサブスクリプションタイプでユーザーがトライアル中かどうかを判断するには、onTrialまたはhasExpiredTrialメソッドにタイプを指定できます。

1if ($user->onTrial('default')) {
2 // ...
3}
4 
5if ($user->hasExpiredTrial('default')) {
6 // ...
7}

支払い方法を事前に登録しない

ユーザーの支払い方法情報を事前に収集せずにトライアル期間を提供したい場合は、ユーザーに関連付けられた顧客レコードのtrial_ends_atカラムに、希望するトライアル終了日を設定できます。これは通常、ユーザー登録時に行われます。

1use App\Models\User;
2 
3$user = User::create([
4 // ...
5]);
6 
7$user->createAsCustomer([
8 'trial_ends_at' => now()->addDays(10)
9]);

Cashierは、この種のトライアルを「ジェネリックトライアル」と呼びます。なぜなら、既存のどのサブスクリプションにも関連付けられていないからです。UserインスタンスのonTrialメソッドは、現在の日付がtrial_ends_atの値を超えていない場合にtrueを返します。

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

ユーザーのために実際のサブスクリプションを作成する準備ができたら、通常通りsubscribeメソッドを使用できます。

1use Illuminate\Http\Request;
2 
3Route::get('/user/subscribe', function (Request $request) {
4 $checkout = $request->user()
5 ->subscribe('pri_monthly')
6 ->returnTo(route('home'));
7 
8 return view('billing', ['checkout' => $checkout]);
9});

ユーザーのトライアル終了日を取得するには、trialEndsAtメソッドを使用できます。このメソッドは、ユーザーがトライアル中の場合はCarbonの日付インスタンスを返し、そうでない場合はnullを返します。デフォルト以外の特定のサブスクリプションのトライアル終了日を取得したい場合は、オプションでサブスクリプションタイプのパラメータを渡すこともできます。

1if ($user->onTrial('default')) {
2 $trialEndsAt = $user->trialEndsAt();
3}

ユーザーが「ジェネリック」トライアル期間中であり、まだ実際のサブスクリプションを作成していないことを具体的に知りたい場合は、onGenericTrialメソッドを使用できます。

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

トライアルの延長または有効化

サブスクリプションの既存のトライアル期間を延長するには、extendTrialメソッドを呼び出し、トライアルが終了すべき時点を指定します。

1$user->subscription()->extendTrial(now()->addDays(5));

または、サブスクリプションのactivateメソッドを呼び出すことで、トライアルを終了させてサブスクリプションを即座に有効化することもできます。

1$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保護から除外する必要があります。

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

Webhookとローカル開発

ローカル開発中にPaddleがアプリケーションにWebhookを送信できるようにするには、NgrokExposeなどのサイト共有サービスを介してアプリケーションを公開する必要があります。Laravel Sailを使用してローカルでアプリケーションを開発している場合は、Sailのサイト共有コマンドを使用できます。

Webhookイベントハンドラの定義

Cashierは、失敗した課金によるサブスクリプションのキャンセルや、その他の一般的なPaddle Webhookを自動的に処理します。しかし、追加で処理したいWebhookイベントがある場合は、Cashierがディスパッチする以下のイベントをリッスンすることで対応できます。

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

両方のイベントには、Paddle Webhookの完全なペイロードが含まれています。例えば、transaction.billed Webhookを処理したい場合、イベントを処理するリスナーを登録できます。

1<?php
2 
3namespace App\Listeners;
4 
5use Laravel\Paddle\Events\WebhookReceived;
6 
7class PaddleEventListener
8{
9 /**
10 * Handle received Paddle webhooks.
11 */
12 public function handle(WebhookReceived $event): void
13 {
14 if ($event->payload['event_type'] === 'transaction.billed') {
15 // Handle the incoming event...
16 }
17 }
18}

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と一致する必要があります。

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

Webhook署名の検証

Webhookを保護するために、PaddleのWebhook署名を使用できます。便宜上、Cashierには受信したPaddle Webhookリクエストが有効であることを検証するミドルウェアが自動的に含まれています。

Webhookの検証を有効にするには、アプリケーションの.envファイルでPADDLE_WEBHOOK_SECRET環境変数が定義されていることを確認してください。Webhookのシークレットは、Paddleアカウントのダッシュボードから取得できます。

単発課金

製品への課金

顧客の製品購入を開始したい場合は、課金対象モデルインスタンスでcheckoutメソッドを使用して購入用のチェックアウトセッションを生成できます。checkoutメソッドは、1つまたは複数の価格IDを受け入れます。必要に応じて、購入する製品の数量を提供するために連想配列を使用できます。

1use Illuminate\Http\Request;
2 
3Route::get('/buy', function (Request $request) {
4 $checkout = $request->user()->checkout(['pri_tshirt', 'pri_socks' => 5]);
5 
6 return view('buy', ['checkout' => $checkout]);
7});

チェックアウトセッションを生成した後、Cashierが提供するpaddle-button Bladeコンポーネントを使用して、ユーザーがPaddleチェックアウトウィジェットを表示し、購入を完了できるようにします。

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

チェックアウトセッションにはcustomDataメソッドがあり、基になるトランザクション作成に任意のカスタムデータを渡すことができます。カスタムデータを渡す際に利用可能なオプションについて詳しく知るには、Paddleのドキュメントを参照してください。

1$checkout = $user->checkout('pri_tshirt')
2 ->customData([
3 'custom_option' => $value,
4 ]);

取引の返金

取引を返金すると、購入時に使用された顧客の支払い方法に返金額が戻されます。Paddleの購入を返金する必要がある場合は、Cashier\Paddle\Transactionモデルでrefundメソッドを使用できます。このメソッドは、最初の引数として理由、返金する1つ以上の価格IDを連想配列としてオプションで金額と共に受け取ります。特定の課金対象モデルの取引は、transactionsメソッドを使用して取得できます。

例えば、価格pri_123pri_456の特定の取引を返金したいとします。pri_123は全額返金し、pri_456は2ドルだけ返金したいとします。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$transaction = $user->transactions()->first();
6 
7$response = $transaction->refund('Accidental charge', [
8 'pri_123', // Fully refund this price...
9 'pri_456' => 200, // Only partially refund this price...
10]);

上記の例では、取引の特定の品目を返金します。取引全体を返金したい場合は、単に理由を提供してください。

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

返金に関する詳細については、Paddleの返金に関するドキュメントを参照してください。

返金は、完全に処理される前に必ずPaddleによる承認が必要です。

取引のクレジット化

返金と同様に、取引をクレジット化することもできます。取引をクレジット化すると、顧客の残高に資金が追加され、将来の購入に使用できます。取引のクレジット化は、手動で収集された取引に対してのみ可能であり、自動で収集された取引(サブスクリプションなど)にはできません。なぜなら、Paddleがサブスクリプションのクレジットを自動的に処理するためです。

1$transaction = $user->transactions()->first();
2 
3// Credit a specific line item fully...
4$response = $transaction->credit('Compensation', 'pri_123');

詳細については、Paddleのクレジット化に関するドキュメントを参照してください

クレジットは手動で収集された取引にのみ適用できます。自動で収集された取引はPaddle自身によってクレジットされます。

取引

transactionsプロパティを介して、課金対象モデルの取引の配列を簡単に取得できます。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$transactions = $user->transactions;

取引は、あなたの製品や購入に対する支払いを表し、請求書が添付されます。完了した取引のみがアプリケーションのデータベースに保存されます。

顧客の取引を一覧表示する際には、取引インスタンスのメソッドを使用して関連する支払い情報を表示できます。たとえば、すべての取引をテーブルに一覧表示し、ユーザーが簡単に請求書をダウンロードできるようにしたい場合があります。

1<table>
2 @foreach ($transactions as $transaction)
3 <tr>
4 <td>{{ $transaction->billed_at->toFormattedDateString() }}</td>
5 <td>{{ $transaction->total() }}</td>
6 <td>{{ $transaction->tax() }}</td>
7 <td><a href="{{ route('download-invoice', $transaction->id) }}" target="_blank">Download</a></td>
8 </tr>
9 @endforeach
10</table>

download-invoiceルートは次のようになります。

1use Illuminate\Http\Request;
2use Laravel\Paddle\Transaction;
3 
4Route::get('/download-invoice/{transaction}', function (Request $request, Transaction $transaction) {
5 return $transaction->redirectToInvoicePdf();
6})->name('download-invoice');

過去と今後の支払い

lastPaymentおよびnextPaymentメソッドを使用して、顧客の過去または今後の定期サブスクリプションの支払いを取得および表示できます。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$subscription = $user->subscription();
6 
7$lastPayment = $subscription->lastPayment();
8$nextPayment = $subscription->nextPayment();

これらのメソッドは両方ともLaravel\Paddle\Paymentのインスタンスを返します。ただし、lastPaymentはWebhookによってトランザクションがまだ同期されていない場合にnullを返し、nextPaymentは請求サイクルが終了した場合(サブスクリプションがキャンセルされた場合など)にnullを返します。

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

テスト

テスト中は、課金フローを手動でテストして、インテグレーションが期待どおりに機能することを確認する必要があります。

CI環境内で実行されるものを含む自動テストについては、LaravelのHTTPクライアントを使用して、PaddleへのHTTPコールを偽装できます。これはPaddleからの実際のレスポンスをテストするものではありませんが、実際にPaddleのAPIを呼び出すことなくアプリケーションをテストする方法を提供します。

Laravelは最も生産的な方法です
ソフトウェアを構築、デプロイ、監視します。