Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

  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
  3. • Serverless Frameworkで AWS LambdaにPHPアプリをデプロイする • PHPで Stripeの決済フォームを実装する • AWS

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

    Secrets Managerで Stripe APIキーを安全に管理する • Webhook APIを実装・デプロイする • 定期課金を実装する際のTips Agenda PHPerKaigi 2022
  5. AWS LambdaでPHPアプリを実行する • デフォルトのランタイムにPHPは存在しない • 「カスタムランタイム」を作成し、 Lambdaにアップロードする • カスタムランタイムは、DockerfileまたはLambda Layerで設定可能

    • 自作または公開されている Layerを利用するスタイルになる 5 PHPerKaigi 2022
  6. 自作する場合の Dockerfileの例 PHPerKaigi 2022 From: https://aws.amazon.com/jp/builders-flash/202106/new-lamb da-container-development-3/?awsf.filter-name=*all

  7. Serverless Framework + brefで手軽にPHP環境 • AWS Lambda向けの Serverless Frameworkプラグイン •

    PHPアプリを 簡単(Bref)にデプロイ・実行できる • Composerを利用して インストール • YAMLや PHPのテンプレートも提供 7 PHPerKaigi 2022 #phperkaigi https://bref.sh/
  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.
  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: '*' …
  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)
  11. • Serverless Frameworkで AWS LambdaにPHPアプリをデプロイする • PHPで Stripeの決済フォームを実装する • AWS

    Secrets Managerで Stripe APIキーを安全に管理する • Webhook APIを実装・デプロイする • 定期課金を実装する際のTips Agenda PHPerKaigi 2022
  12. ライブラリのインストールなどもいつも通り 12 PHPerKaigi 2022 % composer require stripe/stripe-php <?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
  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
  14. PaymentIntentをPHPで作成 14 PHPerKaigi 2022 <?php 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', ]); ?>
  15. Stripe.jsで決済フォームを描画 15 PHPerKaigi 2022 <div id="card-element"></div> <script> document.addEventListener('DOMContentLoaded', async ()

    => { const stripe = Stripe('<?php echo $_ENV["STRIPE_PUBLISHABLE_API_KEY"]; ?>', { apiVersion: '2020-08-27', }); const elements = stripe.elements({ clientSecret: "<?php echo $paymentIntent->client_secret; ?>" }); const paymentElement = elements.create("payment"); paymentElement.mount('#card-element'); }); </script> <script src="https://js.stripe.com/v3/" defer></script>
  16. 複数決済方法に対応した 決済フォームが完成 PHPerKaigi 2022

  17. • Serverless Frameworkで AWS LambdaにPHPアプリをデプロイする • PHPで Stripeの決済フォームを実装する • AWS

    Secrets Managerで Stripe APIキーを安全に管理する • Webhook APIを実装・デプロイする • 定期課金を実装する際のTips Agenda PHPerKaigi 2022
  18. StripeのシークレットAPIキーを安全に管理する • シークレットAPIキーは、顧客情報や決済履歴などの重要なデータにアクセスできる • より安全に管理するために、 AWSのSecrets Managerを利用 • Secrets Managerで集約管理することで、退職者の開発

    PCにキーが残るリスクもなくせる • コストが気になる場合は、 Systems Managerまたは制限付きAPIキーの代用も検討可能 18 PHPerKaigi 2022
  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', ]);
  20. • Serverless Frameworkで AWS LambdaにPHPアプリをデプロイする • PHPで Stripeの決済フォームを実装する • AWS

    Secrets Managerで Stripe APIキーを安全に管理する • Webhook APIを実装・デプロイする • 定期課金を実装する際のTips Agenda PHPerKaigi 2022
  21. コンビニなど、 即時決済でない支払いも Stripeではサポート 21 PHPerKaigi 2022

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

  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'
  24. JSONを返すAPIを実装 24 PHPerKaigi 2022 <?php // リクエスト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);
  25. Stripeからのリクエストであることを検証する処理の例 25 PHPerKaigi 2022 <?php $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(); }
  26. 実装の詳細は Stripe docs参照 PHPerKaigi 2022 https://stripe.com/docs/webhooks/quickstart

  27. • Serverless Frameworkで AWS LambdaにPHPアプリをデプロイする • PHPで Stripeの決済フォームを実装する • AWS

    Secrets Managerで Stripe APIキーを安全に管理する • Webhook APIを実装・デプロイする • 定期課金を実装する際の Tips Agenda PHPerKaigi 2022
  28. 契約と決済情報登録が同時の場合、 先にSubscriptionを作成する 28 PHPerKaigi 2022 % composer require stripe/stripe-php const

    stripe = Stripe(  '<?php echo $_ENV["STRIPE_PUBLISHABLE_API_KEY"]; ?>', { 'apiVersion: '2020-08-27', }); const elements = stripe.elements({ clientSecret: "<?php echo $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を渡す
  29. Webhookを利用して、支払い方法をサブスクに設定 29 PHPerKaigi 2022 <?php 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], ); };
  30. 実装を簡略化したい場合、Checkoutでローコードに 30 PHPerKaigi 2022 <?php $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);
  31. もしくはSetupIntentで先にカード情報を登録する ステップを用意する 31 PHPerKaigi 2022 % composer require stripe/stripe-php const

    stripe = Stripe(  '<?php echo $_ENV["STRIPE_PUBLISHABLE_API_KEY"]; ?>', { 'apiVersion: '2020-08-27', }); const elements = stripe.elements({ clientSecret: "<?php echo $setupIntent->client_secret; ?>" }); const paymentElement = elements.create("payment"); paymentElement.mount('#card-element'); $setupIntent = $stripe->setupIntents->create([ 'customer' => $customerId, ]); SetupIntentを作成して・・・ 決済フォームにclient secretを渡す
  32. まとめ • Serverless Framework + BrefでPHPアプリをAWSにデプロイできる • Stripeなど、composerを使った組み込みも可能 • Stripeを利用する場合、WebPage(HTML)だけでなくREST

    APIも作ろう • ローカルでのテストや、画像などの最適化は Brefのドキュメントをチェック • LaravelやSymfonyのデプロイにもBrefは対応 • AWS + Stripeで、より手軽により少ないメンテナンス工数で アプリケーション・サービスをリリースしよう 32 PHPerKaigi 2022