AWS で Stripe Webhook を受信してみる
最近、仕事で Stripe の Webhook 受信環境を作成したいと思い、試しに Amazon API Gateway + AWS Lambda で作成してみました。
Stripe はクレジットカード全般の処理を SaaS 的に準備してくれているサービスで、何かしらWebサービスにクレジット決済の機能を組み込もうと思った時に便利です。
今回はその中でも、Webhook を利用したイベント連携の機能を試してみました。
例えば、「顧客が解約したタイミングで何か連携したい」といった特定イベントから Webhook で自社のサービスに連携することが可能な機能です。
目次
AWS側の設定 | AWS で Stripe Webhook を受信してみる
AWS Cloud Development Kit (AWS CDK) で作成しています。サンプルソースは下記です。
カスタムドメイン名で受信するようにしています。
bin 配下
import { StripeEventsStack } from '../lib/StripeEvents';
new StripeEventsStack(app, 'StripeEventsStack', {
env: { region: 'ap-northeast-1', account: 'xxxxxx' },
environment: environment,
domainNameHost: `stripe-${environment}`,
domainNameBase: `xxxxx.com`,
hostedZone: 'Z1Qxxxxxxxx',
});
lib 配下
import * as path from 'path';
import { Construct } from 'constructs';
import {
Stack,
StackProps,
aws_lambda as lambda,
aws_lambda_nodejs as lambdaNodejs,
aws_apigateway as apigateway,
aws_certificatemanager as acm,
aws_route53 as route53,
aws_route53_targets as route53Targets,
Duration,
} from 'aws-cdk-lib';
type Props = {
environment: string;
domainNameHost: string;
domainNameBase: string;
hostedZone: string;
} & StackProps;
export class StripeEventsStack extends Stack {
constructor(scope: Construct, id: string, props: Props) {
super(scope, id, props);
const { environment, domainNameHost, domainNameBase, hostedZone } = props;
const lambdaStripeEvents = new lambdaNodejs.NodejsFunction(this, 'lambda', {
runtime: lambda.Runtime.NODEJS_14_X,
entry: path.join(__dirname, '../lambda/stripeEvents/handler.ts'),
handler: 'handler',
environment: {},
timeout: Duration.seconds(300),
});
const stripeEventApi = new apigateway.LambdaRestApi(
this,
'stripeEventApi',
{
handler: lambdaStripeEvents,
proxy: false,
deployOptions: {
loggingLevel: apigateway.MethodLoggingLevel.INFO,
},
defaultMethodOptions: {
authorizationType: apigateway.AuthorizationType.NONE,
},
domainName: {
domainName: `${domainNameHost}.${domainNameBase}`,
certificate: new acm.Certificate(this, 'Certificate', {
domainName: `*.${domainNameBase}`,
validation: acm.CertificateValidation.fromDns(),
}),
},
}
);
const stripeWebhook = stripeEventApi.root.addResource('webhook');
stripeWebhook.addMethod('POST');
const zone = route53.HostedZone.fromLookup(this, 'baseZone', {
domainName: domainNameBase,
});
new route53.ARecord(this, 'ARecod', {
zone: zone,
recordName: domainNameHost,
target: route53.RecordTarget.fromAlias(
new route53Targets.ApiGateway(stripeEventApi)
),
});
}
}
Lambda ソース
export const handler = async (event: any) => {
console.log('event:', event);
return {
isBase64Encoded: false,
statusCode: 200,
headers: { 'test-header-res-key': 'test-header-res-Value' },
body: '...',
};
};
※ Stripe のベストプラクティスを見ますと、Stripe 側の署名を検証した方が良いので、あくまでサンプル出力とお考えください。
Stripe 上で設定 | AWS で Stripe Webhook を受信してみる
Stripe でエンドポイントを登録し、受信イベントをAPIレベルで選択します。
今回は Stripe のテスト環境で試してみました。
まずは、上記 AWS で作成したエンドポイントのURLを作成します。
「 xxxxx.com/webhook 」のようになります。
次に連携するイベントを選択します。
例えば Stripe 上で、サブスクの新しい購入者が発生したことを表す
「customer.subscription.created」を選択してみます。
上記を設定して、テストで適当な customer にサブスクリプションを紐づけてみましょう。
クレジットカードは適当なテスト用の番号で大丈夫です。
参考) https://stripe.com/docs/testing
結果確認 | AWS で Stripe Webhook を受信してみる
実行すると、Stripe の 開発者 > Webhook の画面から、成功/失敗が確認できます。
下記のような形で、Lambda 側でも
event の body で Stripe の subscription が下記の形で取得できてることを確認できました。
{
"id": "evt_xxxxxx",
"object": "event",
"api_version": "2020-08-27",
"created": 1643362950,
"data": {
"object": {
"id": "sub_xxxxxx",
"object": "subscription",
"application_fee_percent": null,
"automatic_tax": {
"enabled": false
},
"billing_cycle_anchor": 1643362948,
"billing_thresholds": null,
"cancel_at": null,
"cancel_at_period_end": false,
"canceled_at": null,
"collection_method": "charge_automatically",
"created": 1643362948,
"current_period_end": 1646041348,
"current_period_start": 1643362948,
"customer": "cus_xxxxxx",
"days_until_due": null,
"default_payment_method": null,
"default_source": null,
"default_tax_rates": [],
"discount": null,
"ended_at": null,
"items": {
"object": "list",
"data": [
{
"id": "si_xxxxxx",
"object": "subscription_item",
"billing_thresholds": null,
"created": 1643362948,
"metadata": {},
"plan": {
"id": "price_xxxxxx",
"object": "plan",
"active": true,
"aggregate_usage": null,
"amount": 2000,
"amount_decimal": "2000",
"billing_scheme": "per_unit",
"created": 1642496340,
"currency": "jpy",
"interval": "month",
"interval_count": 1,
"livemode": false,
"metadata": {},
"nickname": null,
"product": "prod_xxxxxx",
"tiers_mode": null,
"transform_usage": null,
"trial_period_days": null,
"usage_type": "licensed"
},
"price": {
"id": "price_xxxxxx",
"object": "price",
"active": true,
"billing_scheme": "per_unit",
"created": 1642496340,
"currency": "jpy",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"product": "prod_xxxxxx",
"recurring": {
"aggregate_usage": null,
"interval": "month",
"interval_count": 1,
"trial_period_days": null,
"usage_type": "licensed"
},
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "recurring",
"unit_amount": 2000,
"unit_amount_decimal": "2000"
},
"quantity": 1,
"subscription": "sub_xxxxxxxxxxxxxxxxxx",
"tax_rates": []
}
],
"has_more": false,
"total_count": 1,
"url": "/v1/subscription_items?subscription=sub_xxxxxx"
},
"latest_invoice": "in_xxxxxx",
"livemode": false,
"metadata": {},
"next_pending_invoice_item_invoice": null,
"pause_collection": null,
"payment_settings": {
"payment_method_options": null,
"payment_method_types": null
},
"pending_invoice_item_interval": null,
"pending_setup_intent": null,
"pending_update": null,
"plan": {
"id": "xxxxxx",
"object": "plan",
"active": true,
"aggregate_usage": null,
"amount": 2000,
"amount_decimal": "2000",
"billing_scheme": "per_unit",
"created": 1642496340,
"currency": "jpy",
"interval": "month",
"interval_count": 1,
"livemode": false,
"metadata": {},
"nickname": null,
"product": "xxxxxx",
"tiers_mode": null,
"transform_usage": null,
"trial_period_days": null,
"usage_type": "licensed"
},
"quantity": 1,
"schedule": null,
"start_date": 1643362948,
"status": "active",
"transfer_data": null,
"trial_end": null,
"trial_start": null
}
},
"livemode": false,
"pending_webhooks": 1,
"request": {
"id": "xxxxxx",
"idempotency_key": "xxxxxx"
},
"type": "customer.subscription.created"
}
最後に ─
いかがでしたでしょうか。
Stripe は便利で多機能なので、色々と使えそうです。
最後までお読みいただきありがとうございました。
元記事発行日: 2022年06月14日、最終更新日: 2024年02月28日