- はじめに
- Cashierのアップグレード
- インストール
- 設定
- クイックスタート
- 顧客
- 支払い方法
- サブスクリプション
- サブスクリプショントライアル
- Stripe Webhookの処理
- 単一課金
- チェックアウト
- 請求書
- 支払い失敗の処理
- 強力な顧客認証(SCA)
- Stripe SDK
- テスト
#はじめに
Laravel Cashier Stripe は、Stripe のサブスクリプション課金サービスに対して表現力豊かで直感的なインターフェースを提供します。面倒なサブスクリプション課金のボイラープレートコードのほとんどを処理します。基本的なサブスクリプション管理に加え、クーポンの適用、サブスクリプションの切り替え、サブスクリプションの「数量」、キャンセル猶予期間、さらには請求書PDFの生成も可能です。
#Cashierのアップグレード
Cashierを新しいバージョンにアップグレードする際は、必ずアップグレードガイドを注意深く確認してください。
破壊的変更を防ぐため、Cashierは固定のStripe APIバージョンを使用しています。Cashier 15はStripe APIバージョン 2023-10-16 を利用します。Stripeの新機能や改善を活用するため、マイナーリリースでStripe APIバージョンが更新されます。
#インストール
まず、Composerパッケージマネージャーを使ってStripe用のCashierパッケージをインストールします:
composer require laravel/cashier
パッケージをインストールしたら、vendor:publish ArtisanコマンドでCashierのマイグレーションを公開します:
php artisan vendor:publish --tag="cashier-migrations"
その後、データベースをマイグレーションしてください:
php artisan migrate
Cashierのマイグレーションは、users テーブルにいくつかのカラムを追加します。また、顧客のサブスクリプションを管理するための新しい subscriptions テーブルと、複数価格のサブスクリプション用の subscription_items テーブルを作成します。
必要に応じて、vendor:publish ArtisanコマンドでCashierの設定ファイルも公開できます:
php artisan vendor:publish --tag="cashier-config"
最後に、CashierがStripeのすべてのイベントを正しく処理できるように、Webhookの設定を忘れずに行ってください。
StripeはStripe識別子を保存するカラムは大文字小文字を区別すべきと推奨しています。MySQLを使用する場合、stripe_id カラムの照合順序を utf8_bin に設定してください。詳細はStripeのドキュメントをご覧ください。
#設定
#Billableモデル
Cashierを使う前に、課金対象のモデル定義に Billable トレイトを追加してください。通常は App\Models\User モデルです。このトレイトは、サブスクリプションの作成、クーポンの適用、支払い方法情報の更新など、一般的な課金タスクを行うためのメソッドを提供します:
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Billable;
}
Cashierは課金対象モデルがLaravel標準の App\Models\User クラスであると想定しています。変更したい場合は、useCustomerModel メソッドで別のモデルを指定できます。このメソッドは通常、AppServiceProvider クラスの boot メソッド内で呼び出します:
use App\Models\Cashier\User;
use Laravel\Cashier\Cashier;
/**
* アプリケーションサービスのブートストラップ処理。
*/
public function boot(): void
{
Cashier::useCustomerModel(User::class);
}
Laravel標準の App\Models\User モデル以外を使う場合は、Cashierのマイグレーションを公開し、代替モデルのテーブル名に合わせて修正する必要があります。
#APIキー
次に、アプリケーションの .env ファイルでStripeのAPIキーを設定してください。StripeコントロールパネルからAPIキーを取得できます:
STRIPE_KEY=your-stripe-key
STRIPE_SECRET=your-stripe-secret
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret
受信WebhookがStripeからのものであることを確認するため、STRIPE_WEBHOOK_SECRET 環境変数を .env ファイルに必ず定義してください。
#通貨設定
Cashierのデフォルト通貨は米ドル(USD)です。アプリケーションの .env ファイルで CASHIER_CURRENCY 環境変数を設定することで、デフォルト通貨を変更できます:
CASHIER_CURRENCY=eur
Cashierの通貨設定に加え、請求書の表示で使用する通貨のロケールも指定できます。内部的にはCashierはPHPの NumberFormatter クラスを使って通貨ロケールを設定します:
CASHIER_CURRENCY_LOCALE=nl_BE
en 以外のロケールを使う場合は、サーバーに ext-intl PHP拡張がインストールされ設定されていることを確認してください。
#税金設定
Stripe Tax により、Stripeで生成されるすべての請求書の税金を自動計算できます。アプリケーションの App\Providers\AppServiceProvider クラスの boot メソッド内で calculateTaxes メソッドを呼び出すことで自動税計算を有効にできます:
use Laravel\Cashier\Cashier;
/**
* アプリケーションサービスのブートストラップ処理。
*/
public function boot(): void
{
Cashier::calculateTaxes();
}
税計算を有効にすると、新規サブスクリプションや単発請求書に対して自動的に税金が計算されます。
この機能を正しく動作させるには、顧客の請求情報(名前、住所、税IDなど)をStripeに同期する必要があります。Cashierが提供する顧客データ同期や税IDのメソッドを利用してください。
単一課金や単一チャージチェックアウトには税金は計算されません。
#ログ記録
Cashierは致命的なStripeエラーをログに記録する際に使用するログチャネルを指定できます。アプリケーションの .env ファイルで CASHIER_LOGGER 環境変数を定義してログチャネルを指定してください:
CASHIER_LOGGER=stack
Stripe API呼び出しで発生した例外は、アプリケーションのデフォルトログチャネルを通じてログに記録されます。
#カスタムモデルの使用
Cashierが内部で使用するモデルは自由に拡張できます。独自のモデルを定義し、対応するCashierモデルを継承してください:
use Laravel\Cashier\Subscription as CashierSubscription;
class Subscription extends CashierSubscription
{
// ...
}
モデルを定義したら、Laravel\Cashier\Cashier クラスを通じてCashierにカスタムモデルを使用させることができます。通常はアプリケーションの App\Providers\AppServiceProvider クラスの boot メソッド内で設定します:
use App\Models\Cashier\Subscription;
use App\Models\Cashier\SubscriptionItem;
/**
* アプリケーションサービスのブートストラップ処理。
*/
public function boot(): void
{
Cashier::useSubscriptionModel(Subscription::class);
Cashier::useSubscriptionItemModel(SubscriptionItem::class);
}
#クイックスタート
#商品販売
Stripe Checkoutを利用する前に、Stripeダッシュボードで固定価格の商品を定義してください。また、CashierのWebhook設定も行ってください。
アプリケーションで商品やサブスクリプション課金を提供するのは難しく感じるかもしれません。しかし、CashierとStripe Checkoutのおかげで、モダンで堅牢な決済連携を簡単に構築できます。
単発の非継続商品に対して顧客に課金するには、Cashierを使って顧客をStripe Checkoutに誘導し、支払い情報を入力して購入を確定してもらいます。Checkoutで支払いが完了すると、顧客はアプリケーション内の指定した成功URLにリダイレクトされます:
use Illuminate\Http\Request;
Route::get('/checkout', function (Request $request) {
$stripePriceId = 'price_deluxe_album';
$quantity = 1;
return $request->user()->checkout([$stripePriceId => $quantity], [
'success_url' => route('checkout-success'),
'cancel_url' => route('checkout-cancel'),
]);
})->name('checkout');
Route::view('checkout.success')->name('checkout-success');
Route::view('checkout.cancel')->name('checkout-cancel');
上記の例のように、Cashierの checkout メソッドを使って、指定した「価格ID」に基づき顧客をStripe Checkoutにリダイレクトします。Stripeにおける「価格」とは、特定の商品に対して定義された価格を指します。
必要に応じて、checkout メソッドは自動的に Stripe に顧客を作成し、その Stripe 顧客レコードをアプリケーションのデータベース内の対応するユーザーに紐付けます。チェックアウトセッションが完了すると、顧客は専用の成功ページまたはキャンセルページにリダイレクトされ、そこで顧客向けの案内メッセージを表示できます。
#Stripe Checkout にメタデータを提供する
商品を販売する際、完了した注文や購入した商品を自分のアプリケーションで定義した Cart や Order モデルで管理することが一般的です。顧客を Stripe Checkout にリダイレクトして購入を完了させる際、既存の注文識別子を渡す必要がある場合があります。これにより、顧客がアプリケーションに戻ったときに購入完了を対応する注文と関連付けられます。
これを実現するために、checkout メソッドに metadata の配列を渡せます。例えば、ユーザーがチェックアウトを開始した時点で保留中の 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',
]);
return $request->user()->checkout($order->price_ids, [
'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => route('checkout-cancel'),
'metadata' => ['order_id' => $order->id],
]);
})->name('checkout');
上記の例のように、ユーザーがチェックアウトを開始するときに、カートや注文に関連付けられた Stripe の価格識別子をすべて checkout メソッドに渡します。もちろん、これらのアイテムを顧客が追加する「ショッピングカート」や注文と関連付けるのはアプリケーションの責任です。また、注文の ID を metadata 配列を通じて Stripe Checkout セッションに渡しています。最後に、Checkout 成功ルートに CHECKOUT_SESSION_ID のテンプレート変数を追加しました。Stripe が顧客をアプリケーションにリダイレクトするとき、この変数は自動的に Checkout セッション ID で置き換えられます。
次に、Checkout 成功ルートを作成しましょう。これは Stripe Checkout で購入が完了した後にユーザーがリダイレクトされるルートです。このルート内で Stripe Checkout セッション ID と関連する Stripe Checkout インスタンスを取得し、提供したメタデータにアクセスして顧客の注文を更新できます。
use App\Models\Order;
use Illuminate\Http\Request;
use Laravel\Cashier\Cashier;
Route::get('/checkout/success', function (Request $request) {
$sessionId = $request->get('session_id');
if ($sessionId === null) {
return;
}
$session = Cashier::stripe()->checkout->sessions->retrieve($sessionId);
if ($session->payment_status !== 'paid') {
return;
}
$orderId = $session['metadata']['order_id'] ?? null;
$order = Order::findOrFail($orderId);
$order->update(['status' => 'completed']);
return view('checkout-success', ['order' => $order]);
})->name('checkout-success');
詳細は Stripe のドキュメントのCheckout セッションオブジェクトに含まれるデータをご参照ください。
#サブスクリプションの販売
Stripe Checkout を利用する前に、Stripe ダッシュボードで固定価格のプロダクトを定義してください。また、Cashier の webhook 処理の設定も行う必要があります。
アプリケーションで商品やサブスクリプションの課金を提供するのは難しく感じるかもしれません。しかし、Cashier と Stripe Checkout を使えば、モダンで堅牢な決済連携を簡単に構築できます。
Cashier と Stripe Checkout を使ったサブスクリプション販売の例として、基本的な月額プラン(price_basic_monthly)と年額プラン(price_basic_yearly)を持つサブスクリプションサービスを考えます。これらの価格は Stripe ダッシュボードの「Basic」プロダクト(pro_basic)にまとめられています。さらに、エキスパートプランとして pro_expert も提供しているかもしれません。
まず、顧客がサービスにサブスクライブする方法を見てみましょう。例えば、アプリケーションの料金ページで顧客が Basic プランの「subscribe」ボタンをクリックすると想定します。このボタンやリンクは、選択したプランの Stripe Checkout セッションを作成する Laravel のルートに誘導します。
use Illuminate\Http\Request;
Route::get('/subscription-checkout', function (Request $request) {
return $request->user()
->newSubscription('default', 'price_basic_monthly')
->trialDays(5)
->allowPromotionCodes()
->checkout([
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});
上記の例のように、顧客は Basic プランにサブスクライブできる Stripe Checkout セッションにリダイレクトされます。チェックアウト成功またはキャンセル後、顧客は checkout メソッドに渡した URL に戻されます。支払い方法によっては処理に数秒かかるため、実際にサブスクリプションが開始されたことを知るには、Cashier の webhook 処理設定も必要です。
顧客がサブスクリプションを開始できるようになったので、サブスクライブしているユーザーだけがアクセスできるようにアプリケーションの一部を制限しましょう。もちろん、Cashier の Billable トレイトが提供する 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
{
/**
* 受信リクエストを処理します。
*/
public function handle(Request $request, Closure $next): Response
{
if (! $request->user()?->subscribed()) {
// ユーザーを課金ページにリダイレクトし、サブスクライブを促します...
return redirect('/billing');
}
return $next($request);
}
}
ミドルウェアを定義したら、ルートに割り当てられます。
use App\Http\Middleware\Subscribed;
Route::get('/dashboard', function () {
// ...
})->middleware([Subscribed::class]);
#顧客が課金プランを管理できるようにする
もちろん、顧客はサブスクリプションプランを別のプロダクトや「ティア」に変更したい場合があります。最も簡単な方法は、Stripe の Customer Billing Portal に誘導することです。ここでは請求書のダウンロード、支払い方法の更新、サブスクリプションプランの変更が可能なホスト型ユーザーインターフェースを提供しています。
まず、アプリケーション内にユーザーを Laravel のルートに誘導するリンクやボタンを用意します。このルートで Billing Portal セッションを開始します。
<a href="{{ route('billing') }}">
Billing
</a>
次に、Stripe Customer Billing Portal セッションを開始し、ユーザーをポータルにリダイレクトするルートを定義しましょう。redirectToBillingPortal メソッドは、ポータルを退出した際にユーザーが戻る URL を受け取ります。
use Illuminate\Http\Request;
Route::get('/billing', function (Request $request) {
return $request->user()->redirectToBillingPortal(route('dashboard'));
})->middleware(['auth'])->name('billing');
Cashier の webhook 処理を設定していれば、Cashier は Stripe からの webhook を監視して自動的に Cashier 関連のデータベーステーブルを同期します。例えば、ユーザーが Stripe の Customer Billing Portal でサブスクリプションをキャンセルすると、対応する webhook を受け取り、アプリケーションのデータベースでサブスクリプションを「キャンセル済み」とマークします。
#顧客
#顧客の取得
Cashier::findBillable メソッドを使うと、Stripe ID から顧客を取得できます。このメソッドは課金可能モデルのインスタンスを返します。
use Laravel\Cashier\Cashier;
$user = Cashier::findBillable($stripeId);
#顧客の作成
サブスクリプションを開始せずに Stripe 顧客を作成したい場合があります。createAsStripeCustomer メソッドで実現できます。
$stripeCustomer = $user->createAsStripeCustomer();
Stripe に顧客が作成された後、後でサブスクリプションを開始できます。オプションの $options 配列を渡して、Stripe API がサポートする追加の顧客作成パラメータを指定できます。
$stripeCustomer = $user->createAsStripeCustomer($options);
課金可能モデルの Stripe 顧客オブジェクトを取得したい場合は、asStripeCustomer メソッドを使えます。
$stripeCustomer = $user->asStripeCustomer();
createOrGetStripeCustomer メソッドは、課金可能モデルがすでに Stripe 顧客かどうかわからない場合に使います。存在しなければ新規に Stripe 顧客を作成します。
$stripeCustomer = $user->createOrGetStripeCustomer();
#顧客の更新
Stripe 顧客情報を追加で更新したい場合があります。updateStripeCustomer メソッドで実現できます。このメソッドは Stripe API がサポートする顧客更新オプションの配列を受け取ります。
$stripeCustomer = $user->updateStripeCustomer($options);
#残高
Stripe では顧客の「残高」をクレジットまたはデビットできます。この残高は後で新しい請求書に反映されます。顧客の合計残高を確認するには、課金可能モデルの balance メソッドを使います。balance メソッドは顧客の通貨でフォーマットされた文字列を返します。
$balance = $user->balance();
顧客の残高にクレジットを加えるには、creditBalance メソッドに値を渡します。必要に応じて説明も指定できます。
$user->creditBalance(500, 'Premium customer top-up.');
debitBalance メソッドに値を渡すと、顧客の残高がデビットされます。
$user->debitBalance(300, 'Bad usage penalty.');
applyBalance メソッドは顧客の残高取引を新規作成します。balanceTransactions メソッドでこれらの取引記録を取得でき、顧客にクレジットやデビットの履歴を提供するのに役立ちます。
// すべての取引を取得...
$transactions = $user->balanceTransactions();
foreach ($transactions as $transaction) {
// 取引金額...
$amount = $transaction->amount(); // $2.31
// 利用可能な場合は関連請求書を取得...
$invoice = $transaction->invoice();
}
#税務ID
Cashier は顧客の税務IDを簡単に管理できます。例えば、taxIds メソッドは顧客に割り当てられたすべての税務IDをコレクションとして取得します。
$taxIds = $user->taxIds();
特定の識別子を使って顧客の税IDを取得することもできます:
$taxId = $user->findTaxId('txi_belgium');
有効なtypeと値を指定して、createTaxIdメソッドで新しい税IDを作成できます:
$taxId = $user->createTaxId('eu_vat', 'BE0123456789');
createTaxIdメソッドは、VAT IDを即座に顧客アカウントに追加します。VAT IDの検証もStripeによって行われますが、これは非同期処理です。customer.tax_id.updatedのWebhookイベントを購読し、VAT IDのverificationパラメータを確認することで検証の更新を通知できます。Webhookの処理については、Webhookハンドラーの定義に関するドキュメントをご覧ください。
deleteTaxIdメソッドを使って税IDを削除できます:
$user->deleteTaxId('txi_belgium');
#Stripeとの顧客データ同期
通常、アプリケーションのユーザーが名前やメールアドレスなどStripeにも保存されている情報を更新した場合、Stripeにもその更新を通知すべきです。そうすることで、Stripeの情報とアプリケーションの情報が同期されます。
これを自動化するために、課金可能モデルのupdatedイベントに反応するイベントリスナーを定義できます。イベントリスナー内でモデルのsyncStripeCustomerDetailsメソッドを呼び出します:
use App\Models\User;
use function Illuminate\Events\queueable;
/**
* モデルの "booted" メソッド。
*/
protected static function booted(): void
{
static::updated(queueable(function (User $customer) {
if ($customer->hasStripeId()) {
$customer->syncStripeCustomerDetails();
}
}));
}
これで顧客モデルが更新されるたびに、その情報がStripeと同期されます。便利なことに、Cashierは顧客作成時にも自動的にStripeと情報を同期します。
Cashierが提供する様々なメソッドをオーバーライドして、Stripeに同期する顧客情報のカラムをカスタマイズできます。例えば、stripeNameメソッドをオーバーライドして、Cashierが顧客情報をStripeに同期する際に「名前」とみなす属性をカスタマイズできます:
/**
* Stripeに同期する顧客名を取得する。
*/
public function stripeName(): string|null
{
return $this->company_name;
}
同様に、stripeEmail、stripePhone、stripeAddress、stripePreferredLocalesメソッドもオーバーライドできます。これらのメソッドはStripeの顧客オブジェクト更新時に対応するパラメータへ情報を同期します。顧客情報の同期処理を完全に制御したい場合は、syncStripeCustomerDetailsメソッドをオーバーライドしてください。
#請求ポータル
Stripeは請求ポータルの簡単なセットアップ方法を提供しており、顧客がサブスクリプションや支払い方法の管理、請求履歴の閲覧を行えます。コントローラーやルートから課金可能モデルのredirectToBillingPortalメソッドを呼び出して、ユーザーを請求ポータルにリダイレクトできます:
use Illuminate\Http\Request;
Route::get('/billing-portal', function (Request $request) {
return $request->user()->redirectToBillingPortal();
});
デフォルトでは、ユーザーがサブスクリプション管理を終えた後、Stripe請求ポータル内のリンクからアプリケーションのhomeルートに戻れます。戻る先のURLをカスタマイズしたい場合は、redirectToBillingPortalメソッドにURLを渡してください:
use Illuminate\Http\Request;
Route::get('/billing-portal', function (Request $request) {
return $request->user()->redirectToBillingPortal(route('billing'));
});
HTTPリダイレクトレスポンスを生成せずに請求ポータルのURLだけを取得したい場合は、billingPortalUrlメソッドを呼び出せます:
$url = $request->user()->billingPortalUrl(route('billing'));
#支払い方法
#支払い方法の保存
サブスクリプションの作成や「ワンオフ」チャージをStripeで行うには、支払い方法を保存し、その識別子をStripeから取得する必要があります。支払い方法をサブスクリプション用に使うか単発チャージ用に使うかで方法が異なるため、以下で両方を説明します。
#サブスクリプション用の支払い方法
サブスクリプションで将来使うために顧客のクレジットカード情報を保存する場合、Stripeの「Setup Intents」APIを使って安全に支払い方法の詳細を取得する必要があります。「Setup Intent」はStripeに対して顧客の支払い方法をチャージする意図を示します。CashierのBillableトレイトには新しいSetup Intentを簡単に作成できるcreateSetupIntentメソッドが含まれています。このメソッドは支払い方法の詳細を取得するフォームを表示するルートやコントローラーから呼び出してください:
return view('update-payment-method', [
'intent' => $user->createSetupIntent()
]);
Setup Intentを作成してビューに渡したら、そのシークレットを支払い方法を取得する要素に紐付けます。例えば、以下の「支払い方法更新」フォームを考えてみてください:
<input id="card-holder-name" type="text">
<!-- Stripe Elements のプレースホルダー -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ $intent->client_secret }}">
支払い方法を更新
</button>
次に、Stripe.jsライブラリを使ってStripe Elementをフォームに紐付け、顧客の支払い情報を安全に取得します:
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('stripe-public-key');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
</script>
次に、カードを検証し、Stripe の confirmCardSetup メソッドを使用して Stripe から安全な「支払い方法識別子」を取得できます:
const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
const clientSecret = cardButton.dataset.secret;
cardButton.addEventListener('click', async (e) => {
const { setupIntent, error } = await stripe.confirmCardSetup(
clientSecret, {
payment_method: {
card: cardElement,
billing_details: { name: cardHolderName.value }
}
}
);
if (error) {
// ユーザーに "error.message" を表示...
} else {
// カードの検証に成功...
}
});
カードがStripeで検証された後、得られたsetupIntent.payment_method識別子をLaravelアプリケーションに渡し、顧客に紐付けられます。支払い方法は新しい支払い方法として追加するか、デフォルトの支払い方法を更新するために使えます。また、支払い方法識別子を使って新しいサブスクリプションを即座に作成することも可能です。
Setup Intentsや顧客の支払い情報の取得について詳しく知りたい場合は、Stripeが提供する概要をご覧ください。
#単発チャージ用の支払い方法
もちろん、単発チャージの場合は支払い方法識別子を一度だけ使います。Stripeの制限により、顧客の保存されたデフォルト支払い方法は単発チャージに使えません。Stripe.jsライブラリを使って顧客に支払い情報を入力してもらう必要があります。例えば、以下のフォームを考えてみてください:
<input id="card-holder-name" type="text">
<!-- Stripe Elements のプレースホルダー -->
<div id="card-element"></div>
<button id="card-button">
支払いを処理する
</button>
このようなフォームを定義した後、Stripe.jsライブラリを使ってStripe Elementをフォームに紐付け、顧客の支払い情報を安全に取得します:
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('stripe-public-key');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
</script>
次に、カードを検証し、Stripe の createPaymentMethod メソッド を使用して安全な「支払い方法識別子」を取得できます:
const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
cardButton.addEventListener('click', async (e) => {
const { paymentMethod, error } = await stripe.createPaymentMethod(
'card', cardElement, {
billing_details: { name: cardHolderName.value }
}
);
if (error) {
// ユーザーに "error.message" を表示...
} else {
// カードの検証に成功...
}
});
カードが正常に検証されたら、paymentMethod.idをLaravelアプリケーションに渡して単発チャージを処理できます。
#支払い方法の取得
課金可能モデルインスタンスのpaymentMethodsメソッドは、Laravel\Cashier\PaymentMethodインスタンスのコレクションを返します:
$paymentMethods = $user->paymentMethods();
デフォルトでは、このメソッドはすべてのタイプの支払い方法を返します。特定のタイプの支払い方法だけを取得したい場合は、typeを引数に渡せます:
$paymentMethods = $user->paymentMethods('sepa_debit');
顧客のデフォルト支払い方法を取得するには、defaultPaymentMethodメソッドを使います:
$paymentMethod = $user->defaultPaymentMethod();
課金可能モデルに紐付く特定の支払い方法を取得するには、findPaymentMethodメソッドを使います:
$paymentMethod = $user->findPaymentMethod($paymentMethodId);
#支払い方法の有無
課金可能モデルにデフォルト支払い方法が紐付いているか確認するには、hasDefaultPaymentMethodメソッドを呼び出します:
if ($user->hasDefaultPaymentMethod()) {
// ...
}
課金可能モデルに少なくとも1つの支払い方法が紐付いているか確認するには、hasPaymentMethodメソッドを使います:
if ($user->hasPaymentMethod()) {
// ...
}
このメソッドは課金可能モデルに支払い方法が存在するかを判定します。特定のタイプの支払い方法が存在するか確認したい場合は、typeを引数に渡せます:
if ($user->hasPaymentMethod('sepa_debit')) {
// ...
}
#デフォルト支払い方法の更新
updateDefaultPaymentMethodメソッドを使って顧客のデフォルト支払い方法情報を更新できます。このメソッドはStripeの支払い方法識別子を受け取り、新しい支払い方法をデフォルトの請求支払い方法として割り当てます:
$user->updateDefaultPaymentMethod($paymentMethod);
デフォルト支払い方法情報をStripeの顧客デフォルト支払い方法情報と同期したい場合は、updateDefaultPaymentMethodFromStripeメソッドを使います:
$user->updateDefaultPaymentMethodFromStripe();
顧客のデフォルト支払い方法は請求書発行や新規サブスクリプション作成にのみ使用できます。Stripeの制限により、単発チャージには使用できません。
#支払い方法の追加
新しい支払い方法を追加するには、課金可能モデルのaddPaymentMethodメソッドを呼び出し、支払い方法識別子を渡します:
$user->addPaymentMethod($paymentMethod);
支払い方法識別子の取得方法については、支払い方法の保存に関するドキュメントをご覧ください。
#支払い方法の削除
支払い方法を削除するには、削除したいLaravel\Cashier\PaymentMethodインスタンスのdeleteメソッドを呼び出します:
$paymentMethod->delete();
deletePaymentMethod メソッドは、請求可能モデルから特定の支払い方法を削除します。
$user->deletePaymentMethod('pm_visa');
deletePaymentMethods メソッドは、請求可能モデルのすべての支払い方法情報を削除します。
$user->deletePaymentMethods();
デフォルトでは、このメソッドはすべてのタイプの支払い方法を削除します。特定のタイプの支払い方法のみを削除したい場合は、type を引数として渡せます。
$user->deletePaymentMethods('sepa_debit');
ユーザーにアクティブなサブスクリプションがある場合、アプリケーションはデフォルトの支払い方法の削除を許可してはいけません。
#サブスクリプション
サブスクリプションは、顧客の定期支払いを設定する方法を提供します。Cashierで管理されるStripeサブスクリプションは、複数のサブスクリプション価格、数量、トライアルなどに対応しています。
#サブスクリプションの作成
サブスクリプションを作成するには、まず請求可能モデルのインスタンス(通常は App\Models\User のインスタンス)を取得します。モデルインスタンスを取得したら、newSubscription メソッドを使ってサブスクリプションを作成できます。
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription(
'default', 'price_monthly'
)->create($request->paymentMethodId);
// ...
});
newSubscription メソッドの最初の引数はサブスクリプションの内部タイプです。アプリケーションで単一のサブスクリプションのみ提供する場合は、default や primary と呼ぶことが多いです。このタイプはアプリケーション内部用で、ユーザーに表示するものではありません。また、スペースを含まず、サブスクリプション作成後に変更してはいけません。2番目の引数はユーザーが購読する特定の価格で、Stripeの価格IDに対応します。
create メソッドは、Stripeの支払い方法識別子 または Stripe の PaymentMethod オブジェクトを受け取り、サブスクリプションを開始し、請求可能モデルのStripe顧客IDや関連する請求情報をデータベースに更新します。
create メソッドに支払い方法識別子を直接渡すと、その支払い方法は自動的にユーザーの保存済み支払い方法にも追加されます。
#請求書メールによる定期支払いの回収
顧客の定期支払いを自動で回収する代わりに、Stripeに請求書をメール送信させ、顧客が請求書を受け取ってから手動で支払うように指示できます。請求書で定期支払いを回収する場合、顧客は事前に支払い方法を提供する必要はありません。
$user->newSubscription('default', 'price_monthly')->createAndSendInvoice();
サブスクリプションがキャンセルされるまでに顧客が請求書を支払う猶予期間は、days_until_due オプションで決まります。デフォルトは30日ですが、必要に応じて特定の値を指定できます。
$user->newSubscription('default', 'price_monthly')->createAndSendInvoice([], [
'days_until_due' => 30
]);
#数量
サブスクリプション作成時に特定の数量を設定したい場合は、サブスクリプションビルダーの quantity メソッドを呼び出してから作成してください。
$user->newSubscription('default', 'price_monthly')
->quantity(5)
->create($paymentMethod);
#追加の詳細
Stripeがサポートする追加の顧客やサブスクリプションオプションを指定したい場合は、create メソッドの第2、第3引数として渡せます。
$user->newSubscription('default', 'price_monthly')->create($paymentMethod, [
'email' => $email,
], [
'metadata' => ['note' => 'Some extra information.'],
]);
#クーポン
サブスクリプション作成時にクーポンを適用したい場合は、withCoupon メソッドを使います。
$user->newSubscription('default', 'price_monthly')
->withCoupon('code')
->create($paymentMethod);
または、Stripeのプロモーションコードを適用したい場合は、withPromotionCode メソッドを使います。
$user->newSubscription('default', 'price_monthly')
->withPromotionCode('promo_code_id')
->create($paymentMethod);
指定するプロモーションコードIDは、顧客向けのコードではなくStripe APIが割り当てたIDである必要があります。顧客向けコードからプロモーションコードIDを探す場合は、findPromotionCode メソッドを使えます。
// 顧客向けコードからプロモーションコードIDを検索...
$promotionCode = $user->findPromotionCode('SUMMERSALE');
// 有効なプロモーションコードIDを顧客向けコードから検索...
$promotionCode = $user->findActivePromotionCode('SUMMERSALE');
上記の例で返される $promotionCode オブジェクトは Laravel\Cashier\PromotionCode のインスタンスです。このクラスは内部の Stripe\PromotionCode オブジェクトをラップしています。coupon メソッドを呼び出すことで、プロモーションコードに関連するクーポンを取得できます。
$coupon = $user->findPromotionCode('SUMMERSALE')->coupon();
クーポンインスタンスを使うと、割引額やクーポンが固定割引かパーセンテージ割引かを判別できます。
if ($coupon->isPercentage()) {
return $coupon->percentOff().'%'; // 21.5%
} else {
return $coupon->amountOff(); // $5.99
}
現在顧客やサブスクリプションに適用されている割引も取得できます。
$discount = $billable->discount();
$discount = $subscription->discount();
返される Laravel\Cashier\Discount インスタンスは内部の Stripe\Discount オブジェクトをラップしています。coupon メソッドでこの割引に関連するクーポンを取得できます。
$coupon = $subscription->discount()->coupon();
顧客やサブスクリプションに新しいクーポンやプロモーションコードを適用したい場合は、applyCoupon または applyPromotionCode メソッドを使います。
$billable->applyCoupon('coupon_id');
$billable->applyPromotionCode('promotion_code_id');
$subscription->applyCoupon('coupon_id');
$subscription->applyPromotionCode('promotion_code_id');
プロモーションコードは顧客向けコードではなくStripe APIが割り当てたIDを使うことを忘れないでください。顧客やサブスクリプションに同時に適用できるクーポンやプロモーションコードは1つだけです。
詳細はStripeのクーポンおよびプロモーションコードのドキュメントをご参照ください。
#サブスクリプションの追加
すでにデフォルトの支払い方法を持つ顧客にサブスクリプションを追加したい場合は、サブスクリプションビルダーの add メソッドを呼び出します。
use App\Models\User;
$user = User::find(1);
$user->newSubscription('default', 'price_monthly')->add();
#Stripeダッシュボードからのサブスクリプション作成
Stripeダッシュボードからもサブスクリプションを作成できます。その場合、Cashierは新規追加されたサブスクリプションを同期し、タイプを default に割り当てます。ダッシュボードで作成されたサブスクリプションのタイプをカスタマイズするには、Webhookイベントハンドラーを定義してください。
また、Stripeダッシュボードから作成できるサブスクリプションのタイプは1種類のみです。複数のタイプを使う場合は、ダッシュボードから追加できるのは1種類だけです。
最後に、アプリケーションで提供する各サブスクリプションタイプにつき、アクティブなサブスクリプションは1つだけにしてください。顧客が2つの default サブスクリプションを持っている場合でも、Cashierは最新のものだけを使用し、古いものは履歴としてデータベースに保持されます。
#サブスクリプションの状態確認
顧客がサブスクリプションに加入しているかどうかは、便利なメソッドで簡単に確認できます。まず、subscribed メソッドは、トライアル期間中でもアクティブなサブスクリプションがあれば true を返します。subscribed メソッドはサブスクリプションのタイプを第1引数に取ります。
if ($user->subscribed('default')) {
// ...
}
subscribed メソッドはルートミドルウェアとしても使え、ユーザーのサブスクリプション状態に基づいてルートやコントローラーへのアクセスを制御できます。
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserIsSubscribed
{
/**
* 受信リクエストを処理します。
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->user() && ! $request->user()->subscribed('default')) {
// このユーザーは支払い中の顧客ではありません...
return redirect('billing');
}
return $next($request);
}
}
ユーザーがまだトライアル期間中かどうかを判定したい場合は、onTrial メソッドを使えます。このメソッドは、トライアル期間中であることをユーザーに警告するかどうかの判定に便利です。
if ($user->subscription('default')->onTrial()) {
// ...
}
subscribedToProduct メソッドは、指定したStripe製品IDに基づいてユーザーがその製品にサブスクライブしているかを判定します。Stripeでは製品は価格の集合です。この例では、ユーザーの default サブスクリプションがアプリケーションの「premium」製品にアクティブに加入しているかを判定します。指定するStripe製品IDはStripeダッシュボードの製品IDに対応します。
if ($user->subscribedToProduct('prod_premium', 'default')) {
// ...
}
配列を subscribedToProduct メソッドに渡すと、ユーザーの default サブスクリプションが「basic」または「premium」製品にアクティブに加入しているかを判定できます。
if ($user->subscribedToProduct(['prod_basic', 'prod_premium'], 'default')) {
// ...
}
subscribedToPrice メソッドは、顧客のサブスクリプションが指定した価格IDに対応しているかを判定します。
if ($user->subscribedToPrice('price_basic_monthly', 'default')) {
// ...
}
recurring メソッドは、ユーザーが現在サブスクリプション中でトライアル期間を過ぎているかを判定します。
if ($user->subscription('default')->recurring()) {
// ...
}
同じタイプのサブスクリプションをユーザーが2つ持っている場合、subscription メソッドは常に最新のサブスクリプションを返します。例えば、ユーザーが2つの default タイプのサブスクリプションを持っていても、1つは古い期限切れのもので、もう1つは現在のアクティブなものです。最新のものだけが返され、古いものは履歴としてデータベースに保持されます。
#キャンセルされたサブスクリプションの状態
ユーザーがかつてアクティブなサブスクリプションを持っていたがキャンセルしたかどうかは、canceled メソッドで判定できます。
if ($user->subscription('default')->canceled()) {
// ...
}
ユーザーがサブスクリプションをキャンセルしたが、サブスクリプションが完全に終了するまでの「猶予期間(grace period)」にあるかどうかも判別できます。例えば、3月5日にキャンセルしたサブスクリプションが元々3月10日に終了予定の場合、ユーザーは3月10日まで「猶予期間」にあります。この期間中は subscribed メソッドは引き続き true を返すことに注意してください。
if ($user->subscription('default')->onGracePeriod()) {
// ...
}
ユーザーがサブスクリプションをキャンセルし、「猶予期間」も終了しているかどうかを判別するには、ended メソッドを使用します。
if ($user->subscription('default')->ended()) {
// ...
}
#未完了および支払い遅延のステータス
サブスクリプション作成後に追加の支払いアクションが必要な場合、そのサブスクリプションは incomplete(未完了)としてマークされます。サブスクリプションのステータスは Cashier の subscriptions テーブルの stripe_status カラムに保存されます。
同様に、価格を切り替える際に追加の支払いアクションが必要な場合、サブスクリプションは past_due(支払い遅延)としてマークされます。これらの状態のいずれかにあるサブスクリプションは、顧客が支払いを確認するまでアクティブになりません。未完了の支払いがあるかどうかは、課金可能モデルまたはサブスクリプションインスタンスの hasIncompletePayment メソッドで判別できます。
if ($user->hasIncompletePayment('default')) {
// ...
}
if ($user->subscription('default')->hasIncompletePayment()) {
// ...
}
サブスクリプションに未完了の支払いがある場合は、latestPayment 識別子を渡して Cashier の支払い確認ページにユーザーを誘導してください。サブスクリプションインスタンスの latestPayment メソッドでこの識別子を取得できます。
<a href="{{ route('cashier.payment', $subscription->latestPayment()->id) }}">
Please confirm your payment.
</a>
サブスクリプションが past_due または incomplete 状態でもアクティブと見なしたい場合は、Cashier が提供する keepPastDueSubscriptionsActive と keepIncompleteSubscriptionsActive メソッドを使用できます。通常、これらは App\Providers\AppServiceProvider の register メソッド内で呼び出します。
use Laravel\Cashier\Cashier;
/**
* アプリケーションサービスを登録します。
*/
public function register(): void
{
Cashier::keepPastDueSubscriptionsActive();
Cashier::keepIncompleteSubscriptionsActive();
}
サブスクリプションが incomplete 状態の場合、支払いが確認されるまで変更できません。そのため、swap と updateQuantity メソッドは incomplete 状態のとき例外をスローします。
#サブスクリプションスコープ
ほとんどのサブスクリプション状態はクエリスコープとして利用できるため、特定の状態にあるサブスクリプションを簡単にデータベースから取得できます。
// すべてのアクティブなサブスクリプションを取得...
$subscriptions = Subscription::query()->active()->get();
// ユーザーのキャンセル済みサブスクリプションをすべて取得...
$subscriptions = $user->subscriptions()->canceled()->get();
利用可能なスコープの完全な一覧は以下の通りです。
Subscription::query()->active();
Subscription::query()->canceled();
Subscription::query()->ended();
Subscription::query()->incomplete();
Subscription::query()->notCanceled();
Subscription::query()->notOnGracePeriod();
Subscription::query()->notOnTrial();
Subscription::query()->onGracePeriod();
Subscription::query()->onTrial();
Subscription::query()->pastDue();
Subscription::query()->recurring();
#価格の変更
顧客がアプリケーションにサブスクライブした後、時折新しいサブスクリプション価格に変更したい場合があります。価格を切り替えるには、Stripeの価格識別子を swap メソッドに渡します。価格を切り替える際、ユーザーが以前キャンセルしていた場合はサブスクリプションを再アクティブ化したいと想定されます。渡す価格識別子は Stripe ダッシュボードで利用可能な価格識別子である必要があります。
use App\Models\User;
$user = App\Models\User::find(1);
$user->subscription('default')->swap('price_yearly');
顧客がトライアル中の場合、トライアル期間は維持されます。また、サブスクリプションに「数量」が設定されている場合、その数量も維持されます。
トライアル期間をキャンセルして価格を切り替えたい場合は、skipTrial メソッドを呼び出せます。
$user->subscription('default')
->skipTrial()
->swap('price_yearly');
価格を切り替え、次の請求サイクルを待たずに即座に請求したい場合は、swapAndInvoice メソッドを使用できます。
$user = User::find(1);
$user->subscription('default')->swapAndInvoice('price_yearly');
#プロレーション(按分計算)
デフォルトでは、Stripe は価格を切り替える際に料金を按分計算します。noProrate メソッドを使うと、料金を按分せずにサブスクリプションの価格を更新できます。
$user->subscription('default')->noProrate()->swap('price_yearly');
サブスクリプションのプロレーションについて詳しくは、Stripeドキュメントを参照してください。
swapAndInvoice メソッドの前に noProrate メソッドを実行してもプロレーションには影響しません。請求書は必ず発行されます。
#サブスクリプションの数量
サブスクリプションは「数量」によって影響を受けることがあります。例えば、プロジェクト管理アプリケーションでプロジェクトごとに月額10ドルを請求する場合などです。incrementQuantity と decrementQuantity メソッドを使うと、サブスクリプションの数量を簡単に増減できます。
use App\Models\User;
$user = User::find(1);
$user->subscription('default')->incrementQuantity();
// サブスクリプションの現在の数量に5を加える...
$user->subscription('default')->incrementQuantity(5);
$user->subscription('default')->decrementQuantity();
// サブスクリプションの現在の数量から5を減らす...
$user->subscription('default')->decrementQuantity(5);
または、updateQuantity メソッドで特定の数量を設定できます。
$user->subscription('default')->updateQuantity(10);
noProrate メソッドを使うと、料金を按分せずに数量を更新できます。
$user->subscription('default')->noProrate()->updateQuantity(10);
サブスクリプションの数量について詳しくは、Stripeドキュメントを参照してください。
#複数製品のサブスクリプションにおける数量
サブスクリプションが 複数製品を持つサブスクリプション の場合、数量を増減したい price の ID を increment / decrement メソッドの第2引数として渡す必要があります:
$user->subscription('default')->incrementQuantity(1, 'price_chat');
#複数製品のサブスクリプション
複数製品のサブスクリプションでは、1つのサブスクリプションに複数の課金製品を割り当てられます。例えば、月額10ドルの基本サブスクリプションに加え、月額15ドルのライブチャットアドオンを提供するカスタマーサービスのヘルプデスクアプリケーションを想像してください。複数製品のサブスクリプション情報は Cashier の subscription_items テーブルに保存されます。
newSubscription メソッドの第2引数に価格の配列を渡すことで、複数製品を指定できます。
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription('default', [
'price_monthly',
'price_chat',
])->create($request->paymentMethodId);
// ...
});
上記の例では、顧客の default サブスクリプションに2つの価格が紐づきます。両方の価格はそれぞれの請求間隔で課金されます。必要に応じて、quantity メソッドで各価格の数量を指定できます。
$user = User::find(1);
$user->newSubscription('default', ['price_monthly', 'price_chat'])
->quantity(5, 'price_chat')
->create($paymentMethod);
既存のサブスクリプションに別の価格を追加したい場合は、サブスクリプションの addPrice メソッドを呼び出せます。
$user = User::find(1);
$user->subscription('default')->addPrice('price_chat');
上記の例では新しい価格が追加され、次の請求サイクルで顧客に課金されます。即座に請求したい場合は、addPriceAndInvoice メソッドを使えます。
$user->subscription('default')->addPriceAndInvoice('price_chat');
特定の数量で価格を追加したい場合は、addPrice または addPriceAndInvoice メソッドの第2引数に数量を渡せます。
$user = User::find(1);
$user->subscription('default')->addPrice('price_chat', 5);
サブスクリプションから価格を削除するには、removePrice メソッドを使います。
$user->subscription('default')->removePrice('price_chat');
サブスクリプションの最後の価格は削除できません。その場合はサブスクリプションをキャンセルしてください。
#価格の切り替え
複数製品のサブスクリプションで価格を変更することもできます。例えば、顧客が price_basic サブスクリプションに price_chat アドオンを持っていて、price_basic から price_pro にアップグレードしたい場合を想定してください。
use App\Models\User;
$user = User::find(1);
$user->subscription('default')->swap(['price_pro', 'price_chat']);
上記の例を実行すると、price_basic に対応するサブスクリプションアイテムは削除され、price_chat は保持されます。さらに、price_pro の新しいサブスクリプションアイテムが作成されます。
swap メソッドにキーと値の配列を渡すことで、サブスクリプションアイテムのオプションを指定できます。例えば、サブスクリプション価格の数量を指定する場合です。
$user = User::find(1);
$user->subscription('default')->swap([
'price_pro' => ['quantity' => 5],
'price_chat'
]);
サブスクリプションの単一価格を切り替えたい場合は、サブスクリプションアイテムの swap メソッドを使えます。この方法は、他の価格のメタデータを保持したい場合に便利です。
$user = User::find(1);
$user->subscription('default')
->findItemOrFail('price_basic')
->swap('price_pro');
#プロレーション
デフォルトでは、複数製品のサブスクリプションで価格を追加・削除するときに Stripe は料金を按分計算します。按分計算なしで価格調整したい場合は、価格操作に noProrate メソッドをチェーンしてください。
$user->subscription('default')->noProrate()->removePrice('price_chat');
#数量
個別のサブスクリプション価格の数量を更新したい場合は、既存の数量メソッドに価格IDを追加引数として渡して操作できます。
$user = User::find(1);
$user->subscription('default')->incrementQuantity(5, 'price_chat');
$user->subscription('default')->decrementQuantity(3, 'price_chat');
$user->subscription('default')->updateQuantity(10, 'price_chat');
複数価格のサブスクリプションでは、Subscription モデルの stripe_price と quantity 属性は null になります。個別の価格属性にアクセスするには、Subscription モデルの items リレーションを使ってください。
#サブスクリプションアイテム
複数価格のサブスクリプションは、データベースの subscription_items テーブルに複数のサブスクリプション「アイテム」として保存されます。サブスクリプションの items リレーションからアクセスできます。
use App\Models\User;
$user = User::find(1);
$subscriptionItem = $user->subscription('default')->items->first();
// 特定のアイテムの Stripe 価格と数量を取得します...
$stripePrice = $subscriptionItem->stripe_price;
$quantity = $subscriptionItem->quantity;
findItemOrFail メソッドを使って特定の価格を取得することもできます:
$user = User::find(1);
$subscriptionItem = $user->subscription('default')->findItemOrFail('price_chat');
#複数のサブスクリプション
Stripe では、顧客が複数のサブスクリプションを同時に持つことができます。例えば、ジムを運営していて、水泳のサブスクリプションとウェイトリフティングのサブスクリプションを提供し、それぞれ異なる料金設定がある場合です。もちろん、顧客はどちらか一方、または両方のプランに加入できます。
アプリケーションでサブスクリプションを作成する際、newSubscription メソッドにサブスクリプションの種類を指定できます。この種類は、ユーザーが開始するサブスクリプションのタイプを表す任意の文字列です:
use Illuminate\Http\Request;
Route::post('/swimming/subscribe', function (Request $request) {
$request->user()->newSubscription('swimming')
->price('price_swimming_monthly')
->create($request->paymentMethodId);
// ...
});
この例では、顧客に月額の水泳サブスクリプションを開始しました。しかし、後で年間サブスクリプションに切り替えたい場合もあります。顧客のサブスクリプションを調整する際は、swimming サブスクリプションの価格を単に切り替えればよいです:
$user->subscription('swimming')->swap('price_swimming_yearly');
もちろん、サブスクリプションを完全にキャンセルすることもできます:
$user->subscription('swimming')->cancel();
#従量課金
従量課金 は、請求期間中の製品利用量に基づいて顧客に課金する方法です。例えば、月ごとに送信したテキストメッセージやメールの数に応じて課金できます。
従量課金を利用開始するには、まず Stripe ダッシュボードで従量課金用の価格を持つ新しい製品を作成します。その後、meteredPrice を使って顧客のサブスクリプションに従量課金価格IDを追加します:
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription('default')
->meteredPrice('price_metered')
->create($request->paymentMethodId);
// ...
});
Stripe Checkout を使って従量課金サブスクリプションを開始することもできます:
$checkout = Auth::user()
->newSubscription('default', [])
->meteredPrice('price_metered')
->checkout();
return view('your-checkout-view', [
'checkout' => $checkout,
]);
#利用量の報告
顧客がアプリケーションを利用するにつれて、正確に課金できるよう Stripe に利用量を報告します。従量課金サブスクリプションの利用量を増やすには、reportUsage メソッドを使います:
$user = User::find(1);
$user->subscription('default')->reportUsage();
デフォルトでは、請求期間に「利用量1」が追加されます。特定の利用量を請求期間に追加したい場合は、引数にその数値を渡せます:
$user = User::find(1);
$user->subscription('default')->reportUsage(15);
単一のサブスクリプションで複数の価格を提供している場合は、reportUsageFor メソッドを使って報告したい従量課金価格を指定する必要があります:
$user = User::find(1);
$user->subscription('default')->reportUsageFor('price_metered', 15);
以前に報告した利用量を更新する必要がある場合があります。その場合、reportUsage の第2引数にタイムスタンプまたは DateTimeInterface インスタンスを渡します。これにより、指定した日時に報告された利用量が更新されます。指定日時が現在の請求期間内であれば、過去の利用量レコードを更新し続けることができます:
$user = User::find(1);
$user->subscription('default')->reportUsage(5, $timestamp);
#利用記録の取得
顧客の過去の利用量を取得するには、サブスクリプションインスタンスの usageRecords メソッドを使います:
$user = User::find(1);
$usageRecords = $user->subscription('default')->usageRecords();
単一のサブスクリプションで複数の価格を提供している場合は、usageRecordsFor メソッドを使って取得したい従量課金価格を指定できます:
$user = User::find(1);
$usageRecords = $user->subscription('default')->usageRecordsFor('price_metered');
usageRecords と usageRecordsFor メソッドは、利用記録の連想配列を含む Collection インスタンスを返します。この配列をループして顧客の総利用量を表示できます:
@foreach ($usageRecords as $usageRecord)
- 期間開始: {{ $usageRecord['period']['start'] }}
- 期間終了: {{ $usageRecord['period']['end'] }}
- 総利用量: {{ $usageRecord['total_usage'] }}
@endforeach
返されるすべての利用データの詳細や Stripe のカーソルベースのページネーションの使い方については、公式の Stripe API ドキュメントを参照してください。
#サブスクリプションの税金
税率を手動で計算する代わりに、Stripe Tax を使って自動計算できます
ユーザーがサブスクリプションで支払う税率を指定するには、課金対象モデルに taxRates メソッドを実装し、Stripe の税率IDを含む配列を返します。これらの税率はStripe ダッシュボードで定義できます:
/**
* 顧客のサブスクリプションに適用する税率。
*
* @return array<int, string>
*/
public function taxRates(): array
{
return ['txr_id'];
}
taxRates メソッドを使うと、国や税率が異なるユーザーごとに税率を適用できるため便利です。
複数の製品を含むサブスクリプションを提供している場合は、課金対象モデルに priceTaxRates メソッドを実装して、価格ごとに異なる税率を定義できます:
/**
* 顧客のサブスクリプションに適用する税率。
*
* @return array<string, array<int, string>>
*/
public function priceTaxRates(): array
{
return [
'price_monthly' => ['txr_id'],
];
}
taxRates メソッドはサブスクリプション課金にのみ適用されます。Cashier で「単発」課金を行う場合は、その都度税率を手動で指定する必要があります。
#税率の同期
taxRates メソッドで返すハードコードされた税率IDを変更しても、既存のサブスクリプションの税設定は変わりません。既存サブスクリプションの税率を新しい taxRates の値に更新したい場合は、ユーザーのサブスクリプションインスタンスで syncTaxRates メソッドを呼び出してください:
$user->subscription('default')->syncTaxRates();
これにより、複数の製品を含むサブスクリプションの各アイテム税率も同期されます。アプリケーションで複数の製品を含むサブスクリプションを提供している場合は、請求可能なモデルが priceTaxRates メソッドを実装していることを確認してください 上で説明した。
#税金免除
Cashier は、顧客が税金免除かどうかを判定するために isNotTaxExempt、isTaxExempt、reverseChargeApplies メソッドも提供しています。これらのメソッドは Stripe API を呼び出して顧客の免除状況を判定します:
use App\Models\User;
$user = User::find(1);
$user->isTaxExempt();
$user->isNotTaxExempt();
$user->reverseChargeApplies();
これらのメソッドは Laravel\Cashier\Invoice オブジェクトでも利用可能です。ただし、Invoice オブジェクトで呼び出すと、請求書作成時点の免除状況を判定します。
#サブスクリプションのアンカーデート
デフォルトでは、請求サイクルのアンカーはサブスクリプション作成日、またはトライアル期間がある場合はトライアル終了日です。請求アンカーデートを変更したい場合は、anchorBillingCycleOn メソッドを使います:
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$anchor = Carbon::parse('first day of next month');
$request->user()->newSubscription('default', 'price_monthly')
->anchorBillingCycleOn($anchor->startOfDay())
->create($request->paymentMethodId);
// ...
});
サブスクリプションの請求サイクル管理について詳しくは、Stripe の請求サイクルドキュメントを参照してください。
#サブスクリプションのキャンセル
サブスクリプションをキャンセルするには、ユーザーのサブスクリプションで cancel メソッドを呼び出します:
$user->subscription('default')->cancel();
サブスクリプションがキャンセルされると、Cashier は自動的に subscriptions テーブルの ends_at カラムを設定します。このカラムは subscribed メソッドが false を返し始めるタイミングを判断するために使われます。
例えば、顧客が3月1日にサブスクリプションをキャンセルしても、終了予定日が3月5日であれば、subscribed メソッドは3月5日まで true を返し続けます。これは通常、ユーザーが請求サイクル終了までアプリを利用できるためです。
ユーザーがサブスクリプションをキャンセルしたが「猶予期間」中かどうかは、onGracePeriod メソッドで判定できます:
if ($user->subscription('default')->onGracePeriod()) {
// ...
}
サブスクリプションを即時キャンセルしたい場合は、ユーザーのサブスクリプションで cancelNow メソッドを呼び出します:
$user->subscription('default')->cancelNow();
未請求の従量課金利用分や新規・保留中の按分請求アイテムを即時請求してキャンセルしたい場合は、cancelNowAndInvoice メソッドを使います:
$user->subscription('default')->cancelNowAndInvoice();
特定の日時にキャンセルすることも可能です:
$user->subscription('default')->cancelAt(
now()->addDays(10)
);
最後に、ユーザーモデルを削除する前に必ずサブスクリプションをキャンセルしてください:
$user->subscription('default')->cancelNow();
$user->delete();
#サブスクリプションの再開
顧客がサブスクリプションをキャンセルした後、猶予期間内であれば resume メソッドで再開できます:
$user->subscription('default')->resume();
顧客がキャンセル後、サブスクリプションが完全に終了する前に再開すると、即時請求は発生せず、元の請求サイクルで再度課金されます。
#サブスクリプショントライアル
#支払い方法を事前に取得する場合
トライアル期間を提供しつつ、支払い方法情報を事前に取得したい場合は、サブスクリプション作成時に trialDays メソッドを使います:
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$request->user()->newSubscription('default', 'price_monthly')
->trialDays(10)
->create($request->paymentMethodId);
// ...
});
このメソッドは、データベースのサブスクリプションレコードにトライアル終了日を設定し、Stripe に対してその日までは課金を開始しないよう指示します。trialDays を使うと、Cashier は Stripe で設定されたデフォルトのトライアル期間を上書きします。
顧客のサブスクリプションがトライアル終了日までにキャンセルされない場合、トライアル終了後すぐに課金されるため、ユーザーにトライアル終了日を必ず通知してください。
trialUntil メソッドは、トライアル期間の終了日時を指定する DateTime インスタンスを渡せます:
use Carbon\Carbon;
$user->newSubscription('default', 'price_monthly')
->trialUntil(Carbon::now()->addDays(10))
->create($paymentMethod);
ユーザーがトライアル期間中かどうかは、ユーザーインスタンスの onTrial メソッドか、サブスクリプションインスタンスの onTrial メソッドのどちらかで判定できます。以下の2つの例は同じ意味です:
if ($user->onTrial('default')) {
// ...
}
if ($user->subscription('default')->onTrial()) {
// ...
}
endTrial メソッドを使うと、サブスクリプションのトライアルを即座に終了できます:
$user->subscription('default')->endTrial();
既存のトライアルが期限切れかどうかは、hasExpiredTrial メソッドで判定できます:
if ($user->hasExpiredTrial('default')) {
// ...
}
if ($user->subscription('default')->hasExpiredTrial()) {
// ...
}
#Stripe / Cashier でのトライアル日数の定義
価格のトライアル日数は Stripe ダッシュボードで設定するか、常に Cashier で明示的に渡すか選べます。Stripe でトライアル日数を設定した場合、過去にサブスクリプションがあった顧客の新規サブスクリプションも含め、skipTrial() メソッドを明示的に呼ばない限り常にトライアル期間が付与されることに注意してください。
#支払い方法を先に受け取らない場合
ユーザーの支払い方法情報を先に受け取らずにトライアル期間を提供したい場合は、ユーザーレコードの trial_ends_at カラムに希望のトライアル終了日時を設定できます。通常はユーザー登録時に行います:
use App\Models\User;
$user = User::create([
// ...
'trial_ends_at' => now()->addDays(10),
]);
Billable モデルのクラス定義内で trial_ends_at 属性に対して 日付キャスト を必ず追加してください。
Cashier ではこのタイプのトライアルを「ジェネリックトライアル」と呼びます。既存のサブスクリプションに紐づかないためです。Billable モデルの onTrial メソッドは、現在の日付が trial_ends_at の値を過ぎていなければ true を返します:
if ($user->onTrial()) {
// ユーザーはトライアル期間中です...
}
ユーザーの実際のサブスクリプションを作成する準備ができたら、通常通り newSubscription メソッドを使えます:
$user = User::find(1);
$user->newSubscription('default', 'price_monthly')->create($paymentMethod);
ユーザーのトライアル終了日時は trialEndsAt メソッドで取得できます。このメソッドはトライアル中なら Carbon の日付インスタンスを返し、そうでなければ null を返します。特定のサブスクリプションタイプのトライアル終了日時を取得したい場合は、オプションのサブスクリプションタイプパラメータを渡せます:
if ($user->onTrial()) {
$trialEndsAt = $user->trialEndsAt('main');
}
ユーザーが「ジェネリック」トライアル期間中で、まだ実際のサブスクリプションを作成していないことを特に知りたい場合は、onGenericTrial メソッドを使えます:
if ($user->onGenericTrial()) {
// ユーザーは「ジェネリック」トライアル期間中です...
}
#トライアル期間の延長
extendTrial メソッドを使うと、サブスクリプション作成後にトライアル期間を延長できます。トライアルがすでに期限切れで顧客に請求が始まっていても、延長トライアルを提供可能です。トライアル期間中の時間は次回請求書から差し引かれます:
use App\Models\User;
$subscription = User::find(1)->subscription('default');
// 今から7日後にトライアルを終了する...
$subscription->extendTrial(
now()->addDays(7)
);
// トライアルにさらに5日追加する...
$subscription->extendTrial(
$subscription->trial_ends_at->addDays(5)
);
#Stripe Webhook の処理
ローカル開発中の webhook テストには Stripe CLI を利用できます。
Stripe は様々なイベントを webhook 経由でアプリケーションに通知できます。デフォルトで、Cashier サービスプロバイダーが Cashier の webhook コントローラーを指すルートを自動登録します。このコントローラーがすべての webhook リクエストを処理します。
デフォルトで Cashier の webhook コントローラーは、失敗したチャージが多すぎるサブスクリプションのキャンセル(Stripe 設定による)、顧客の更新・削除、サブスクリプションの更新、支払い方法の変更を自動処理します。ただし、後述するように、このコントローラーを拡張して任意の Stripe webhook イベントを処理できます。
Stripe webhook を正しく処理するには、Stripe 管理画面で webhook URL を設定してください。デフォルトでは Cashier の webhook コントローラーは /stripe/webhook パスに応答します。Stripe 管理画面で有効にすべき webhook イベントの一覧は以下の通りです:
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedcustomer.updatedcustomer.deletedpayment_method.automatically_updatedinvoice.payment_action_requiredinvoice.payment_succeeded
便利なことに、Cashier には cashier:webhook Artisan コマンドが用意されています。このコマンドは Cashier が必要とするすべてのイベントをリッスンする webhook を Stripe に作成します:
php artisan cashier:webhook
作成される webhook はデフォルトで APP_URL 環境変数と Cashier に含まれる cashier.webhook ルートの URL を指します。別の URL を使いたい場合はコマンド実行時に --url オプションを指定できます:
php artisan cashier:webhook --url "https://example.com/stripe/webhook"
作成される webhook は、Cashier のバージョンに対応した Stripe API バージョンを使用します。別の Stripe バージョンを使いたい場合は --api-version オプションを指定してください:
php artisan cashier:webhook --api-version="2019-12-03"
作成後、webhook は即座に有効になります。作成はするが準備ができるまで無効にしておきたい場合は、コマンド実行時に --disabled オプションを指定できます:
php artisan cashier:webhook --disabled
受信する Stripe webhook リクエストは、Cashier に含まれる webhook 署名検証 ミドルウェアで必ず保護してください。
#Webhook と CSRF 保護
Stripe webhook は Laravel の CSRF 保護 をバイパスする必要があるため、アプリケーションの App\Http\Middleware\VerifyCsrfToken ミドルウェアで URI を例外リストに追加するか、web ミドルウェアグループ外にルートを配置してください:
protected $except = [
'stripe/*',
];
#Webhook イベントハンドラーの定義
Cashier は失敗したチャージによるサブスクリプションキャンセルやその他一般的な Stripe webhook イベントを自動処理します。追加で処理したい webhook イベントがあれば、Cashier が発行する以下のイベントをリッスンして対応できます:
Laravel\Cashier\Events\WebhookReceivedLaravel\Cashier\Events\WebhookHandled
両イベントには Stripe webhook の全ペイロードが含まれます。例えば invoice.payment_succeeded webhook を処理したい場合は、イベントを処理する リスナー を登録できます:
<?php
namespace App\Listeners;
use Laravel\Cashier\Events\WebhookReceived;
class StripeEventListener
{
/**
* 受信した Stripe webhook を処理します。
*/
public function handle(WebhookReceived $event): void
{
if ($event->payload['type'] === 'invoice.payment_succeeded') {
// 受信イベントを処理...
}
}
}
リスナーを定義したら、アプリケーションの EventServiceProvider に登録してください:
<?php
namespace App\Providers;
use App\Listeners\StripeEventListener;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Laravel\Cashier\Events\WebhookReceived;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
WebhookReceived::class => [
StripeEventListener::class,
],
];
}
#Webhook 署名の検証
Webhook のセキュリティ確保には Stripe の webhook 署名 を利用できます。Cashier は受信した Stripe webhook リクエストの妥当性を検証するミドルウェアを自動で含みます。
Webhook 検証を有効にするには、アプリケーションの .env ファイルに STRIPE_WEBHOOK_SECRET 環境変数を設定してください。webhook の secret は Stripe アカウントのダッシュボードから取得できます。
#単発チャージ
#シンプルチャージ
顧客に対して一度限りの請求を行いたい場合は、課金可能なモデルインスタンスの charge メソッドを使用できます。charge メソッドの第2引数として、支払い方法の識別子 を渡す必要があります:
use Illuminate\Http\Request;
Route::post('/purchase', function (Request $request) {
$stripeCharge = $request->user()->charge(
100, $request->paymentMethodId
);
// ...
});
charge メソッドは第3引数に配列を受け取り、Stripe のチャージ作成時に任意のオプションを渡せます。利用可能なオプションの詳細は Stripe ドキュメント を参照してください:
$user->charge(100, $paymentMethod, [
'custom_option' => $value,
]);
基になる顧客やユーザーなしで charge メソッドを使うこともできます。その場合はアプリケーションの Billable モデルの新しいインスタンスで charge を呼び出します:
use App\Models\User;
$stripeCharge = (new User)->charge(100, $paymentMethod);
チャージが失敗すると charge メソッドは例外を投げます。成功した場合は Laravel\Cashier\Payment のインスタンスが返されます:
try {
$payment = $user->charge(100, $paymentMethod);
} catch (Exception $e) {
// ...
}
charge メソッドの支払い金額は、アプリケーションで使う通貨の最小単位で指定してください。例えば米ドルの場合はセント単位で指定します。
#請求書付きチャージ
一度だけのチャージで PDF 請求書を顧客に提供したい場合は、invoicePrice メソッドを使えます。例えば、顧客にシャツを5枚請求する場合:
$user->invoicePrice('price_tshirt', 5);
請求書はユーザーのデフォルト支払い方法に即座に請求されます。invoicePrice メソッドは第3引数に請求アイテムのオプション配列を、第4引数に請求書自体のオプション配列を受け取れます:
$user->invoicePrice('price_tshirt', 5, [
'discounts' => [
['coupon' => 'SUMMER21SALE']
],
], [
'default_tax_rates' => ['txr_id'],
]);
invoicePrice と同様に、tabPrice メソッドを使うと複数アイテム(1請求書あたり最大250アイテム)を顧客の「タブ」に追加し、まとめて請求できます。例えばシャツ5枚とマグカップ2個を請求する場合:
$user->tabPrice('price_tshirt', 5);
$user->tabPrice('price_mug', 2);
$user->invoice();
または、invoiceFor メソッドを使って顧客のデフォルト支払い方法に対して「一回限り」のチャージを行うこともできます:
$user->invoiceFor('One Time Fee', 500);
invoiceFor メソッドも利用可能ですが、事前定義された価格を使う invoicePrice と tabPrice メソッドの利用を推奨します。そうすることで、Stripe ダッシュボードで商品別の売上分析やデータがより充実します。
invoice、invoicePrice、および invoiceFor メソッドは、失敗した請求を再試行するStripeの請求書を作成します。請求書で失敗したチャージを再試行させたくない場合は、最初の失敗後にStripe APIを使って請求書をクローズする必要があります。
#Payment Intents の作成
請求可能なモデルインスタンスの pay メソッドを呼び出すことで、新しいStripeのPayment Intentを作成できます。このメソッドを呼ぶと、Laravel\Cashier\Payment インスタンスでラップされたPayment Intentが作成されます:
use Illuminate\Http\Request;
Route::post('/pay', function (Request $request) {
$payment = $request->user()->pay(
$request->get('amount')
);
return $payment->client_secret;
});
Payment Intentを作成した後、クライアントシークレットをアプリケーションのフロントエンドに返し、ユーザーがブラウザで支払いを完了できるようにします。StripeのPayment Intentを使った支払いフローの詳細は、Stripeのドキュメントをご覧ください。
pay メソッドを使うと、Stripeダッシュボードで有効になっているデフォルトの支払い方法が顧客に利用可能になります。特定の支払い方法のみを許可したい場合は、payWith メソッドを使えます:
use Illuminate\Http\Request;
Route::post('/pay', function (Request $request) {
$payment = $request->user()->payWith(
$request->get('amount'), ['card', 'bancontact']
);
return $payment->client_secret;
});
pay と payWith メソッドは、アプリケーションで使用している通貨の最小単位で支払い金額を受け取ります。例えば、米ドルの場合は金額をセント単位で指定してください。
#チャージの返金
Stripeのチャージを返金する必要がある場合は、refund メソッドを使えます。このメソッドは最初の引数にStripeのPayment Intent IDを受け取ります:
$payment = $user->charge(100, $paymentMethodId);
$user->refund($payment->id);
#請求書
#請求書の取得
請求可能なモデルの請求書を配列で簡単に取得するには、invoices メソッドを使います。invoices メソッドは Laravel\Cashier\Invoice インスタンスのコレクションを返します:
$invoices = $user->invoices();
保留中の請求書も結果に含めたい場合は、invoicesIncludingPending メソッドを使えます:
$invoices = $user->invoicesIncludingPending();
特定の請求書をIDで取得するには、findInvoice メソッドを使います:
$invoice = $user->findInvoice($invoiceId);
#請求書情報の表示
顧客の請求書一覧を表示する際は、請求書のメソッドを使って関連情報を表示できます。例えば、すべての請求書をテーブルで一覧表示し、ユーザーが簡単にダウンロードできるようにすることが可能です:
<table>
@foreach ($invoices as $invoice)
<tr>
<td>{{ $invoice->date()->toFormattedDateString() }}</td>
<td>{{ $invoice->total() }}</td>
<td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td>
</tr>
@endforeach
</table>
#今後の請求書
顧客の今後の請求書を取得するには、upcomingInvoice メソッドを使います:
$invoice = $user->upcomingInvoice();
同様に、顧客が複数のサブスクリプションを持っている場合は、特定のサブスクリプションの今後の請求書も取得できます:
$invoice = $user->subscription('default')->upcomingInvoice();
#サブスクリプション請求書のプレビュー
previewInvoice メソッドを使うと、価格変更前に請求書をプレビューできます。これにより、価格変更後の顧客の請求書がどのようになるかを確認できます:
$invoice = $user->subscription('default')->previewInvoice('price_yearly');
複数の新しい価格で請求書をプレビューしたい場合は、価格の配列を previewInvoice メソッドに渡せます:
$invoice = $user->subscription('default')->previewInvoice(['price_yearly', 'price_metered']);
#請求書PDFの生成
請求書PDFを生成する前に、ComposerでDompdfライブラリをインストールしてください。これはCashierのデフォルトの請求書レンダラーです:
composer require dompdf/dompdf
ルートやコントローラー内で、downloadInvoice メソッドを使って指定した請求書のPDFダウンロードを生成できます。このメソッドは請求書のダウンロードに必要なHTTPレスポンスを自動生成します:
use Illuminate\Http\Request;
Route::get('/user/invoice/{invoice}', function (Request $request, string $invoiceId) {
return $request->user()->downloadInvoice($invoiceId);
});
デフォルトでは、請求書のすべてのデータはStripeに保存された顧客および請求書データから取得されます。ファイル名は app.name の設定値に基づきます。ただし、downloadInvoice メソッドの第2引数に配列を渡すことで、会社名や製品情報など一部のデータをカスタマイズできます:
return $request->user()->downloadInvoice($invoiceId, [
'vendor' => 'Your Company',
'product' => 'Your Product',
'street' => 'Main Str. 1',
'location' => '2000 Antwerp, Belgium',
'phone' => '+32 499 00 00 00',
'email' => '[email protected]',
'url' => 'https://example.com',
'vendorVat' => 'BE123456789',
]);
downloadInvoice メソッドは第3引数でカスタムファイル名も指定できます。このファイル名には自動的に .pdf が付加されます:
return $request->user()->downloadInvoice($invoiceId, [], 'my-invoice');
#カスタム請求書レンダラー
Cashierではカスタム請求書レンダラーの利用も可能です。デフォルトでは DompdfInvoiceRenderer を使い、dompdf PHPライブラリで請求書を生成します。独自のレンダラーを使いたい場合は、Laravel\Cashier\Contracts\InvoiceRenderer インターフェイスを実装してください。例えば、サードパーティのPDFレンダリングサービスのAPIを使って請求書PDFを生成することもできます:
use Illuminate\Support\Facades\Http;
use Laravel\Cashier\Contracts\InvoiceRenderer;
use Laravel\Cashier\Invoice;
class ApiInvoiceRenderer implements InvoiceRenderer
{
/**
* 指定された請求書をレンダリングし、生のPDFバイト列を返します。
*/
public function render(Invoice $invoice, array $data = [], array $options = []): string
{
$html = $invoice->view($data)->render();
return Http::get('https://example.com/html-to-pdf', ['html' => $html])->get()->body();
}
}
請求書レンダラー契約を実装したら、アプリケーションの config/cashier.php 設定ファイル内の cashier.invoices.renderer 設定値をカスタムレンダラーのクラス名に更新してください。
#チェックアウト
Cashier StripeはStripe Checkoutもサポートしています。Stripe Checkoutは、カスタムの支払いページを実装する手間を省き、ホストされた事前構築済みの支払いページを提供します。
以下のドキュメントでは、CashierでStripe Checkoutを使い始める方法を説明しています。Stripe Checkoutの詳細は、Stripeの公式ドキュメントもご覧ください。
#商品のチェックアウト
Stripeダッシュボードで作成済みの商品に対して、請求可能なモデルの checkout メソッドを使ってチェックアウトを実行できます。checkout メソッドは新しいStripe Checkoutセッションを開始します。デフォルトではStripeのPrice IDを渡す必要があります:
use Illuminate\Http\Request;
Route::get('/product-checkout', function (Request $request) {
return $request->user()->checkout('price_tshirt');
});
必要に応じて、商品数量も指定できます:
use Illuminate\Http\Request;
Route::get('/product-checkout', function (Request $request) {
return $request->user()->checkout(['price_tshirt' => 15]);
});
顧客がこのルートにアクセスすると、StripeのCheckoutページにリダイレクトされます。デフォルトでは購入完了またはキャンセル時に home ルートにリダイレクトされますが、success_url と cancel_url オプションでカスタムのコールバックURLを指定できます:
use Illuminate\Http\Request;
Route::get('/product-checkout', function (Request $request) {
return $request->user()->checkout(['price_tshirt' => 1], [
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});
success_url オプションを定義する際、StripeにチェックアウトセッションIDをクエリ文字列パラメータとして追加させることができます。そのためには、success_url のクエリ文字列にリテラル文字列 {CHECKOUT_SESSION_ID} を含めてください。Stripeはこのプレースホルダーを実際のチェックアウトセッションIDに置き換えます:
use Illuminate\Http\Request;
use Stripe\Checkout\Session;
use Stripe\Customer;
Route::get('/product-checkout', function (Request $request) {
return $request->user()->checkout(['price_tshirt' => 1], [
'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => route('checkout-cancel'),
]);
});
Route::get('/checkout-success', function (Request $request) {
$checkoutSession = $request->user()->stripe()->checkout->sessions->retrieve($request->get('session_id'));
return view('checkout.success', ['checkoutSession' => $checkoutSession]);
})->name('checkout-success');
#プロモーションコード
デフォルトでは、Stripe Checkoutはユーザーが利用可能なプロモーションコードを許可していません。幸い、Checkoutページでこれを有効にする簡単な方法があります。allowPromotionCodes メソッドを呼び出してください:
use Illuminate\Http\Request;
Route::get('/product-checkout', function (Request $request) {
return $request->user()
->allowPromotionCodes()
->checkout('price_tshirt');
});
#単一チャージのチェックアウト
Stripeダッシュボードで作成されていないアドホックな商品の単純なチャージも実行できます。その場合は請求可能なモデルの checkoutCharge メソッドを使い、チャージ金額、商品名、任意で数量を渡します。顧客がこのルートにアクセスするとStripeのCheckoutページにリダイレクトされます:
use Illuminate\Http\Request;
Route::get('/charge-checkout', function (Request $request) {
return $request->user()->checkoutCharge(1200, 'T-Shirt', 5);
});
checkoutCharge メソッドを使うと、Stripeダッシュボードに常に新しい商品と価格が作成されます。したがって、事前にStripeダッシュボードで商品を作成し、代わりに checkout メソッドを使うことを推奨します。
#サブスクリプションのチェックアウト
サブスクリプションでStripe Checkoutを使うには、Stripeダッシュボードで customer.subscription.created ウェブフックを有効にする必要があります。このウェブフックはデータベースにサブスクリプションレコードを作成し、関連するサブスクリプションアイテムをすべて保存します。
Cashierのサブスクリプションビルダーでサブスクリプションを定義した後、checkout メソッドを呼び出してStripe Checkoutを開始できます。顧客がこのルートにアクセスするとStripeのCheckoutページにリダイレクトされます:
use Illuminate\Http\Request;
Route::get('/subscription-checkout', function (Request $request) {
return $request->user()
->newSubscription('default', 'price_monthly')
->checkout();
});
商品チェックアウトと同様に、成功時とキャンセル時のURLをカスタマイズできます:
use Illuminate\Http\Request;
Route::get('/subscription-checkout', function (Request $request) {
return $request->user()
->newSubscription('default', 'price_monthly')
->checkout([
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});
もちろん、サブスクリプションのチェックアウトでプロモーションコードを有効にすることもできます。
use Illuminate\Http\Request;
Route::get('/subscription-checkout', function (Request $request) {
return $request->user()
->newSubscription('default', 'price_monthly')
->allowPromotionCodes()
->checkout();
});
残念ながら、Stripe Checkout はサブスクリプション開始時のすべての請求オプションに対応していません。サブスクリプションビルダーの anchorBillingCycleOn メソッドの使用、割引計算の設定、支払い動作の設定は Stripe Checkout セッション中には効果がありません。利用可能なパラメータについては、Stripe Checkout Session API ドキュメントをご確認ください。
#Stripe Checkout とトライアル期間
もちろん、Stripe Checkout を使って完了するサブスクリプションを作成する際にトライアル期間を設定できます。
$checkout = Auth::user()->newSubscription('default', 'price_monthly')
->trialDays(3)
->checkout();
ただし、トライアル期間は最低でも48時間必要です。これは Stripe Checkout がサポートする最小のトライアル時間です。
#サブスクリプションとウェブフック
Stripe と Cashier はウェブフックを通じてサブスクリプションのステータスを更新するため、顧客が支払い情報を入力してアプリに戻った時点でサブスクリプションがまだ有効になっていない可能性があります。この場合、支払いまたはサブスクリプションが保留中であることをユーザーに伝えるメッセージを表示するとよいでしょう。
#税IDの収集
Checkout は顧客の税IDの収集もサポートしています。チェックアウトセッションでこれを有効にするには、セッション作成時に collectTaxIds メソッドを呼び出します。
$checkout = $user->collectTaxIds()->checkout('price_tshirt');
このメソッドを呼び出すと、顧客が法人として購入するかどうかを示す新しいチェックボックスが表示されます。法人の場合は税ID番号を入力する機会が与えられます。
すでにアプリケーションのサービスプロバイダーで自動税収集を設定している場合、この機能は自動的に有効になり、collectTaxIds メソッドを呼び出す必要はありません。
#ゲストチェックアウト
Checkout::guest メソッドを使うと、アカウントを持たないアプリのゲスト向けにチェックアウトセッションを開始できます。
use Illuminate\Http\Request;
use Laravel\Cashier\Checkout;
Route::get('/product-checkout', function (Request $request) {
return Checkout::guest()->create('price_tshirt', [
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});
既存ユーザーのチェックアウトセッション作成時と同様に、Laravel\Cashier\CheckoutBuilder インスタンスの追加メソッドを使ってゲストチェックアウトセッションをカスタマイズできます。
use Illuminate\Http\Request;
use Laravel\Cashier\Checkout;
Route::get('/product-checkout', function (Request $request) {
return Checkout::guest()
->withPromotionCode('promo-code')
->create('price_tshirt', [
'success_url' => route('your-success-route'),
'cancel_url' => route('your-cancel-route'),
]);
});
ゲストチェックアウト完了後、Stripe は checkout.session.completed ウェブフックイベントを送信できます。必ずStripeウェブフックの設定を行い、このイベントをアプリに送信するようにしてください。Stripeダッシュボードでウェブフックを有効にしたら、Cashierでウェブフックを処理できます。ウェブフックのペイロードに含まれるオブジェクトは、顧客の注文を処理するために確認できるcheckoutオブジェクトです。
#支払い失敗の処理
サブスクリプションや単一請求の支払いが失敗することがあります。この場合、Cashier は Laravel\Cashier\Exceptions\IncompletePayment 例外をスローして通知します。この例外をキャッチした後、2つの対応方法があります。
まず、Cashier に含まれる専用の支払い確認ページに顧客をリダイレクトできます。このページは Cashier のサービスプロバイダーで名前付きルートとして登録されています。したがって、IncompletePayment 例外をキャッチして支払い確認ページにリダイレクトできます。
use Laravel\Cashier\Exceptions\IncompletePayment;
try {
$subscription = $user->newSubscription('default', 'price_monthly')
->create($paymentMethod);
} catch (IncompletePayment $exception) {
return redirect()->route(
'cashier.payment',
[$exception->payment->id, 'redirect' => route('home')]
);
}
支払い確認ページでは、顧客にクレジットカード情報の再入力や Stripe が要求する追加の操作(例:「3Dセキュア」認証)を促します。支払い確認後、上記の redirect パラメータで指定したURLにリダイレクトされます。リダイレクト時に URL に message(文字列)と success(整数)のクエリ文字列変数が追加されます。現在、支払いページは以下の支払い方法タイプをサポートしています:
- クレジットカード
- Alipay
- Bancontact
- BECS Direct Debit
- EPS
- Giropay
- iDEAL
- SEPA Direct Debit
あるいは、Stripe に支払い確認を任せることもできます。この場合、支払い確認ページにリダイレクトする代わりに、Stripe ダッシュボードで自動請求メールの設定を行います。ただし、IncompletePayment 例外が発生した場合は、ユーザーに支払い確認のためのメールが送信されることを通知してください。
支払い例外は、Billable トレイトを使うモデルの charge、invoiceFor、invoice メソッドで発生する可能性があります。サブスクリプション関連では、SubscriptionBuilder の create メソッドや、Subscription と SubscriptionItem モデルの incrementAndInvoice、swapAndInvoice メソッドで発生することがあります。
既存のサブスクリプションに未完了の支払いがあるかどうかは、課金可能モデルまたはサブスクリプションインスタンスの hasIncompletePayment メソッドで判定できます。
if ($user->hasIncompletePayment('default')) {
// ...
}
if ($user->subscription('default')->hasIncompletePayment()) {
// ...
}
未完了支払いの具体的な状態は、例外インスタンスの payment プロパティを調べることで判別できます。
use Laravel\Cashier\Exceptions\IncompletePayment;
try {
$user->charge(1000, 'pm_card_threeDSecure2Required');
} catch (IncompletePayment $exception) {
// 支払いインテントのステータスを取得...
$exception->payment->status;
// 特定の条件をチェック...
if ($exception->payment->requiresPaymentMethod()) {
// ...
} elseif ($exception->payment->requiresConfirmation()) {
// ...
}
}
#支払いの確認
一部の支払い方法では、支払いを確定するために追加データが必要です。例えば、SEPA 支払い方法では支払い処理中に追加の「mandate」データが必要です。withPaymentConfirmationOptions メソッドでこのデータを Cashier に渡せます。
$subscription->withPaymentConfirmationOptions([
'mandate_data' => '...',
])->swap('price_xxx');
支払い確認時に受け付けるすべてのオプションについては、Stripe API ドキュメントをご参照ください。
#強力な顧客認証(SCA)
ビジネスや顧客がヨーロッパに拠点を置く場合、EU の強力な顧客認証(SCA)規制に準拠する必要があります。これは2019年9月に欧州連合が支払い詐欺防止のために導入した規制です。幸い、Stripe と Cashier は SCA 対応アプリケーションの構築に対応しています。
はじめる前に、Stripe の PSD2 と SCA に関するガイドおよびSCA 新APIのドキュメントを必ず確認してください。
#追加確認が必要な支払い
SCA 規制では、支払いを確定・処理するために追加の認証が必要になることがあります。この場合、Cashier は Laravel\Cashier\Exceptions\IncompletePayment 例外をスローして追加認証が必要であることを通知します。これらの例外の処理方法は支払い失敗の処理のドキュメントに詳しく記載されています。
Stripe や Cashier が表示する支払い確認画面は、特定の銀行やカード発行会社の支払いフローに合わせてカスタマイズされており、追加のカード認証、一時的な少額請求、別デバイス認証などの検証が含まれることがあります。
#未完了および支払い遅延状態
支払いに追加確認が必要な場合、サブスクリプションは stripe_status データベースカラムで示されるように incomplete または past_due 状態のままになります。Cashier は支払い確認が完了し、Stripe からウェブフックで通知を受けると自動的にサブスクリプションを有効化します。
incomplete と past_due 状態の詳細については、これらの状態に関する追加ドキュメントをご参照ください。
#オフセッション支払い通知
SCA 規制により、サブスクリプションが有効な間でも顧客は時折支払い情報の確認が必要です。Cashier はオフセッション支払い確認が必要な場合に顧客へ通知を送信できます。これには、サブスクリプションの更新時などが該当します。Cashier の支払い通知は CASHIER_PAYMENT_NOTIFICATION 環境変数に通知クラスを設定することで有効化できます。デフォルトでは無効です。もちろん、Cashier にはこの目的で使える通知クラスが含まれていますが、必要に応じて独自の通知クラスを用意してもかまいません。
CASHIER_PAYMENT_NOTIFICATION=Laravel\Cashier\Notifications\ConfirmPayment
オフセッション支払い確認通知を確実に配信するには、Stripe ウェブフックの設定を行い、Stripe ダッシュボードで invoice.payment_action_required ウェブフックを有効にしてください。また、Billable モデルは Laravel の Illuminate\Notifications\Notifiable トレイトを使用している必要があります。
顧客が手動で追加確認が必要な支払いを行った場合でも通知は送信されます。Stripe は支払いが手動かオフセッションかを判別できません。ただし、顧客が支払い確認後に支払いページを訪れても「支払い成功」メッセージが表示され、誤って同じ支払いを二重に確認して二重請求されることはありません。
#Stripe SDK
多くの Cashier オブジェクトは Stripe SDK オブジェクトのラッパーです。Stripe オブジェクトを直接操作したい場合は、asStripe メソッドで簡単に取得できます。
$stripeSubscription = $subscription->asStripeSubscription();
$stripeSubscription->application_fee_percent = 5;
$stripeSubscription->save();
updateStripeSubscription メソッドを使って Stripe サブスクリプションを直接更新することもできます。
$subscription->updateStripeSubscription(['application_fee_percent' => 5]);
Cashier クラスの stripe メソッドを使うと、Stripe\StripeClient クライアントを直接利用できます。例えば、このメソッドを使って StripeClient インスタンスにアクセスし、Stripeアカウントから価格の一覧を取得できます。
use Laravel\Cashier\Cashier;
$prices = Cashier::stripe()->prices->all();
#テスト
Cashier を使ったアプリケーションのテスト時に、Stripe API への実際の HTTP リクエストをモックすることもできますが、その場合は Cashier の動作を部分的に再実装する必要があります。したがって、テストでは実際の Stripe API にアクセスすることを推奨します。これにより処理は遅くなりますが、アプリケーションが期待通りに動作していることをより確信でき、遅いテストは PHPUnit の独自のテストグループに分けることも可能です。
テスト時には、Cashier 自体にはすでに優れたテストスイートがあるため、自分のアプリケーションのサブスクリプションや支払いのフローだけに集中し、Cashier の内部動作すべてをテストする必要はありません。
はじめに、Stripe のテスト用シークレットキーを phpunit.xml ファイルに追加してください:
<env name="STRIPE_SECRET" value="sk_test_<your-key>"/>
これでテスト中に Cashier を操作すると、実際に Stripe のテスト環境へ API リクエストが送信されます。利便性のため、テスト用の Stripe アカウントにサブスクリプションや価格情報をあらかじめ用意しておくとよいでしょう。
クレジットカードの拒否や失敗など、さまざまな請求シナリオをテストするために、Stripe が提供する豊富なテスト用カード番号とトークンを利用できます。