0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Laravel × Stripe】サブスクリプション処理を実装する手順3~Webhookで決済処理~

Last updated at Posted at 2024-08-12

【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のダッシュボードを開き、[開発者]->[エンドポイント]を開き、[エンドポイントを追加]をクリックします。

スクリーンショット 2024-08-12 10.17.20.png


エンドポイントURLを入力・イベントを選択し、[イベントを追加]ボタンをクリックします。

エンドポイントURL:

[ドメインURL]/stripe/subscription/webhook

イベント:

customer.subscription.created
customer.subscription.updated
customer.subscription.deleted

スクリーンショット 2024-08-12 10.20.54.png


署名シークレットをコピーしてプロジェクトに保存します。

.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);
    }

長くなったので、次回はカスタマーポータルを表示する処理について記述していきます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?