Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9328

Stripeによる署名検証をFirebase Cloud Functionsを利用してローカルで行う

$
0
0
Motivation 最近流行の決済サービスであるStripeであるが、決済処理時のエンドポイントに送信するWebhookイベントのヘッダに署名を含めることができる。これによりそれがサードパーティでなくStripeによって送信されたものであるかどうかを検証することができる。 そこで、本記事ではこの署名検証処理をローカルで手軽にテストできるようにすることを目的とする。 Prerequisite テスト環境 OS: macOS node: v14.17.3 Firebase Cloud Functions プロジェクトの追加やCLIでのログインは省略 詳しくはこちら:https://firebase.google.com/docs/functions/get-started Cloud Functionsの実装 index.ts import * as express from "express"; import * as functions from "firebase-functions"; import { https } from "firebase-functions"; import Stripe from "stripe"; // 環境変数 const SECRET_KEY = "sk_xxxx"; // APIキー const ENDPOINT_SECRET = "whsec_XXXX"; // 署名シークレット。後述のStripe CLIによるサーバー起動時に表示されるシークレットを指定する。 const app = express(); // webhook app.post("/webhook-stripe/", async (req, res) => { const signature = req.headers["stripe-signature"];// ここに署名が入っている if (!signature) { throw new Error("no avalilable signature header"); } try { const stripe = new Stripe(SECRET_KEY, { apiVersion: "2020-08-27", }); // 署名検証 const firebaseRequest = req as https.Request; const event = stripe.webhooks.constructEvent(firebaseRequest.rawBody, signature, ENDPOINT_SECRET); res.status(200).send(); } catch (e) { console.log(e); res.status(500).send(`Verification Error: ${e.message}`); } }); export const verifySignature = functions.https.onRequest(app); ハマったポイント stripe.webhooks.constructEvent()の第一引数に渡すpayloadは、stripeからのraw request body(Buffer)を渡さなければいけないが、通常の方法だとなぜか上手くいかず「No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?」のエラーが出た。 原因:Cloud Functions + Expressの環境では、requestがデフォルトでjsonパースされているみたいで、どうしてもraw bodyが取得できなかった 解決法: import { https } from "firebase-functions"; ... const firebaseRequest = req as https.Request; // -> firebaseRequest.rawBodyをstripe.webhooks.constructEventのpayloadで渡すようにする ローカルでエミュレート $ firebase emulators:start これにより、http://localhost:5001/{projectId}/{region}/{functionName}/{functionPath}/ で署名検証用のCloud Functionsが立ち上がる。このURLはwebhook URLとして、後述のStripe CLIでサーバーを起動するときに指定する。 ここで、functionNameはverifySignature、functionPathはwebhook-stripe/である。 region, projectIdはご自身のFirebaseに作成したプロジェクトのリージョン及びプロジェクトIDを参照すること。 Stripe アカウント作成 省略 CLIのインストール $ brew install stripe/stripe-cli/stripe ログイン $ stripe login # ブラウザに遷移するので、画面に従い認証を行う CLIによるサーバー起動 $ stripe listen --forward-to http://localhost:5001/{projectId}/us-central1/verifySignature/webhook-stripe/ # 署名シークレットが表示されるため、これで index.ts 内で指定するものを更新し、再度firebase emulators:start で起動し直す Test & Result リクエストの形式 $ stripe trigger <EVENT_NAME> に入れるべきものは、以下で確認可能 $ stripe trigger --help ... Supported events: account.updated balance.available charge.captured ... 実際にstripeに何らかのリクエストを投げる $ stripe trigger balance.available ... functions: Beginning execution of "us-central1-verifySignature" { id: 'evt_1JGOSGFZkFlFEY6jnM5qUSGL', object: 'event', api_version: '2020-08-27', created: 1627047019, data: { object: { id: 'pi_1JGOSEFZkFlFEY6jH4vzdVHS', object: 'payment_intent', amount: 2000, amount_capturable: 0, amount_received: 2000, application: null, application_fee_amount: null, canceled_at: null, cancellation_reason: null, capture_method: 'automatic', charges: [Object], client_secret: 'pi_1JGOSEFZkFlFEY6jH4vzdVHS_secret_oIsYsVEmk91lO3D8RERkUU0JO', confirmation_method: 'automatic', created: 1627047018, currency: 'usd', customer: null, description: '(created by Stripe CLI)', invoice: null, last_payment_error: null, livemode: false, metadata: {}, next_action: null, on_behalf_of: null, payment_method: 'pm_1JGOSEFZkFlFEY6jre53Hhu9', payment_method_options: [Object], payment_method_types: [Array], receipt_email: null, review: null, setup_future_usage: null, shipping: [Object], source: null, statement_descriptor: null, statement_descriptor_suffix: null, status: 'succeeded', transfer_data: null, transfer_group: null } }, livemode: false, pending_webhooks: 3, request: { id: 'req_f1Qn79RYEzu0Lm', idempotency_key: null }, type: 'payment_intent.succeeded' } i functions: Beginning execution of "us-central1-verifySignature" i functions: Beginning execution of "us-central1-verifySignature" { id: 'evt_1JGOSGFZkFlFEY6j57JY0AMQ', object: 'event', api_version: '2020-08-27', created: 1627047019, data: { object: { id: 'ch_1JGOSFFZkFlFEY6jjreb2LGJ', object: 'charge', amount: 2000, amount_captured: 2000, amount_refunded: 0, application: null, application_fee: null, application_fee_amount: null, balance_transaction: 'txn_1JGOSFFZkFlFEY6jXw83a3Wy', billing_details: [Object], calculated_statement_descriptor: 'Stripe', captured: true, created: 1627047019, currency: 'usd', customer: null, description: '(created by Stripe CLI)', destination: null, dispute: null, disputed: false, failure_code: null, failure_message: null, fraud_details: {}, invoice: null, livemode: false, metadata: {}, on_behalf_of: null, order: null, outcome: [Object], paid: true, payment_intent: 'pi_1JGOSEFZkFlFEY6jH4vzdVHS', payment_method: 'pm_1JGOSEFZkFlFEY6jre53Hhu9', payment_method_details: [Object], receipt_email: null, receipt_number: null, receipt_url: 'https://pay.stripe.com/receipts/acct_1JExhyFZkFlFEY6j/ch_1JGOSFFZkFlFEY6jjreb2LGJ/rcpt_JuCrnUA8LSppUxCxBQ58XX5CbdP0igP', refunded: false, refunds: [Object], review: null, shipping: [Object], source: null, source_transfer: null, statement_descriptor: null, statement_descriptor_suffix: null, status: 'succeeded', transfer_data: null, transfer_group: null } }, livemode: false, pending_webhooks: 3, request: { id: 'req_f1Qn79RYEzu0Lm', idempotency_key: null }, type: 'charge.succeeded' } { id: 'evt_1JGOSGFZkFlFEY6jXdUgwE4B', object: 'event', api_version: '2020-08-27', created: 1627047018, data: { object: { id: 'pi_1JGOSEFZkFlFEY6jH4vzdVHS', object: 'payment_intent', amount: 2000, amount_capturable: 0, amount_received: 0, application: null, application_fee_amount: null, canceled_at: null, cancellation_reason: null, capture_method: 'automatic', charges: [Object], client_secret: 'pi_1JGOSEFZkFlFEY6jH4vzdVHS_secret_oIsYsVEmk91lO3D8RERkUU0JO', confirmation_method: 'automatic', created: 1627047018, currency: 'usd', customer: null, description: '(created by Stripe CLI)', invoice: null, last_payment_error: null, livemode: false, metadata: {}, next_action: null, on_behalf_of: null, payment_method: null, payment_method_options: [Object], payment_method_types: [Array], receipt_email: null, review: null, setup_future_usage: null, shipping: [Object], source: null, statement_descriptor: null, statement_descriptor_suffix: null, status: 'requires_payment_method', transfer_data: null, transfer_group: null } }, livemode: false, pending_webhooks: 3, request: { id: 'req_f1Qn79RYEzu0Lm', idempotency_key: null }, type: 'payment_intent.created' } i functions: Finished "us-central1-verifySignature" in ~1s i functions: Finished "us-central1-verifySignature" in ~1s i functions: Finished "us-central1-verifySignature" in ~1s i functions: Beginning execution of "us-central1-verifySignature" { id: 'evt_1JGOSGFZkFlFEY6jPyo2vocQ', object: 'event', api_version: '2020-08-27', created: 1627047020, data: { object: { object: 'balance', available: [Array], livemode: false, pending: [Array] } }, livemode: false, pending_webhooks: 3, request: { id: null, idempotency_key: null }, type: 'balance.available' } すなわち、署名検証を行なった結果、Stripeからのリクエストであることが証明された。 Couclusion Firebase Cloud Functionsのローカルエミュレーションを用いて、Stripeの署名検証テストを行うことができた。

Viewing all articles
Browse latest Browse all 9328

Trending Articles