@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,87 @@
|
|
|
1
|
+
import { StripeUsageBuffer, StripeUsageBufferState, StripeUsageEventStore } from "./interface";
|
|
2
|
+
import { flushStripeMeter } from "./flush_meter";
|
|
3
|
+
import { StripeMeterClient } from "./stripe_meter_client";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Record a usage event and flush to Stripe when the buffer is full or aged.
|
|
7
|
+
*
|
|
8
|
+
* The store (e.g. D1) is the source of truth. The buffer (e.g. KV) only decides
|
|
9
|
+
* flush timing — eventual-consistency races there never affect billing.
|
|
10
|
+
* When a flush fails the buffer is kept so the next request retries immediately;
|
|
11
|
+
* unsent units remain pending in the store and are sent together next time.
|
|
12
|
+
*
|
|
13
|
+
* 使用量イベントを記録し、バッファが満杯または時間経過でStripeへフラッシュします。
|
|
14
|
+
*
|
|
15
|
+
* 正本はストア(例: D1)。バッファ(例: KV)はフラッシュタイミングの判定のみで、
|
|
16
|
+
* 結果整合の競合があっても課金には影響しません。フラッシュ失敗時はバッファを残して
|
|
17
|
+
* 次のリクエストで即再送させます。未送信分はストアに残り、次回まとめて送られます。
|
|
18
|
+
*/
|
|
19
|
+
export async function recordUsage(options: {
|
|
20
|
+
store: StripeUsageEventStore,
|
|
21
|
+
buffer: StripeUsageBuffer,
|
|
22
|
+
client: StripeMeterClient,
|
|
23
|
+
customerKey: string,
|
|
24
|
+
stripeCustomerId: string | null | undefined,
|
|
25
|
+
endpoint: string,
|
|
26
|
+
units?: number | undefined,
|
|
27
|
+
source?: string | undefined,
|
|
28
|
+
flushUnits?: number | undefined,
|
|
29
|
+
flushMs?: number | undefined,
|
|
30
|
+
bufferTtlSeconds?: number | undefined,
|
|
31
|
+
}): Promise<void> {
|
|
32
|
+
const units = options.units ?? 1;
|
|
33
|
+
const flushUnits = options.flushUnits ?? 10;
|
|
34
|
+
const flushMs = options.flushMs ?? 60_000;
|
|
35
|
+
const bufferTtlSeconds = options.bufferTtlSeconds ?? 3600;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await options.store.insert({
|
|
39
|
+
id: crypto.randomUUID(),
|
|
40
|
+
customerKey: options.customerKey,
|
|
41
|
+
endpoint: options.endpoint,
|
|
42
|
+
units: units,
|
|
43
|
+
source: options.source ?? null,
|
|
44
|
+
createdAt: Date.now(),
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn("usage insert failed", error);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let buffer: StripeUsageBufferState = { units: 0, since: Date.now() };
|
|
52
|
+
try {
|
|
53
|
+
const stored = await options.buffer.get(options.customerKey);
|
|
54
|
+
if (stored && typeof stored === "object") {
|
|
55
|
+
buffer = stored;
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// ignore corrupt buffer
|
|
59
|
+
}
|
|
60
|
+
buffer.units += units;
|
|
61
|
+
const aged = Date.now() - buffer.since >= flushMs;
|
|
62
|
+
const full = buffer.units >= flushUnits;
|
|
63
|
+
await options.buffer.put(options.customerKey, buffer, bufferTtlSeconds);
|
|
64
|
+
|
|
65
|
+
if (!full && !aged) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let flushed = false;
|
|
69
|
+
try {
|
|
70
|
+
flushed = await flushStripeMeter({
|
|
71
|
+
store: options.store,
|
|
72
|
+
client: options.client,
|
|
73
|
+
customerKey: options.customerKey,
|
|
74
|
+
stripeCustomerId: options.stripeCustomerId,
|
|
75
|
+
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.warn("stripe meter flush error", error);
|
|
78
|
+
}
|
|
79
|
+
if (!flushed) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
await options.buffer.put(
|
|
83
|
+
options.customerKey,
|
|
84
|
+
{ units: 0, since: Date.now() },
|
|
85
|
+
bufferTtlSeconds,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import Stripe from "stripe";
|
|
2
|
+
import { createStripeClient } from "../stripe_client";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for [StripeMeterClient].
|
|
6
|
+
*
|
|
7
|
+
* [StripeMeterClient]のオプション。
|
|
8
|
+
*/
|
|
9
|
+
export interface StripeMeterClientOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Stripe secret key. When empty the client is disabled and sends nothing.
|
|
12
|
+
*
|
|
13
|
+
* Stripeのシークレットキー。空の場合クライアントは無効となり何も送信しません。
|
|
14
|
+
*/
|
|
15
|
+
secretKey?: string | undefined;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Meter event name registered on Stripe (billing meters, basil or later).
|
|
19
|
+
*
|
|
20
|
+
* Stripeに登録したMeterイベント名(billing meters、basil以降)。
|
|
21
|
+
*/
|
|
22
|
+
eventName: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Fetch implementation used by the Stripe SDK's fetch HTTP client.
|
|
26
|
+
* Defaults to the global `fetch`. Inject for tests.
|
|
27
|
+
*
|
|
28
|
+
* Stripe SDKのfetch HTTPクライアントが使用するfetch実装。
|
|
29
|
+
* デフォルトはグローバル`fetch`。テスト時に注入します。
|
|
30
|
+
*/
|
|
31
|
+
fetch?: typeof fetch | undefined;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Stripe SDK client instance. When specified, `secretKey` / `fetch` are not
|
|
35
|
+
* used to construct a client.
|
|
36
|
+
*
|
|
37
|
+
* Stripe SDKのクライアントインスタンス。指定した場合`secretKey` / `fetch`は
|
|
38
|
+
* クライアント生成に使用されません。
|
|
39
|
+
*/
|
|
40
|
+
stripeClient?: Stripe | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Client for the Stripe Meter Event API (`/v1/billing/meter_events`).
|
|
45
|
+
*
|
|
46
|
+
* Uses the Stripe SDK with the fetch HTTP client so it works on Cloudflare
|
|
47
|
+
* Workers.
|
|
48
|
+
*
|
|
49
|
+
* Stripe Meter Event API(`/v1/billing/meter_events`)用のクライアント。
|
|
50
|
+
*
|
|
51
|
+
* Cloudflare Workersで動作するようfetch HTTPクライアント付きのStripe SDKを
|
|
52
|
+
* 使用します。
|
|
53
|
+
*/
|
|
54
|
+
export class StripeMeterClient {
|
|
55
|
+
/**
|
|
56
|
+
* Client for the Stripe Meter Event API (`/v1/billing/meter_events`).
|
|
57
|
+
*
|
|
58
|
+
* Stripe Meter Event API(`/v1/billing/meter_events`)用のクライアント。
|
|
59
|
+
*/
|
|
60
|
+
constructor(options: StripeMeterClientOptions) {
|
|
61
|
+
this._secretKey = options.secretKey ?? "";
|
|
62
|
+
this._eventName = options.eventName;
|
|
63
|
+
this._fetch = options.fetch;
|
|
64
|
+
this._client = options.stripeClient;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private readonly _secretKey: string;
|
|
68
|
+
private readonly _eventName: string;
|
|
69
|
+
private readonly _fetch: typeof fetch | undefined;
|
|
70
|
+
private _client: Stripe | undefined;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Whether the client can send meter events.
|
|
74
|
+
*
|
|
75
|
+
* Meterイベントを送信できる状態かどうか。
|
|
76
|
+
*/
|
|
77
|
+
get enabled(): boolean {
|
|
78
|
+
return (this._client !== undefined || this._secretKey.length > 0) && this._eventName.length > 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private get client(): Stripe {
|
|
82
|
+
if (!this._client) {
|
|
83
|
+
this._client = createStripeClient({
|
|
84
|
+
secretKey: this._secretKey,
|
|
85
|
+
fetch: this._fetch,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return this._client;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Send one meter event. Returns `false` when Stripe responds with an error.
|
|
93
|
+
*
|
|
94
|
+
* Meterイベントを1件送信します。Stripeがエラーを返した場合は`false`を返します。
|
|
95
|
+
*/
|
|
96
|
+
async sendMeterEvent(options: {
|
|
97
|
+
stripeCustomerId: string,
|
|
98
|
+
value: number,
|
|
99
|
+
identifier: string,
|
|
100
|
+
}): Promise<boolean> {
|
|
101
|
+
try {
|
|
102
|
+
await this.client.billing.meterEvents.create({
|
|
103
|
+
event_name: this._eventName,
|
|
104
|
+
identifier: options.identifier,
|
|
105
|
+
payload: {
|
|
106
|
+
stripe_customer_id: options.stripeCustomerId,
|
|
107
|
+
value: String(options.value),
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
return true;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
113
|
+
console.warn("stripe meter flush failed", message.slice(0, 240));
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import Stripe from "stripe";
|
|
2
|
+
import { Context } from "hono";
|
|
3
|
+
import { resolveConfig } from "@mathrunet/masamune_cloudflare/dist/lib/src/http_error";
|
|
4
|
+
import { WorkersOptions } from "@mathrunet/masamune_cloudflare/dist/lib/src/workers_base";
|
|
5
|
+
import { SqlDatabaseLike } from "./meter/d1_usage_event_store";
|
|
6
|
+
import { D1StripePurchaseStore, D1StripePurchaseStoreOptions } from "./purchase/d1_purchase_store";
|
|
7
|
+
import { StripePurchaseStore } from "./purchase/interface";
|
|
8
|
+
import { createStripeClient } from "./stripe_client";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Email content sent for 3D Secure authentication.
|
|
12
|
+
*
|
|
13
|
+
* 3Dセキュア認証用に送信するメールの内容。
|
|
14
|
+
*/
|
|
15
|
+
export interface StripePurchaseEmail {
|
|
16
|
+
/**
|
|
17
|
+
* Sender's email address.
|
|
18
|
+
*
|
|
19
|
+
* 送信元メールアドレス。
|
|
20
|
+
*/
|
|
21
|
+
from: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Email address to be sent to.
|
|
25
|
+
*
|
|
26
|
+
* 送信先メールアドレス。
|
|
27
|
+
*/
|
|
28
|
+
to: string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Email title.
|
|
32
|
+
*
|
|
33
|
+
* メールタイトル。
|
|
34
|
+
*/
|
|
35
|
+
subject: string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Email content.
|
|
39
|
+
*
|
|
40
|
+
* メール本文。
|
|
41
|
+
*/
|
|
42
|
+
text: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Options for the Stripe purchase workers.
|
|
47
|
+
*
|
|
48
|
+
* Stripe課金ワーカーのオプション。
|
|
49
|
+
*/
|
|
50
|
+
export interface StripePurchaseWorkersOptions extends WorkersOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Stripe secret key.
|
|
53
|
+
*
|
|
54
|
+
* If not specified, it is resolved from the `PURCHASE_STRIPE_SECRETKEY` environment variable (Workers secret).
|
|
55
|
+
*
|
|
56
|
+
* Stripeのシークレットキー。
|
|
57
|
+
*
|
|
58
|
+
* 指定されていない場合は`PURCHASE_STRIPE_SECRETKEY`環境変数(Workersシークレット)から解決されます。
|
|
59
|
+
*/
|
|
60
|
+
secretKey?: string | undefined;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Meter event name registered on Stripe (billing meters, basil or later).
|
|
64
|
+
*
|
|
65
|
+
* If not specified, it is resolved from the `PURCHASE_STRIPE_METER_EVENT_NAME` environment variable.
|
|
66
|
+
*
|
|
67
|
+
* Stripeに登録したMeterイベント名(billing meters、basil以降)。
|
|
68
|
+
*
|
|
69
|
+
* 指定されていない場合は`PURCHASE_STRIPE_METER_EVENT_NAME`環境変数から解決されます。
|
|
70
|
+
*/
|
|
71
|
+
meterEventName?: string | undefined;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Signing secret for the main Stripe webhook.
|
|
75
|
+
*
|
|
76
|
+
* If not specified, it is resolved from the `PURCHASE_STRIPE_WEBHOOKSECRET` environment variable (Workers secret).
|
|
77
|
+
*
|
|
78
|
+
* StripeメインWebhookの署名シークレット。
|
|
79
|
+
*
|
|
80
|
+
* 指定されていない場合は`PURCHASE_STRIPE_WEBHOOKSECRET`環境変数(Workersシークレット)から解決されます。
|
|
81
|
+
*/
|
|
82
|
+
webhookSecret?: string | undefined;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Signing secret for the Stripe Connect webhook.
|
|
86
|
+
*
|
|
87
|
+
* If not specified, it is resolved from the `PURCHASE_STRIPE_WEBHOOKCONNECTSECRET` environment variable (Workers secret).
|
|
88
|
+
*
|
|
89
|
+
* Stripe Connect用Webhookの署名シークレット。
|
|
90
|
+
*
|
|
91
|
+
* 指定されていない場合は`PURCHASE_STRIPE_WEBHOOKCONNECTSECRET`環境変数(Workersシークレット)から解決されます。
|
|
92
|
+
*/
|
|
93
|
+
webhookConnectSecret?: string | undefined;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Purchase data store factory. Defaults to [D1StripePurchaseStore] on the D1
|
|
97
|
+
* binding named by [d1Binding] (default `DB`).
|
|
98
|
+
*
|
|
99
|
+
* 購入データストアのファクトリ。デフォルトは[d1Binding](既定`DB`)で指定した
|
|
100
|
+
* D1バインディング上の[D1StripePurchaseStore]。
|
|
101
|
+
*/
|
|
102
|
+
store?: ((context: Context) => StripePurchaseStore) | undefined;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Name of the D1 binding used by the default store. Defaults to `DB`.
|
|
106
|
+
*
|
|
107
|
+
* デフォルトストアが使用するD1バインディング名。デフォルトは`DB`。
|
|
108
|
+
*/
|
|
109
|
+
d1Binding?: string | undefined;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Table name options for the default [D1StripePurchaseStore].
|
|
113
|
+
*
|
|
114
|
+
* デフォルトの[D1StripePurchaseStore]のテーブル名オプション。
|
|
115
|
+
*/
|
|
116
|
+
tables?: D1StripePurchaseStoreOptions | undefined;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Resolve the email address of an application user (parity with
|
|
120
|
+
* `admin.auth().getUser()` in the Firebase implementation). When not
|
|
121
|
+
* specified, the `email` field of the stored user document and the billing
|
|
122
|
+
* details of the default payment method are used as fallbacks.
|
|
123
|
+
*
|
|
124
|
+
* アプリケーションユーザーのメールアドレスを解決します(Firebase実装の
|
|
125
|
+
* `admin.auth().getUser()`相当)。未指定の場合は保存済みユーザードキュメントの
|
|
126
|
+
* `email`フィールドとデフォルト支払い方法のbilling detailsをフォールバックとして
|
|
127
|
+
* 使用します。
|
|
128
|
+
*/
|
|
129
|
+
resolveUserEmail?: ((context: Context, userId: string) => Promise<string | null | undefined>) | undefined;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Send an email for 3D Secure authentication. When not specified, the
|
|
133
|
+
* provider resolved from `PURCHASE_STRIPE_EMAILPROVIDER` (default
|
|
134
|
+
* `sendgrid`, using the `MAIL_SENDGRID_APIKEY` Workers secret) is used.
|
|
135
|
+
*
|
|
136
|
+
* 3Dセキュア認証用のメールを送信します。未指定の場合は
|
|
137
|
+
* `PURCHASE_STRIPE_EMAILPROVIDER`(既定`sendgrid`、`MAIL_SENDGRID_APIKEY`の
|
|
138
|
+
* Workersシークレットを使用)から解決したプロバイダーを使用します。
|
|
139
|
+
*/
|
|
140
|
+
sendEmail?: ((context: Context, email: StripePurchaseEmail) => Promise<void>) | undefined;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Stripe SDK client factory. Defaults to [createStripeClient] with the
|
|
144
|
+
* resolved secret key. Inject for tests.
|
|
145
|
+
*
|
|
146
|
+
* Stripe SDKクライアントのファクトリ。デフォルトは解決したシークレットキー
|
|
147
|
+
* を用いた[createStripeClient]。テスト時に注入します。
|
|
148
|
+
*/
|
|
149
|
+
stripeClient?: ((context: Context, secretKey: string) => Stripe) | undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolve the Stripe SDK client from options and the resolved secret key.
|
|
154
|
+
*
|
|
155
|
+
* オプションと解決済みシークレットキーからStripe SDKクライアントを解決します。
|
|
156
|
+
*/
|
|
157
|
+
export function resolveStripeClient(
|
|
158
|
+
context: Context,
|
|
159
|
+
options: StripePurchaseWorkersOptions,
|
|
160
|
+
secretKey: string,
|
|
161
|
+
): Stripe {
|
|
162
|
+
if (options.stripeClient) {
|
|
163
|
+
return options.stripeClient(context, secretKey);
|
|
164
|
+
}
|
|
165
|
+
return createStripeClient({ secretKey });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Resolve the Stripe secret key from options and `context.env`.
|
|
170
|
+
*
|
|
171
|
+
* オプションと`context.env`からStripeのシークレットキーを解決します。
|
|
172
|
+
*/
|
|
173
|
+
export function resolveStripeSecretKey(
|
|
174
|
+
context: Context,
|
|
175
|
+
options: StripePurchaseWorkersOptions,
|
|
176
|
+
): string {
|
|
177
|
+
return resolveConfig(context, options.secretKey, "PURCHASE_STRIPE_SECRETKEY") ?? "";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Resolve the Stripe meter event name from options and `context.env`.
|
|
182
|
+
*
|
|
183
|
+
* オプションと`context.env`からStripeのMeterイベント名を解決します。
|
|
184
|
+
*/
|
|
185
|
+
export function resolveStripeMeterEventName(
|
|
186
|
+
context: Context,
|
|
187
|
+
options: StripePurchaseWorkersOptions,
|
|
188
|
+
): string | undefined {
|
|
189
|
+
return resolveConfig(context, options.meterEventName, "PURCHASE_STRIPE_METER_EVENT_NAME");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Resolve the main webhook signing secret from options and `context.env`.
|
|
194
|
+
*
|
|
195
|
+
* オプションと`context.env`からメインWebhookの署名シークレットを解決します。
|
|
196
|
+
*/
|
|
197
|
+
export function resolveStripeWebhookSecret(
|
|
198
|
+
context: Context,
|
|
199
|
+
options: StripePurchaseWorkersOptions,
|
|
200
|
+
): string {
|
|
201
|
+
return resolveConfig(context, options.webhookSecret, "PURCHASE_STRIPE_WEBHOOKSECRET") ?? "";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Resolve the Connect webhook signing secret from options and `context.env`.
|
|
206
|
+
*
|
|
207
|
+
* オプションと`context.env`からConnect用Webhookの署名シークレットを解決します。
|
|
208
|
+
*/
|
|
209
|
+
export function resolveStripeWebhookConnectSecret(
|
|
210
|
+
context: Context,
|
|
211
|
+
options: StripePurchaseWorkersOptions,
|
|
212
|
+
): string {
|
|
213
|
+
return resolveConfig(context, options.webhookConnectSecret, "PURCHASE_STRIPE_WEBHOOKCONNECTSECRET") ?? "";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Resolve the purchase data store from options and `context.env`.
|
|
218
|
+
*
|
|
219
|
+
* オプションと`context.env`から購入データストアを解決します。
|
|
220
|
+
*/
|
|
221
|
+
export function resolveStripePurchaseStore(
|
|
222
|
+
context: Context,
|
|
223
|
+
options: StripePurchaseWorkersOptions,
|
|
224
|
+
): StripePurchaseStore {
|
|
225
|
+
if (options.store) {
|
|
226
|
+
return options.store(context);
|
|
227
|
+
}
|
|
228
|
+
const binding = options.d1Binding ?? "DB";
|
|
229
|
+
const env = (context.env ?? {}) as Record<string, unknown>;
|
|
230
|
+
const db = env[binding] as SqlDatabaseLike | undefined;
|
|
231
|
+
if (!db) {
|
|
232
|
+
throw new Error(`The D1 binding "${binding}" is not found. Set options.store or options.d1Binding.`);
|
|
233
|
+
}
|
|
234
|
+
return new D1StripePurchaseStore(db, options.tables ?? {});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Resolve the email sender from options and `context.env`.
|
|
239
|
+
*
|
|
240
|
+
* Returns `null` when no provider is configured.
|
|
241
|
+
*
|
|
242
|
+
* オプションと`context.env`からメール送信手段を解決します。
|
|
243
|
+
*
|
|
244
|
+
* プロバイダーが未設定の場合は`null`を返します。
|
|
245
|
+
*/
|
|
246
|
+
export function resolveStripeEmailSender(
|
|
247
|
+
context: Context,
|
|
248
|
+
options: StripePurchaseWorkersOptions,
|
|
249
|
+
): ((email: StripePurchaseEmail) => Promise<void>) | null {
|
|
250
|
+
if (options.sendEmail) {
|
|
251
|
+
const sendEmail = options.sendEmail;
|
|
252
|
+
return (email) => sendEmail(context, email);
|
|
253
|
+
}
|
|
254
|
+
const provider = resolveConfig(context, undefined, "PURCHASE_STRIPE_EMAILPROVIDER") ?? "sendgrid";
|
|
255
|
+
switch (provider) {
|
|
256
|
+
case "sendgrid": {
|
|
257
|
+
const apiKey = resolveConfig(context, undefined, "MAIL_SENDGRID_APIKEY");
|
|
258
|
+
if (!apiKey) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
return (email) => sendWithSendGrid(apiKey, email);
|
|
262
|
+
}
|
|
263
|
+
default: {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// SendGrid REST API (v3) 直呼び。@mathrunet/masamune_cloudflare_mail_sendgrid と
|
|
270
|
+
// 同一の送信仕様だが、同パッケージの再エクスポート連鎖をWorkersバンドルへ
|
|
271
|
+
// 持ち込まないためここにインライン実装する。
|
|
272
|
+
async function sendWithSendGrid(apiKey: string, email: StripePurchaseEmail): Promise<void> {
|
|
273
|
+
const res = await fetch("https://api.sendgrid.com/v3/mail/send", {
|
|
274
|
+
method: "POST",
|
|
275
|
+
headers: {
|
|
276
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
277
|
+
"Content-Type": "application/json",
|
|
278
|
+
},
|
|
279
|
+
body: JSON.stringify({
|
|
280
|
+
personalizations: [
|
|
281
|
+
{
|
|
282
|
+
to: [{ email: email.to }],
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
from: { email: email.from },
|
|
286
|
+
subject: email.subject,
|
|
287
|
+
content: [
|
|
288
|
+
{
|
|
289
|
+
type: "text/plain",
|
|
290
|
+
value: email.text,
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
}),
|
|
294
|
+
});
|
|
295
|
+
if (!res.ok) {
|
|
296
|
+
const body = await res.text();
|
|
297
|
+
throw new Error(`Failed to send mail through SendGrid: ${res.status} ${body}`);
|
|
298
|
+
}
|
|
299
|
+
}
|