【Laravel × Stripe】サブスクリプション処理を実装する手順2~CheckOutで決済ページの表示~
の続きです。
webhookは、Stripeがイベント(支払いの成功、サブスクリプションの更新など)を検出した際に、事前に設定したURL(エンドポイント)にHTTPリクエストを送信する仕組みです。これにより、リアルタイムでStripeのイベントに応答し、データベースの更新やメール通知などを自動的に行うことができます。
つまり、Striepe側で決済を実行し、成功の有無をプロジェクト側に受け取ることができます。
ここでは決済が成功したときに、webhookからレスポンスを受け取り、userテーブルにサブスク契約中であることを登録する処理を記述していきます。
Stripeダッシュボードからwebhookのエンドポイントを登録する
エンドポイントとは、webhookを受け取るURLのことです。
ここでは、[ドメインURL]/stripe/subscription/webhook
ルーティング(シリーズ1のおさらい)
# 決済完了ウェブホック
/* */
Route::post('stripe/subscription/webhook',
[StripSubscriptionController::class, 'webhook']);
Stripeのダッシュボードを開き、[開発者]->[エンドポイント]を開き、[エンドポイントを追加]をクリックします。
エンドポイントURLを入力・イベントを選択し、[イベントを追加]ボタンをクリックします。
エンドポイントURL:
[ドメインURL]/stripe/subscription/webhook
イベント:
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
署名シークレットをコピーしてプロジェクトに保存します。
.env
STRIPE_SUBSCRIPTION_ENDPOINT_SECRET=[サブスクエンドポイント証明シークレット]
config/stripe.php
<?php
return [
'public_key' => env('STRIPE_KEY'),
'secret_key' => env('STRIPE_SECRET'),
'price_id' => env('STRIPE_PRICE_ID'),
//追加
'subscription_endpoint_secret'=>
env('STRIPE_SUBSCRIPTION_ENDPOINT_SECRET'),
];
csrf除外処理
App\Http\Middleware\VerifyCsrfToken.php
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
protected $except = [
# サブスク決済完了ウェブホック
'stripe/subscription/webhook',
];
}
webhookを受け取る処理
webhookを受け取り、イベントごとに処理を分岐
コントローラー:StripSubscriptionController.php
/**
* サブスク決済完了ウェブホック
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
/
public function webhook(Request $request)
{
payload = json_decode($request->getContent(), true);
# Stripeの署名を検証する
$sigHeader = $request->header('Stripe-Signature');
$endpointSecret = config('stripe.subscription_endpoint_secret'); //エンドポイントシークレット
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
$event = null;
try {
$event = \Stripe\Webhook::constructEvent(
$payload, $sig_header, $endpoint_secret
);
} catch(\UnexpectedValueException $e) {
// Invalid payload
http_response_code(400);
exit();
} catch(\Stripe\Exception\SignatureVerificationException $e) {
// Invalid signature
http_response_code(400);
exit();
}
# イベント別の分岐処理
switch ($event->type)
{
## サブスクが更新されました
case 'customer.subscription.updated':
/* サブスクの[契約更新]フルフィルメントを実行 */
$session = $event->data->object;
return $this->handleCoustomerSubscriptionUpdate($request, $session);
break;
## サブスクが削除されました
case 'customer.subscription.deleted':
/* サブスクの[契約キャンセル]フルフィルメントを実行 */
$session = $event->data->object;
return $this->handleCoustomerSubscriptionDeleted($request, $session);
break;
default:
// 未知のイベントに対する処理
return response([], 200);
break;
}
}
イベントに応じたフィルフィメント処理
コントローラー:StripSubscriptionController.php
/**
* サブスクの[契約更新]フルフィルメントを実行するためのコード
*
* @param Object $session //Stripe Checkout Session オブジェクト
* @return Json
*/
private function handleCoustomerSubscriptionUpdate($request, $session)
{
# CheckoutSessionが処理済みの時は、スキップ
$session_id = $session['id'];
$column = 'stripe_checkout_session_id';
$check_day = now()->copy()->subHour(23);
# 客の情報
$user = User::where('stripe_id', $session['customer'])->first();
if( !$user ){
return response(['message' => '一致するユーザー情報がありません。'], 403);
}
# サブスク登録の確認
if( $user->subscription_id ){
return response(['message' => 'ユーザーのサブスク契約すみです。'], 403);
}
# 新規契約のとき=>UserにサブスクIDを保存
$subscription_id = $session->items->data[0]->plan['id'];
$is_update = isset( $user->subscription_id );
if( !$is_update || $user->subscription_id != $subscription_id ){
$user->update(['subscription_id'=>$subscription_id]);
}
# 200レスポンスを返す
return response( compact('user','subscription','point_history','ticket_history'), 200);
}
/**
* サブスクの[契約キャンセル]フルフィルメントを実行するためのコード
*
* @param Object $session //Stripe Checkout Session オブジェクト
* @return Json
*/
private function handleCoustomerSubscriptionDeleted($request, $session)
{
# サブスクプランの情報
$subscription_id = $session->items->data[0]->plan['id'];
$subscription = self::Subscriptions()[$subscription_id];
if( !$subscription ){
return response(['message' => 'サブスク情報がアプリケーション側で登録されていません。'], 403);
}
# 客の情報
$user = User::where('stripe_id', $session['customer'])->first();
if( !$user ){
return response(['message' => '一致するユーザー情報がありません。'], 403);
}
# UserkaraサブスクIDを削除
if( !$user->subscription_id ){
return response(['message' => 'ユーザーのサブスク契約情報が登録されていません。'], 403);
}
$user->update(['subscription_id'=>null]);//削除
# 200レスポンスを返す
return response( compact('user','subscription'), 200);
}
長くなったので、次回はカスタマーポータルを表示する処理について記述していきます。