@mathrunet/masamune_cloudflare_purchase_stripe 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -0
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/functions.d.ts +44 -0
- package/dist/functions.js +47 -0
- package/dist/functions.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/meter/d1_usage_event_store.d.ts +128 -0
- package/dist/lib/meter/d1_usage_event_store.js +78 -0
- package/dist/lib/meter/d1_usage_event_store.js.map +1 -0
- package/dist/lib/meter/flush_meter.d.ts +19 -0
- package/dist/lib/meter/flush_meter.js +38 -0
- package/dist/lib/meter/flush_meter.js.map +1 -0
- package/dist/lib/meter/interface.d.ts +109 -0
- package/dist/lib/meter/interface.js +3 -0
- package/dist/lib/meter/interface.js.map +1 -0
- package/dist/lib/meter/kv_usage_buffer.d.ts +56 -0
- package/dist/lib/meter/kv_usage_buffer.js +50 -0
- package/dist/lib/meter/kv_usage_buffer.js.map +1 -0
- package/dist/lib/meter/record_usage.d.ts +29 -0
- package/dist/lib/meter/record_usage.js +72 -0
- package/dist/lib/meter/record_usage.js.map +1 -0
- package/dist/lib/meter/stripe_meter_client.d.ts +76 -0
- package/dist/lib/meter/stripe_meter_client.js +74 -0
- package/dist/lib/meter/stripe_meter_client.js.map +1 -0
- package/dist/lib/options.d.ts +179 -0
- package/dist/lib/options.js +132 -0
- package/dist/lib/options.js.map +1 -0
- package/dist/lib/purchase/d1_purchase_store.d.ts +78 -0
- package/dist/lib/purchase/d1_purchase_store.js +194 -0
- package/dist/lib/purchase/d1_purchase_store.js.map +1 -0
- package/dist/lib/purchase/helpers.d.ts +36 -0
- package/dist/lib/purchase/helpers.js +64 -0
- package/dist/lib/purchase/helpers.js.map +1 -0
- package/dist/lib/purchase/interface.d.ts +182 -0
- package/dist/lib/purchase/interface.js +27 -0
- package/dist/lib/purchase/interface.js.map +1 -0
- package/dist/lib/purchase/sync_payment.d.ts +15 -0
- package/dist/lib/purchase/sync_payment.js +79 -0
- package/dist/lib/purchase/sync_payment.js.map +1 -0
- package/dist/lib/stripe_client.d.ts +55 -0
- package/dist/lib/stripe_client.js +50 -0
- package/dist/lib/stripe_client.js.map +1 -0
- package/dist/meter.d.ts +21 -0
- package/dist/meter.js +38 -0
- package/dist/meter.js.map +1 -0
- package/dist/purchase.d.ts +58 -0
- package/dist/purchase.js +48 -0
- package/dist/purchase.js.map +1 -0
- package/dist/workers/stripe.d.ts +1 -0
- package/dist/workers/stripe.js +861 -0
- package/dist/workers/stripe.js.map +1 -0
- package/dist/workers/stripe_webhook.d.ts +1 -0
- package/dist/workers/stripe_webhook.js +384 -0
- package/dist/workers/stripe_webhook.js.map +1 -0
- package/dist/workers/stripe_webhook_connect.d.ts +1 -0
- package/dist/workers/stripe_webhook_connect.js +97 -0
- package/dist/workers/stripe_webhook_connect.js.map +1 -0
- package/dist/workers/stripe_webhook_secure.d.ts +1 -0
- package/dist/workers/stripe_webhook_secure.js +82 -0
- package/dist/workers/stripe_webhook_secure.js.map +1 -0
- package/jest.config.json +19 -0
- package/package.json +56 -0
- package/src/functions.ts +48 -0
- package/src/index.ts +25 -0
- package/src/lib/meter/d1_usage_event_store.ts +176 -0
- package/src/lib/meter/flush_meter.ts +45 -0
- package/src/lib/meter/interface.ts +118 -0
- package/src/lib/meter/kv_usage_buffer.ts +79 -0
- package/src/lib/meter/record_usage.ts +87 -0
- package/src/lib/meter/stripe_meter_client.ts +117 -0
- package/src/lib/options.ts +299 -0
- package/src/lib/purchase/d1_purchase_store.ts +268 -0
- package/src/lib/purchase/helpers.ts +77 -0
- package/src/lib/purchase/interface.ts +215 -0
- package/src/lib/purchase/sync_payment.ts +95 -0
- package/src/lib/stripe_client.ts +76 -0
- package/src/meter.ts +21 -0
- package/src/purchase.ts +66 -0
- package/src/workers/stripe.ts +881 -0
- package/src/workers/stripe_webhook.ts +414 -0
- package/src/workers/stripe_webhook_connect.ts +107 -0
- package/src/workers/stripe_webhook_secure.ts +91 -0
- package/test/helpers/mem_store.ts +97 -0
- package/test/meter.test.ts +217 -0
- package/test/purchase_store.test.ts +154 -0
- package/test/stripe.test.ts +146 -0
- package/test/stripe_webhook.test.ts +174 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const http_error_1 = require("@mathrunet/masamune_cloudflare/dist/lib/src/http_error");
|
|
4
|
+
require("@mathrunet/masamune");
|
|
5
|
+
const options_1 = require("../lib/options");
|
|
6
|
+
/**
|
|
7
|
+
* Webhook for proper redirection when 3D Secure authentication is required
|
|
8
|
+
* (parity with the `stripeWebhookSecure` function of
|
|
9
|
+
* `@mathrunet/masamune_firebase_purchase_stripe`).
|
|
10
|
+
* Please set here for `returnUrl`.
|
|
11
|
+
*
|
|
12
|
+
* 3Dセキュア認証が必要な場合、適切なリダイレクトを行うためのWebhookです
|
|
13
|
+
* (`@mathrunet/masamune_firebase_purchase_stripe`の`stripeWebhookSecure`と同等)。
|
|
14
|
+
* `returnUrl`にこちらを設定してください。
|
|
15
|
+
*
|
|
16
|
+
* @param {string} PURCHASE_STRIPE_SECRETKEY
|
|
17
|
+
* API key (secret key) to connect to Stripe.
|
|
18
|
+
* Stripeへ接続するためのAPIキー(シークレットキー)。
|
|
19
|
+
*/
|
|
20
|
+
module.exports = (hono, options, data) => {
|
|
21
|
+
hono.get("/", async (c) => {
|
|
22
|
+
try {
|
|
23
|
+
const apiKey = (0, options_1.resolveStripeSecretKey)(c, options);
|
|
24
|
+
const store = (0, options_1.resolveStripePurchaseStore)(c, options);
|
|
25
|
+
const stripeClient = (0, options_1.resolveStripeClient)(c, options, apiKey);
|
|
26
|
+
const token = c.req.query("token");
|
|
27
|
+
if (!token || typeof token !== "string") {
|
|
28
|
+
return c.json({
|
|
29
|
+
"error": "Invalid parameters",
|
|
30
|
+
}, 403);
|
|
31
|
+
}
|
|
32
|
+
let param;
|
|
33
|
+
try {
|
|
34
|
+
param = JSON.parse(await token.decrypt({
|
|
35
|
+
key: apiKey.slice(0, 32),
|
|
36
|
+
ivKey: apiKey.slice(-16),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
return c.json({
|
|
41
|
+
"error": "Invalid parameters",
|
|
42
|
+
}, 403);
|
|
43
|
+
}
|
|
44
|
+
if (!param["userId"] || !param["orderId"] || !param["successUrl"] || !param["failureUrl"]) {
|
|
45
|
+
return c.json({
|
|
46
|
+
"error": "Invalid parameters",
|
|
47
|
+
}, 403);
|
|
48
|
+
}
|
|
49
|
+
const userId = param["userId"];
|
|
50
|
+
const orderId = param["orderId"];
|
|
51
|
+
const successUrl = param["successUrl"];
|
|
52
|
+
const failureUrl = param["failureUrl"];
|
|
53
|
+
const purchaseDoc = await store.getPurchase(orderId, userId);
|
|
54
|
+
// Firebase実装は`paymentId`を参照するが、購入作成時に保存されるキーは
|
|
55
|
+
// `purchaseId`のためそちらを正とし、`paymentId`はフォールバックに残す。
|
|
56
|
+
const purchaseId = purchaseDoc?.data["purchaseId"] ?? purchaseDoc?.data["paymentId"];
|
|
57
|
+
if (!purchaseDoc || !purchaseId) {
|
|
58
|
+
return c.json({
|
|
59
|
+
"error": "The purchase data is not found.",
|
|
60
|
+
}, 404);
|
|
61
|
+
}
|
|
62
|
+
const purchase = await stripeClient.paymentIntents.retrieve(purchaseId);
|
|
63
|
+
if (!purchase) {
|
|
64
|
+
return c.json({
|
|
65
|
+
"error": "The purchase data is not found.",
|
|
66
|
+
}, 404);
|
|
67
|
+
}
|
|
68
|
+
const status = purchase.status;
|
|
69
|
+
if (status === "requires_capture" || status === "succeeded" || status === "processing") {
|
|
70
|
+
return c.redirect(successUrl);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
return c.redirect(failureUrl);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
return (0, http_error_1.jsonError)(c, err);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return hono;
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=stripe_webhook_secure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stripe_webhook_secure.js","sourceRoot":"","sources":["../../src/workers/stripe_webhook_secure.ts"],"names":[],"mappings":"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"}
|
package/jest.config.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"moduleNameMapper": {},
|
|
3
|
+
"moduleDirectories": [
|
|
4
|
+
"src",
|
|
5
|
+
"node_modules"
|
|
6
|
+
],
|
|
7
|
+
"moduleFileExtensions": [
|
|
8
|
+
"js",
|
|
9
|
+
"ts"
|
|
10
|
+
],
|
|
11
|
+
"preset": "ts-jest",
|
|
12
|
+
"testEnvironment": "node",
|
|
13
|
+
"roots": [
|
|
14
|
+
"<rootDir>/test"
|
|
15
|
+
],
|
|
16
|
+
"transform": {
|
|
17
|
+
"^.+\\.(ts|tsx)$": "ts-jest"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mathrunet/masamune_cloudflare_purchase_stripe",
|
|
3
|
+
"version": "3.2.0",
|
|
4
|
+
"description": "Masamune framework plugin package for billing with Stripe on Cloudflare Workers.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepare": "npm run build",
|
|
10
|
+
"prepublish": "npm install && npm run build && npm run test",
|
|
11
|
+
"test": "NODE_OPTIONS='--experimental-vm-modules' jest",
|
|
12
|
+
"postversion": "npm run changelog",
|
|
13
|
+
"changelog": "conventional-changelog -p eslint -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md && git commit -m 'chore(release): publish package'",
|
|
14
|
+
"publish": "npm publish",
|
|
15
|
+
"minor_update": "npm version minor",
|
|
16
|
+
"major_update": "npm version major",
|
|
17
|
+
"patch_update": "npm version patch"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": "22"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/mathrunet/node_masamune.git"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"masamune",
|
|
31
|
+
"katana",
|
|
32
|
+
"flutter",
|
|
33
|
+
"cloudflare",
|
|
34
|
+
"workers",
|
|
35
|
+
"stripe"
|
|
36
|
+
],
|
|
37
|
+
"author": "mathru",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/mathrunet/node_masamune/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/mathrunet/node_masamune#readme",
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@mathrunet/masamune": "^3.10.4",
|
|
45
|
+
"@mathrunet/masamune_cloudflare": "^3.3.5",
|
|
46
|
+
"hono": "^4.12.27",
|
|
47
|
+
"stripe": "^17.6.0",
|
|
48
|
+
"typescript": "^5.9.2"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/jest": "^30.0.0",
|
|
52
|
+
"conventional-changelog-cli": "^5.0.0",
|
|
53
|
+
"jest": "^30.1.3",
|
|
54
|
+
"ts-jest": "^29.4.11"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/functions.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { WorkersData } from "@mathrunet/masamune_cloudflare";
|
|
2
|
+
import { StripePurchaseWorkersOptions } from "./lib/options";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Define a list of applicable Functions for CloudflareWorkers.
|
|
6
|
+
*
|
|
7
|
+
* Endpoints have parity with `@mathrunet/masamune_firebase_purchase_stripe`:
|
|
8
|
+
* `stripe` handles mode-based actions (`create_customer_and_payment`,
|
|
9
|
+
* `create_purchase`, `create_subscription`, etc.) and the webhooks receive
|
|
10
|
+
* events from Stripe. The metered billing library is provided separately in
|
|
11
|
+
* `lib/meter`.
|
|
12
|
+
*
|
|
13
|
+
* CloudflareWorkers用の適用可能なFunctionの一覧を定義します。
|
|
14
|
+
*
|
|
15
|
+
* エンドポイントは`@mathrunet/masamune_firebase_purchase_stripe`と同等です。
|
|
16
|
+
* `stripe`は`mode`分岐のアクション(`create_customer_and_payment`、
|
|
17
|
+
* `create_purchase`、`create_subscription`など)を処理し、各WebhookはStripeから
|
|
18
|
+
* のイベントを受信します。従量課金ライブラリは`lib/meter`で別途提供します。
|
|
19
|
+
*/
|
|
20
|
+
export const Functions = {
|
|
21
|
+
/**
|
|
22
|
+
* Performs various Stripe processes (mode-based actions).
|
|
23
|
+
*
|
|
24
|
+
* Stripeの各種処理(mode分岐のアクション)を実行します。
|
|
25
|
+
*/
|
|
26
|
+
stripe: (options: StripePurchaseWorkersOptions = {}) => new WorkersData({ path: "/stripe", func: require("./workers/stripe"), options: options }),
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Receives and processes webhooks from Stripe.
|
|
30
|
+
*
|
|
31
|
+
* StripeからのWebhookを受け取り処理を行います。
|
|
32
|
+
*/
|
|
33
|
+
stripeWebhook: (options: StripePurchaseWorkersOptions = {}) => new WorkersData({ path: "/stripe/webhook", func: require("./workers/stripe_webhook"), options: options }),
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Receives and processes webhooks for Stripe Connect.
|
|
37
|
+
*
|
|
38
|
+
* Stripe Connect用のWebhookを受信して処理します。
|
|
39
|
+
*/
|
|
40
|
+
stripeWebhookConnect: (options: StripePurchaseWorkersOptions = {}) => new WorkersData({ path: "/stripe/webhook/connect", func: require("./workers/stripe_webhook_connect"), options: options }),
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Webhook for proper redirection when 3D Secure authentication is required.
|
|
44
|
+
*
|
|
45
|
+
* 3Dセキュア認証が必要な場合、適切なリダイレクトを行うためのWebhookです。
|
|
46
|
+
*/
|
|
47
|
+
stripeWebhookSecure: (options: StripePurchaseWorkersOptions = {}) => new WorkersData({ path: "/stripe/webhook/secure", func: require("./workers/stripe_webhook_secure"), options: options }),
|
|
48
|
+
} as const;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 mathru. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Masamune framework plugin package for billing with Stripe on Cloudflare Workers.
|
|
5
|
+
*
|
|
6
|
+
* To use, import * as m from "@mathrunet/masamune_cloudflare_purchase_stripe";
|
|
7
|
+
*
|
|
8
|
+
* [mathru.net]: https://mathru.net
|
|
9
|
+
* [YouTube]: https://www.youtube.com/c/mathrunetchannel
|
|
10
|
+
*/
|
|
11
|
+
export * from "@mathrunet/masamune";
|
|
12
|
+
export * from "@mathrunet/masamune_cloudflare";
|
|
13
|
+
export * from "./functions";
|
|
14
|
+
export * from "./lib/options";
|
|
15
|
+
export * from "./lib/stripe_client";
|
|
16
|
+
export * from "./lib/purchase/interface";
|
|
17
|
+
export * from "./lib/purchase/d1_purchase_store";
|
|
18
|
+
export * from "./lib/purchase/helpers";
|
|
19
|
+
export * from "./lib/purchase/sync_payment";
|
|
20
|
+
export * from "./lib/meter/interface";
|
|
21
|
+
export * from "./lib/meter/stripe_meter_client";
|
|
22
|
+
export * from "./lib/meter/flush_meter";
|
|
23
|
+
export * from "./lib/meter/record_usage";
|
|
24
|
+
export * from "./lib/meter/d1_usage_event_store";
|
|
25
|
+
export * from "./lib/meter/kv_usage_buffer";
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { StripeUsageEvent, StripeUsageEventStore } from "./interface";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal structural interface for a D1-like SQL database.
|
|
5
|
+
*
|
|
6
|
+
* Cloudflare's `D1Database` satisfies this without depending on
|
|
7
|
+
* `@cloudflare/workers-types`.
|
|
8
|
+
*
|
|
9
|
+
* D1相当のSQLデータベースの最小の構造的インターフェース。
|
|
10
|
+
*
|
|
11
|
+
* `@cloudflare/workers-types`に依存せずCloudflareの`D1Database`が適合します。
|
|
12
|
+
*/
|
|
13
|
+
export interface SqlDatabaseLike {
|
|
14
|
+
/**
|
|
15
|
+
* Prepare a SQL statement.
|
|
16
|
+
*
|
|
17
|
+
* SQLステートメントを準備します。
|
|
18
|
+
*/
|
|
19
|
+
prepare(sql: string): {
|
|
20
|
+
bind(...args: unknown[]): {
|
|
21
|
+
run(): Promise<unknown>;
|
|
22
|
+
all<T = Record<string, unknown>>(): Promise<{ results?: T[] | undefined }>;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Column mapping options for [D1UsageEventStore]. Defaults follow the
|
|
29
|
+
* `usage_events` schema (id / api_key_id / endpoint / units / decision_source /
|
|
30
|
+
* created_at / flushed_to_stripe).
|
|
31
|
+
*
|
|
32
|
+
* [D1UsageEventStore]のカラムマッピングオプション。デフォルトは`usage_events`
|
|
33
|
+
* スキーマ(id / api_key_id / endpoint / units / decision_source / created_at /
|
|
34
|
+
* flushed_to_stripe)に従います。
|
|
35
|
+
*/
|
|
36
|
+
export interface D1UsageEventStoreOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Table name. Defaults to `usage_events`.
|
|
39
|
+
*
|
|
40
|
+
* テーブル名。デフォルトは`usage_events`。
|
|
41
|
+
*/
|
|
42
|
+
table?: string | undefined;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Primary key column. Defaults to `id`.
|
|
46
|
+
*
|
|
47
|
+
* 主キーのカラム。デフォルトは`id`。
|
|
48
|
+
*/
|
|
49
|
+
idColumn?: string | undefined;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Customer key column. Defaults to `api_key_id`.
|
|
53
|
+
*
|
|
54
|
+
* 顧客キーのカラム。デフォルトは`api_key_id`。
|
|
55
|
+
*/
|
|
56
|
+
customerKeyColumn?: string | undefined;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Endpoint column. Defaults to `endpoint`.
|
|
60
|
+
*
|
|
61
|
+
* エンドポイントのカラム。デフォルトは`endpoint`。
|
|
62
|
+
*/
|
|
63
|
+
endpointColumn?: string | undefined;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Units column. Defaults to `units`.
|
|
67
|
+
*
|
|
68
|
+
* ユニット数のカラム。デフォルトは`units`。
|
|
69
|
+
*/
|
|
70
|
+
unitsColumn?: string | undefined;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Source label column. Defaults to `decision_source`.
|
|
74
|
+
*
|
|
75
|
+
* ソースラベルのカラム。デフォルトは`decision_source`。
|
|
76
|
+
*/
|
|
77
|
+
sourceColumn?: string | undefined;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creation time column. Defaults to `created_at`.
|
|
81
|
+
*
|
|
82
|
+
* 作成時刻のカラム。デフォルトは`created_at`。
|
|
83
|
+
*/
|
|
84
|
+
createdAtColumn?: string | undefined;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Flushed flag column (0/1). Defaults to `flushed_to_stripe`.
|
|
88
|
+
*
|
|
89
|
+
* 送信済みフラグ(0/1)のカラム。デフォルトは`flushed_to_stripe`。
|
|
90
|
+
*/
|
|
91
|
+
flushedColumn?: string | undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* [StripeUsageEventStore] backed by Cloudflare D1 (or any compatible SQL database).
|
|
96
|
+
*
|
|
97
|
+
* Cloudflare D1(または互換SQLデータベース)を用いた[StripeUsageEventStore]。
|
|
98
|
+
*/
|
|
99
|
+
export class D1UsageEventStore implements StripeUsageEventStore {
|
|
100
|
+
/**
|
|
101
|
+
* [StripeUsageEventStore] backed by Cloudflare D1 (or any compatible SQL database).
|
|
102
|
+
*
|
|
103
|
+
* Cloudflare D1(または互換SQLデータベース)を用いた[StripeUsageEventStore]。
|
|
104
|
+
*/
|
|
105
|
+
constructor(db: SqlDatabaseLike, options: D1UsageEventStoreOptions = {}) {
|
|
106
|
+
this._db = db;
|
|
107
|
+
this._table = options.table ?? "usage_events";
|
|
108
|
+
this._id = options.idColumn ?? "id";
|
|
109
|
+
this._customerKey = options.customerKeyColumn ?? "api_key_id";
|
|
110
|
+
this._endpoint = options.endpointColumn ?? "endpoint";
|
|
111
|
+
this._units = options.unitsColumn ?? "units";
|
|
112
|
+
this._source = options.sourceColumn ?? "decision_source";
|
|
113
|
+
this._createdAt = options.createdAtColumn ?? "created_at";
|
|
114
|
+
this._flushed = options.flushedColumn ?? "flushed_to_stripe";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private readonly _db: SqlDatabaseLike;
|
|
118
|
+
private readonly _table: string;
|
|
119
|
+
private readonly _id: string;
|
|
120
|
+
private readonly _customerKey: string;
|
|
121
|
+
private readonly _endpoint: string;
|
|
122
|
+
private readonly _units: string;
|
|
123
|
+
private readonly _source: string;
|
|
124
|
+
private readonly _createdAt: string;
|
|
125
|
+
private readonly _flushed: string;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Persist a usage event with `flushed = 0`.
|
|
129
|
+
*
|
|
130
|
+
* 使用量イベントを未送信状態で永続化します。
|
|
131
|
+
*/
|
|
132
|
+
async insert(event: StripeUsageEvent): Promise<void> {
|
|
133
|
+
await this._db
|
|
134
|
+
.prepare(
|
|
135
|
+
`INSERT INTO ${this._table}
|
|
136
|
+
(${this._id}, ${this._customerKey}, ${this._endpoint}, ${this._units}, ${this._source}, ${this._createdAt}, ${this._flushed})
|
|
137
|
+
VALUES (?, ?, ?, ?, ?, ?, 0)`,
|
|
138
|
+
)
|
|
139
|
+
.bind(event.id, event.customerKey, event.endpoint, event.units, event.source, event.createdAt)
|
|
140
|
+
.run();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* List events not yet flushed to Stripe for the given customer key.
|
|
145
|
+
*
|
|
146
|
+
* 指定した顧客キーのStripe未送信イベントを一覧します。
|
|
147
|
+
*/
|
|
148
|
+
async listPending(customerKey: string): Promise<{ id: string, units: number }[]> {
|
|
149
|
+
const pending = await this._db
|
|
150
|
+
.prepare(
|
|
151
|
+
`SELECT ${this._id} AS id, ${this._units} AS units FROM ${this._table}
|
|
152
|
+
WHERE ${this._customerKey} = ? AND ${this._flushed} = 0`,
|
|
153
|
+
)
|
|
154
|
+
.bind(customerKey)
|
|
155
|
+
.all<{ id: string, units: number }>();
|
|
156
|
+
return pending.results ?? [];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Mark the given events as flushed to Stripe.
|
|
161
|
+
*
|
|
162
|
+
* 指定したイベントをStripe送信済みとして記録します。
|
|
163
|
+
*/
|
|
164
|
+
async markFlushed(ids: string[]): Promise<void> {
|
|
165
|
+
if (ids.length <= 0) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
169
|
+
await this._db
|
|
170
|
+
.prepare(
|
|
171
|
+
`UPDATE ${this._table} SET ${this._flushed} = 1 WHERE ${this._id} IN (${placeholders})`,
|
|
172
|
+
)
|
|
173
|
+
.bind(...ids)
|
|
174
|
+
.run();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { StripeUsageEventStore } from "./interface";
|
|
2
|
+
import { StripeMeterClient } from "./stripe_meter_client";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Flush pending usage events for one customer to the Stripe Meter Event API.
|
|
6
|
+
*
|
|
7
|
+
* Returns `true` when there was nothing to send or the flush succeeded, and
|
|
8
|
+
* `false` when Stripe rejected the batch (events stay pending for a retry).
|
|
9
|
+
*
|
|
10
|
+
* 顧客1件分の未送信使用量イベントをStripe Meter Event APIへフラッシュします。
|
|
11
|
+
*
|
|
12
|
+
* 送信対象がない、または送信に成功した場合は`true`、Stripeがバッチを拒否した
|
|
13
|
+
* 場合は`false`を返します(イベントは未送信のまま残り、次回再送されます)。
|
|
14
|
+
*/
|
|
15
|
+
export async function flushStripeMeter(options: {
|
|
16
|
+
store: StripeUsageEventStore,
|
|
17
|
+
client: StripeMeterClient,
|
|
18
|
+
customerKey: string,
|
|
19
|
+
stripeCustomerId: string | null | undefined,
|
|
20
|
+
}): Promise<boolean> {
|
|
21
|
+
if (!options.client.enabled || !options.stripeCustomerId) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const rows = await options.store.listPending(options.customerKey);
|
|
26
|
+
const total = rows.reduce((sum, row) => sum + row.units, 0);
|
|
27
|
+
if (total <= 0) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 時間窓ベースだと同一窓内の別バッチが Stripe の冪等性で捨てられ過小計上になる。
|
|
32
|
+
// 送信成功後に flushed を確定するため、identifier はバッチごとに一意でよい。
|
|
33
|
+
const identifier = `${options.customerKey}:${crypto.randomUUID()}`;
|
|
34
|
+
const sent = await options.client.sendMeterEvent({
|
|
35
|
+
stripeCustomerId: options.stripeCustomerId,
|
|
36
|
+
value: total,
|
|
37
|
+
identifier: identifier,
|
|
38
|
+
});
|
|
39
|
+
if (!sent) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await options.store.markFlushed(rows.map((row) => row.id));
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A usage event to be recorded as the source of truth for metered billing.
|
|
3
|
+
*
|
|
4
|
+
* 従量課金の正本として記録される使用量イベント。
|
|
5
|
+
*/
|
|
6
|
+
export interface StripeUsageEvent {
|
|
7
|
+
/**
|
|
8
|
+
* Unique id of the event.
|
|
9
|
+
*
|
|
10
|
+
* イベントの一意なID。
|
|
11
|
+
*/
|
|
12
|
+
id: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Key identifying the customer on your side (e.g. API key id).
|
|
16
|
+
*
|
|
17
|
+
* 自側で顧客を識別するキー(例: APIキーID)。
|
|
18
|
+
*/
|
|
19
|
+
customerKey: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The endpoint or feature that consumed the units.
|
|
23
|
+
*
|
|
24
|
+
* ユニットを消費したエンドポイントや機能。
|
|
25
|
+
*/
|
|
26
|
+
endpoint: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Number of units consumed.
|
|
30
|
+
*
|
|
31
|
+
* 消費ユニット数。
|
|
32
|
+
*/
|
|
33
|
+
units: number;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Optional source label of the event.
|
|
37
|
+
*
|
|
38
|
+
* イベントのソースラベル(任意)。
|
|
39
|
+
*/
|
|
40
|
+
source: string | null;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creation time in milliseconds since epoch.
|
|
44
|
+
*
|
|
45
|
+
* 作成時刻(エポックからのミリ秒)。
|
|
46
|
+
*/
|
|
47
|
+
createdAt: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Storage for usage events. The store is the source of truth for billing.
|
|
52
|
+
*
|
|
53
|
+
* 使用量イベントのストレージ。課金の正本となる。
|
|
54
|
+
*/
|
|
55
|
+
export interface StripeUsageEventStore {
|
|
56
|
+
/**
|
|
57
|
+
* Persist a usage event with `flushed = false`.
|
|
58
|
+
*
|
|
59
|
+
* 使用量イベントを未送信状態で永続化します。
|
|
60
|
+
*/
|
|
61
|
+
insert(event: StripeUsageEvent): Promise<void>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* List events not yet flushed to Stripe for the given customer key.
|
|
65
|
+
*
|
|
66
|
+
* 指定した顧客キーのStripe未送信イベントを一覧します。
|
|
67
|
+
*/
|
|
68
|
+
listPending(customerKey: string): Promise<{ id: string, units: number }[]>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Mark the given events as flushed to Stripe.
|
|
72
|
+
*
|
|
73
|
+
* 指定したイベントをStripe送信済みとして記録します。
|
|
74
|
+
*/
|
|
75
|
+
markFlushed(ids: string[]): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* State of the flush-trigger buffer for a customer.
|
|
80
|
+
*
|
|
81
|
+
* 顧客ごとのフラッシュトリガー用バッファの状態。
|
|
82
|
+
*/
|
|
83
|
+
export interface StripeUsageBufferState {
|
|
84
|
+
/**
|
|
85
|
+
* Accumulated units since the last flush.
|
|
86
|
+
*
|
|
87
|
+
* 前回フラッシュ以降に積み上がったユニット数。
|
|
88
|
+
*/
|
|
89
|
+
units: number;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Time in milliseconds when the buffer window started.
|
|
93
|
+
*
|
|
94
|
+
* バッファ窓が開始した時刻(ミリ秒)。
|
|
95
|
+
*/
|
|
96
|
+
since: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Buffer used only to decide when to flush. Billing accuracy does not depend on it.
|
|
101
|
+
*
|
|
102
|
+
* フラッシュタイミングの判定のみに使うバッファ。課金精度はこれに依存しない。
|
|
103
|
+
*/
|
|
104
|
+
export interface StripeUsageBuffer {
|
|
105
|
+
/**
|
|
106
|
+
* Get the buffer state for the customer key. Returns `null` if absent or corrupt.
|
|
107
|
+
*
|
|
108
|
+
* 顧客キーのバッファ状態を取得します。存在しない・壊れている場合は`null`。
|
|
109
|
+
*/
|
|
110
|
+
get(customerKey: string): Promise<StripeUsageBufferState | null>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Store the buffer state for the customer key.
|
|
114
|
+
*
|
|
115
|
+
* 顧客キーのバッファ状態を保存します。
|
|
116
|
+
*/
|
|
117
|
+
put(customerKey: string, value: StripeUsageBufferState, ttlSeconds: number): Promise<void>;
|
|
118
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { StripeUsageBuffer, StripeUsageBufferState } from "./interface";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal structural interface for a KV-like namespace.
|
|
5
|
+
*
|
|
6
|
+
* Cloudflare's `KVNamespace` satisfies this without depending on
|
|
7
|
+
* `@cloudflare/workers-types`.
|
|
8
|
+
*
|
|
9
|
+
* KV相当のネームスペースの最小の構造的インターフェース。
|
|
10
|
+
*
|
|
11
|
+
* `@cloudflare/workers-types`に依存せずCloudflareの`KVNamespace`が適合します。
|
|
12
|
+
*/
|
|
13
|
+
export interface KVNamespaceLike {
|
|
14
|
+
/**
|
|
15
|
+
* Get a JSON value for the key.
|
|
16
|
+
*
|
|
17
|
+
* キーに対応するJSON値を取得します。
|
|
18
|
+
*/
|
|
19
|
+
get(key: string, type: "json"): Promise<unknown | null>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Store a string value for the key.
|
|
23
|
+
*
|
|
24
|
+
* キーに対応する文字列値を保存します。
|
|
25
|
+
*/
|
|
26
|
+
put(
|
|
27
|
+
key: string,
|
|
28
|
+
value: string,
|
|
29
|
+
options?: { expirationTtl?: number | undefined },
|
|
30
|
+
): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* [StripeUsageBuffer] backed by Cloudflare KV.
|
|
35
|
+
*
|
|
36
|
+
* Cloudflare KVを用いた[StripeUsageBuffer]。
|
|
37
|
+
*/
|
|
38
|
+
export class KVUsageBuffer implements StripeUsageBuffer {
|
|
39
|
+
/**
|
|
40
|
+
* [StripeUsageBuffer] backed by Cloudflare KV.
|
|
41
|
+
*
|
|
42
|
+
* Cloudflare KVを用いた[StripeUsageBuffer]。
|
|
43
|
+
*/
|
|
44
|
+
constructor(kv: KVNamespaceLike, options: { prefix?: string | undefined } = {}) {
|
|
45
|
+
this._kv = kv;
|
|
46
|
+
this._prefix = options.prefix ?? "usage:";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private readonly _kv: KVNamespaceLike;
|
|
50
|
+
private readonly _prefix: string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get the buffer state for the customer key. Returns `null` if absent or corrupt.
|
|
54
|
+
*
|
|
55
|
+
* 顧客キーのバッファ状態を取得します。存在しない・壊れている場合は`null`。
|
|
56
|
+
*/
|
|
57
|
+
async get(customerKey: string): Promise<StripeUsageBufferState | null> {
|
|
58
|
+
try {
|
|
59
|
+
const stored = await this._kv.get(`${this._prefix}${customerKey}`, "json");
|
|
60
|
+
if (stored && typeof stored === "object") {
|
|
61
|
+
return stored as StripeUsageBufferState;
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// ignore corrupt buffer
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Store the buffer state for the customer key.
|
|
71
|
+
*
|
|
72
|
+
* 顧客キーのバッファ状態を保存します。
|
|
73
|
+
*/
|
|
74
|
+
async put(customerKey: string, value: StripeUsageBufferState, ttlSeconds: number): Promise<void> {
|
|
75
|
+
await this._kv.put(`${this._prefix}${customerKey}`, JSON.stringify(value), {
|
|
76
|
+
expirationTtl: ttlSeconds,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|