$30 off During Our Annual Pro Sale. View Details »

AWS LambdaとStripeで オンライン決済・定期課金APIを実装しよう/phperkaigi-2022

AWS LambdaとStripeで オンライン決済・定期課金APIを実装しよう/phperkaigi-2022

オンライン決済サービスStripeを利用することで、少ないコードで複雑な料金体系の定期課金や決済機能を実装することができます。
また、Webhookを利用したバックエンドシステムへの組み込みや自動化、CRMなどとの連携も難しくありません。

このトークでは、PHPの実行環境としてAWS Lambdaを利用し、以下のトピックについて紹介します。
・AWS LambdaでPHPを利用する方法(Serverless Framework)
・AWS Secrets Managerを利用した、安全なAPIキー運用
・Stripe / Stripe Webhookを利用した定期課金の実装やサービス連携方法

◆セッション情報
2022/04/11 14:55〜 Track B レギュラートーク(20分)
https://fortee.jp/phperkaigi-2022/proposal/1f84d28b-264d-4d2d-8462-57539710d9e3

Hidetaka Okamoto (Stripe)

April 11, 2022
Tweet

More Decks by Hidetaka Okamoto (Stripe)

Other Decks in Programming

Transcript

  1. AWS LambdaとStripeで
    オンライン決済・定期課金APIを実装しよう
    PHPerKaigi 2022
    Hidetaka Okamoto (@hide__dev)
    2022/04/11

    View Slide

  2. 岡本 秀高 ( @hide__dev )
    ● Stripe Developer Advocate
    (ex-developer in Digitalcube)
    ● JavaScript / TypeScript developer
    ● AWS / Next.js / WordPress / etc…
    ● WordCamp Kyoto 2017 / JP_Stripes
    Connect 2019 / AWS Samurai 2017 /
    etc…
    ● 🐈(猫フラご容赦󰢛)
    2
    PHPerKaigi 2022
    #phperkaigi

    View Slide

  3. ● Serverless Frameworkで
    AWS LambdaにPHPアプリをデプロイする
    ● PHPで
    Stripeの決済フォームを実装する
    ● AWS Secrets Managerで
    Stripe APIキーを安全に管理する
    ● Webhook APIを実装・デプロイする
    ● 定期課金を実装する際のTips
    Agenda
    PHPerKaigi 2022

    View Slide

  4. ● Serverless Frameworkで
    AWS LambdaにPHPアプリをデプロイする
    ● PHPで
    Stripeの決済フォームを実装する
    ● AWS Secrets Managerで
    Stripe APIキーを安全に管理する
    ● Webhook APIを実装・デプロイする
    ● 定期課金を実装する際のTips
    Agenda
    PHPerKaigi 2022

    View Slide

  5. AWS LambdaでPHPアプリを実行する
    ● デフォルトのランタイムにPHPは存在しない
    ● 「カスタムランタイム」を作成し、
    Lambdaにアップロードする
    ● カスタムランタイムは、DockerfileまたはLambda Layerで設定可能
    ● 自作または公開されている
    Layerを利用するスタイルになる
    5
    PHPerKaigi 2022

    View Slide

  6. 自作する場合の
    Dockerfileの例
    PHPerKaigi 2022
    From:
    https://aws.amazon.com/jp/builders-flash/202106/new-lamb
    da-container-development-3/?awsf.filter-name=*all

    View Slide

  7. Serverless Framework + brefで手軽にPHP環境
    ● AWS Lambda向けの
    Serverless Frameworkプラグイン
    ● PHPアプリを
    簡単(Bref)にデプロイ・実行できる
    ● Composerを利用して
    インストール
    ● YAMLや
    PHPのテンプレートも提供
    7
    PHPerKaigi 2022
    #phperkaigi
    https://bref.sh/

    View Slide

  8. Serverless FWスタックのセットアップ
    8
    PHPerKaigi 2022
    % composer init
    % composer require bref/bref
    % vendor/bin/bref init
    What kind of lambda do you want to create? (you will be able to add more functions later by editing
    `serverless.yml`) [Web application]:
    [0] Web application
    [1] Event-driven function
    > 0
    Creating index.php
    Creating serverless.yml
    [OK] Project initialized and ready to test or deploy.

    View Slide

  9. “Web application”でのYAMLファイル(一部)
    9
    PHPerKaigi 2022
    provider:
    name: aws
    region: us-east-1
    runtime: provided.al2
    plugins:
    - ./vendor/bref/bref
    functions:
    api:
    handler: index.php
    layers:
    - ${bref:layer.php-81-fpm}
    events:
    - httpApi: '*'

    View Slide

  10. serverless deployでデプロイ
    10
    PHPerKaigi 2022
    $ serverless deploy
    Deploying app to stage dev (us-east-1)
    ✔ Service deployed to stack app-dev (98s)
    endpoint: ANY -
    https://xx.execute-api.us-east-1.amazonaws.com
    functions:
    api: app-dev-api (1.4 MB)

    View Slide

  11. ● Serverless Frameworkで
    AWS LambdaにPHPアプリをデプロイする
    ● PHPで
    Stripeの決済フォームを実装する
    ● AWS Secrets Managerで
    Stripe APIキーを安全に管理する
    ● Webhook APIを実装・デプロイする
    ● 定期課金を実装する際のTips
    Agenda
    PHPerKaigi 2022

    View Slide

  12. ライブラリのインストールなどもいつも通り
    12
    PHPerKaigi 2022
    % composer require stripe/stripe-php
    require_once __DIR__.'/vendor/autoload.php';
    $stripe = new \Stripe\StripeClient([
    'api_key' => $_ENV['STRIPE_SECRET_API_KEY'],
    'stripe_version' => '2020-08-27',
    ]);
    % composer require stripe/stripe-php

    View Slide

  13. 環境変数は.env -> serverless.ymlで設定
    13
    PHPerKaigi 2022
    % composer require stripe/stripe-php
    useDotenv: true

    functions:
    api:

    environment:
    STRIPE_SECRET_API_KEY:
    ${env:STRIPE_SECRET_API_KEY}
    STRIPE_PUBLISHABLE_API_KEY:
    ${env:STRIPE_PUBLISHABLE_API_KEY}
    STRIPE_SECRET_API_KEY=sk_test_xxx
    STRIPE_PUBLISHABLE_API_KEY=pk_test_xxx
    .env
    serverless.yml

    View Slide

  14. PaymentIntentをPHPで作成
    14
    PHPerKaigi 2022
    require_once __DIR__.'/vendor/autoload.php';
    $stripe = new \Stripe\StripeClient([
    'api_key' => $_ENV['STRIPE_SECRET_API_KEY'],
    'stripe_version' => '2020-08-27',
    ]);
    $paymentIntent = $stripe->paymentIntents->create([
    'payment_method_types' => ['card', ‘konbini’],
    'amount' => 1009,
    'currency' => 'jpy',
    ]);
    ?>

    View Slide

  15. Stripe.jsで決済フォームを描画
    15
    PHPerKaigi 2022

    <br/>document.addEventListener('DOMContentLoaded', async () => {<br/>const stripe = Stripe('<?php echo $_ENV["STRIPE_PUBLISHABLE_API_KEY"]; ?>', {<br/>apiVersion: '2020-08-27',<br/>});<br/>const elements = stripe.elements({<br/>clientSecret: "<?php echo $paymentIntent->client_secret; ?>"<br/>});<br/>const paymentElement = elements.create("payment");<br/>paymentElement.mount('#card-element');<br/>});<br/>

    View Slide

  16. 複数決済方法に対応した
    決済フォームが完成
    PHPerKaigi 2022

    View Slide

  17. ● Serverless Frameworkで
    AWS LambdaにPHPアプリをデプロイする
    ● PHPで
    Stripeの決済フォームを実装する
    ● AWS Secrets Managerで
    Stripe APIキーを安全に管理する
    ● Webhook APIを実装・デプロイする
    ● 定期課金を実装する際のTips
    Agenda
    PHPerKaigi 2022

    View Slide

  18. StripeのシークレットAPIキーを安全に管理する
    ● シークレットAPIキーは、顧客情報や決済履歴などの重要なデータにアクセスできる
    ● より安全に管理するために、
    AWSのSecrets Managerを利用
    ● Secrets Managerで集約管理することで、退職者の開発
    PCにキーが残るリスクもなくせる
    ● コストが気になる場合は、
    Systems Managerまたは制限付きAPIキーの代用も検討可能
    18
    PHPerKaigi 2022

    View Slide

  19. AWS SDK(PHP)でシークレットAPIキーを取得する
    19
    PHPerKaigi 2022
    use Aws\SecretsManager\SecretsManagerClient;
    $client = new SecretsManagerClient([
    'profile' => 'default',
    'version' => '2017-10-17',
    ]);
    $result = $client->getSecretValue([
    'SecretId' => '<<{{MySecretName}}>>',
    ]);
    if (isset($result['SecretString'])) {
    $secret = $result['SecretString'];
    } else {
    $secret = base64_decode($result['SecretBinary']);
    }
    $stripe = new \Stripe\StripeClient([
    'api_key' => $secret['STRIPE_SECRET_API_KEY'],
    'stripe_version' => '2020-08-27',
    ]);

    View Slide

  20. ● Serverless Frameworkで
    AWS LambdaにPHPアプリをデプロイする
    ● PHPで
    Stripeの決済フォームを実装する
    ● AWS Secrets Managerで
    Stripe APIキーを安全に管理する
    ● Webhook APIを実装・デプロイする
    ● 定期課金を実装する際のTips
    Agenda
    PHPerKaigi 2022

    View Slide

  21. コンビニなど、
    即時決済でない支払いも
    Stripeではサポート
    21
    PHPerKaigi 2022

    View Slide

  22. 決済イベントを
    Webhookで処理
    PHPerKaigi 2022
    https://stripe.com/docs/webhooks/quickstart

    View Slide

  23. serverless.ymlでWebhook用のリソースを追加
    23
    PHPerKaigi 2022
    functions:
    webhook:
    handler: webhook.php
    description: ''
    timeout: 28
    layers:
    - ${bref:layer.php-81-fpm}
    events:
    - httpApi:
    method: POST
    path: '/webhook'

    View Slide

  24. JSONを返すAPIを実装
    24
    PHPerKaigi 2022
    // リクエストBody
    $payload = @file_get_contents('php://input');
    // Lambdaのコンテキスト(trace idなど)
    $lambdaContext = json_decode($_SERVER['LAMBDA_INVOCATION_CONTEXT'], true);
    // Lambdaのリクエストコンテキスト(Lambdaのstageなど)
    $requestContext = json_decode($_SERVER['LAMBDA_REQUEST_CONTEXT'], true);
    header('Content-Type: application/json; charset=UTF-8');
    print json_encode([
    ‘message' => ‘OK',
    ], JSON_PRETTY_PRINT);

    View Slide

  25. Stripeからのリクエストであることを検証する処理の例
    25
    PHPerKaigi 2022
    $endpoint_secret = 'whsec_...';
    $payload = @file_get_contents('php://input');
    $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();
    }

    View Slide

  26. 実装の詳細は
    Stripe docs参照
    PHPerKaigi 2022
    https://stripe.com/docs/webhooks/quickstart

    View Slide

  27. ● Serverless Frameworkで
    AWS LambdaにPHPアプリをデプロイする
    ● PHPで
    Stripeの決済フォームを実装する
    ● AWS Secrets Managerで
    Stripe APIキーを安全に管理する
    ● Webhook APIを実装・デプロイする
    ● 定期課金を実装する際の Tips
    Agenda
    PHPerKaigi 2022

    View Slide

  28. 契約と決済情報登録が同時の場合、
    先にSubscriptionを作成する
    28
    PHPerKaigi 2022
    % composer require stripe/stripe-php
    const stripe = Stripe(
     '', {
    'apiVersion: '2020-08-27',
    });
    const elements = stripe.elements({
    clientSecret: "$subscription->latest_invoice->payment_intent->client_secret; ?>"
    });
    const paymentElement = elements.create("payment");
    paymentElement.mount('#card-element');
    $subscription = $stripe->subscriptions->create([
    'customer' => $customer_id,
    'items' => [[
    'price' => $price_id,
    ]],
    'payment_behavior' => 'default_incomplete',
    'expand' => ['latest_invoice.payment_intent'],
    ]);
    Subscriptionを作成して・・・
    決済フォームにclient secretを渡す

    View Slide

  29. Webhookを利用して、支払い方法をサブスクに設定
    29
    PHPerKaigi 2022
    if ($object['billing_reason'] == 'subscription_create') {
    $subscription_id = $object['subscription'];
    $payment_intent_id = $object['payment_intent'];
    # Retrieve the payment intent used to pay the subscription
    $payment_intent = $stripe->paymentIntents->retrieve(
    $payment_intent_id,
    []
    );
    $stripe->subscriptions->update(
    $subscription_id,
    ['default_payment_method' => $payment_intent->payment_method],
    );
    };

    View Slide

  30. 実装を簡略化したい場合、Checkoutでローコードに
    30
    PHPerKaigi 2022
    $priceId = '{{PRICE_ID}}';
    $session = \Stripe\Checkout\Session::create([
    'success_url' => 'https://example.com/success.html?session_id={CHECKOUT_SESSION_ID}',
    'cancel_url' => 'https://example.com/canceled.html',
    'mode' => 'subscription',
    'line_items' => [[
    'price' => $priceId,
    'quantity' => 1,
    ]],
    ]);
    header("HTTP/1.1 303 See Other");
    header("Location: " . $session->url);

    View Slide

  31. もしくはSetupIntentで先にカード情報を登録する
    ステップを用意する
    31
    PHPerKaigi 2022
    % composer require stripe/stripe-php
    const stripe = Stripe(
     '', {
    'apiVersion: '2020-08-27',
    });
    const elements = stripe.elements({
    clientSecret: "$setupIntent->client_secret; ?>"
    });
    const paymentElement = elements.create("payment");
    paymentElement.mount('#card-element');
    $setupIntent =
    $stripe->setupIntents->create([
    'customer' => $customerId,
    ]);
    SetupIntentを作成して・・・
    決済フォームにclient secretを渡す

    View Slide

  32. まとめ
    ● Serverless Framework + BrefでPHPアプリをAWSにデプロイできる
    ● Stripeなど、composerを使った組み込みも可能
    ● Stripeを利用する場合、WebPage(HTML)だけでなくREST APIも作ろう
    ● ローカルでのテストや、画像などの最適化は
    Brefのドキュメントをチェック
    ● LaravelやSymfonyのデプロイにもBrefは対応
    ● AWS + Stripeで、より手軽により少ないメンテナンス工数で
    アプリケーション・サービスをリリースしよう
    32
    PHPerKaigi 2022

    View Slide