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

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

    Secrets Managerで Stripe APIキーを安全に管理する • Webhook APIを実装・デプロイする • 定期課金を実装する際のTips Agenda PHPerKaigi 2022
  4. Serverless Framework + brefで手軽にPHP環境 • AWS Lambda向けの Serverless Frameworkプラグイン •

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

    Secrets Managerで Stripe APIキーを安全に管理する • Webhook APIを実装・デプロイする • 定期課金を実装する際のTips Agenda PHPerKaigi 2022
  9. ライブラリのインストールなどもいつも通り 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
  10. 環境変数は.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
  11. 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', ]); ?>
  12. 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>
  13. • Serverless Frameworkで AWS LambdaにPHPアプリをデプロイする • PHPで Stripeの決済フォームを実装する • AWS

    Secrets Managerで Stripe APIキーを安全に管理する • Webhook APIを実装・デプロイする • 定期課金を実装する際のTips Agenda PHPerKaigi 2022
  14. 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', ]);
  15. • Serverless Frameworkで AWS LambdaにPHPアプリをデプロイする • PHPで Stripeの決済フォームを実装する • AWS

    Secrets Managerで Stripe APIキーを安全に管理する • Webhook APIを実装・デプロイする • 定期課金を実装する際のTips Agenda PHPerKaigi 2022
  16. 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'
  17. 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);
  18. 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(); }
  19. • Serverless Frameworkで AWS LambdaにPHPアプリをデプロイする • PHPで Stripeの決済フォームを実装する • AWS

    Secrets Managerで Stripe APIキーを安全に管理する • Webhook APIを実装・デプロイする • 定期課金を実装する際の Tips Agenda PHPerKaigi 2022
  20. 契約と決済情報登録が同時の場合、 先に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を渡す
  21. 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], ); };
  22. 実装を簡略化したい場合、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);
  23. もしくは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を渡す
  24. まとめ • Serverless Framework + BrefでPHPアプリをAWSにデプロイできる • Stripeなど、composerを使った組み込みも可能 • Stripeを利用する場合、WebPage(HTML)だけでなくREST

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