@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,414 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { jsonError } from "@mathrunet/masamune_cloudflare/dist/lib/src/http_error";
|
|
3
|
+
import {
|
|
4
|
+
resolveStripeClient,
|
|
5
|
+
resolveStripePurchaseStore,
|
|
6
|
+
resolveStripeSecretKey,
|
|
7
|
+
resolveStripeWebhookSecret,
|
|
8
|
+
StripePurchaseWorkersOptions,
|
|
9
|
+
} from "../lib/options";
|
|
10
|
+
import { constructStripeEvent } from "../lib/stripe_client";
|
|
11
|
+
import { syncStripePayment } from "../lib/purchase/sync_payment";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Receives and processes webhooks from Stripe (parity with the `stripeWebhook`
|
|
15
|
+
* function of `@mathrunet/masamune_firebase_purchase_stripe`).
|
|
16
|
+
* Please register the URL when you deploy this in your Stripe webhook settings.
|
|
17
|
+
*
|
|
18
|
+
* StripeからのWebhookを受け取り処理を行います
|
|
19
|
+
* (`@mathrunet/masamune_firebase_purchase_stripe`の`stripeWebhook`と同等)。
|
|
20
|
+
* こちらをデプロイした際のURLをStripeのWebhook設定に登録してください。
|
|
21
|
+
*
|
|
22
|
+
* @param {string} PURCHASE_STRIPE_SECRETKEY
|
|
23
|
+
* API key (secret key) to connect to Stripe.
|
|
24
|
+
* Stripeへ接続するためのAPIキー(シークレットキー)。
|
|
25
|
+
*
|
|
26
|
+
* @param {string} PURCHASE_STRIPE_WEBHOOKSECRET
|
|
27
|
+
* Specify the **Signature Secret** after setting it up as a webhook.
|
|
28
|
+
* Webhookとして設定したあとの**署名シークレット**を指定します。
|
|
29
|
+
*/
|
|
30
|
+
module.exports = (
|
|
31
|
+
hono: Hono,
|
|
32
|
+
options: StripePurchaseWorkersOptions,
|
|
33
|
+
data: { [key: string]: any },
|
|
34
|
+
) => {
|
|
35
|
+
hono.post("/", async (c) => {
|
|
36
|
+
try {
|
|
37
|
+
const apiKey = resolveStripeSecretKey(c, options);
|
|
38
|
+
const webhookSecret = resolveStripeWebhookSecret(c, options);
|
|
39
|
+
const store = resolveStripePurchaseStore(c, options);
|
|
40
|
+
const stripeClient = resolveStripeClient(c, options, apiKey);
|
|
41
|
+
const signature = c.req.header("stripe-signature");
|
|
42
|
+
if (!signature || !webhookSecret) {
|
|
43
|
+
return c.json({
|
|
44
|
+
"error": "Access denied.",
|
|
45
|
+
}, 403);
|
|
46
|
+
}
|
|
47
|
+
const payload = await c.req.text();
|
|
48
|
+
let event;
|
|
49
|
+
try {
|
|
50
|
+
event = await constructStripeEvent({
|
|
51
|
+
client: stripeClient,
|
|
52
|
+
payload,
|
|
53
|
+
signature,
|
|
54
|
+
secret: webhookSecret,
|
|
55
|
+
});
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.warn(err);
|
|
58
|
+
return c.json({
|
|
59
|
+
"error": "Access denied.",
|
|
60
|
+
}, 403);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
switch (event.type) {
|
|
64
|
+
case "payment_intent.requires_action":
|
|
65
|
+
case "payment_intent.amount_capturable_updated": {
|
|
66
|
+
const payment = event.data.object as {
|
|
67
|
+
[key: string]: any
|
|
68
|
+
};
|
|
69
|
+
const purchaseId = payment["id"];
|
|
70
|
+
const customerId = payment["customer"];
|
|
71
|
+
const status = payment["status"];
|
|
72
|
+
if (!customerId) {
|
|
73
|
+
return c.json({
|
|
74
|
+
"error": "The customer id is not found.",
|
|
75
|
+
}, 404);
|
|
76
|
+
}
|
|
77
|
+
if (!status) {
|
|
78
|
+
return c.json({
|
|
79
|
+
"error": "The status is not found.",
|
|
80
|
+
}, 404);
|
|
81
|
+
}
|
|
82
|
+
const user = await store.findUserByCustomerId(customerId);
|
|
83
|
+
if (!user) {
|
|
84
|
+
return c.json({
|
|
85
|
+
"error": "The account data is not found.",
|
|
86
|
+
}, 404);
|
|
87
|
+
}
|
|
88
|
+
const purchase = await store.findPurchaseByPurchaseId(purchaseId);
|
|
89
|
+
if (!purchase) {
|
|
90
|
+
return c.json({
|
|
91
|
+
"error": "The purchase data is not found.",
|
|
92
|
+
}, 404);
|
|
93
|
+
}
|
|
94
|
+
const update: { [key: string]: any } = {};
|
|
95
|
+
switch (status) {
|
|
96
|
+
case "requires_payment_method":
|
|
97
|
+
case "requires_confirmation": {
|
|
98
|
+
update["confirm"] = false;
|
|
99
|
+
update["verify"] = false;
|
|
100
|
+
update["capture"] = false;
|
|
101
|
+
update["success"] = false;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "requires_action": {
|
|
105
|
+
update["confirm"] = true;
|
|
106
|
+
update["verify"] = false;
|
|
107
|
+
update["capture"] = false;
|
|
108
|
+
update["success"] = false;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case "requires_capture": {
|
|
112
|
+
update["confirm"] = true;
|
|
113
|
+
update["verify"] = true;
|
|
114
|
+
update["capture"] = false;
|
|
115
|
+
update["success"] = false;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
update["updatedTime"] = new Date();
|
|
120
|
+
await store.savePurchase(purchase.orderId, update);
|
|
121
|
+
return c.json({
|
|
122
|
+
"success": true,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
case "payment_intent.succeeded": {
|
|
126
|
+
const payment = event.data.object as {
|
|
127
|
+
[key: string]: any
|
|
128
|
+
};
|
|
129
|
+
const purchaseId = payment["id"];
|
|
130
|
+
const customerId = payment["customer"];
|
|
131
|
+
if (!customerId) {
|
|
132
|
+
return c.json({
|
|
133
|
+
"error": "The customer id is not found.",
|
|
134
|
+
}, 404);
|
|
135
|
+
}
|
|
136
|
+
const user = await store.findUserByCustomerId(customerId);
|
|
137
|
+
if (!user) {
|
|
138
|
+
return c.json({
|
|
139
|
+
"error": "The account data is not found.",
|
|
140
|
+
}, 404);
|
|
141
|
+
}
|
|
142
|
+
const purchase = await store.findPurchaseByPurchaseId(purchaseId);
|
|
143
|
+
if (!purchase) {
|
|
144
|
+
return c.json({
|
|
145
|
+
"error": "The purchase data is not found.",
|
|
146
|
+
}, 404);
|
|
147
|
+
}
|
|
148
|
+
const update: { [key: string]: any } = {};
|
|
149
|
+
update["confirm"] = true;
|
|
150
|
+
update["verify"] = true;
|
|
151
|
+
update["capture"] = true;
|
|
152
|
+
update["success"] = true;
|
|
153
|
+
update["error"] = null;
|
|
154
|
+
update["errorMessage"] = null;
|
|
155
|
+
update["updatedTime"] = new Date();
|
|
156
|
+
|
|
157
|
+
if (payment["charges"] && payment["charges"]["data"] && payment["charges"]["data"].length > 0 && payment["charges"]["data"][0]) {
|
|
158
|
+
if (payment["charges"]["data"][0]["receipt_url"]) {
|
|
159
|
+
update["receiptUrl"] = payment["charges"]["data"][0]["receipt_url"];
|
|
160
|
+
}
|
|
161
|
+
if (payment["charges"]["data"][0]["amount_captured"]) {
|
|
162
|
+
update["capturedAmount"] = payment["charges"]["data"][0]["amount_captured"];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
await store.savePurchase(purchase.orderId, update);
|
|
166
|
+
return c.json({
|
|
167
|
+
"success": true,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
case "payment_intent.payment_failed": {
|
|
171
|
+
const payment = event.data.object as {
|
|
172
|
+
[key: string]: any
|
|
173
|
+
};
|
|
174
|
+
const purchaseId = payment["id"];
|
|
175
|
+
const customerId = payment["customer"];
|
|
176
|
+
const status = payment["status"];
|
|
177
|
+
if (!customerId) {
|
|
178
|
+
return c.json({
|
|
179
|
+
"error": "The customer id is not found.",
|
|
180
|
+
}, 404);
|
|
181
|
+
}
|
|
182
|
+
const user = await store.findUserByCustomerId(customerId);
|
|
183
|
+
if (!user) {
|
|
184
|
+
return c.json({
|
|
185
|
+
"error": "The account data is not found.",
|
|
186
|
+
}, 404);
|
|
187
|
+
}
|
|
188
|
+
const purchase = await store.findPurchaseByPurchaseId(purchaseId);
|
|
189
|
+
if (!purchase) {
|
|
190
|
+
return c.json({
|
|
191
|
+
"error": "The purchase data is not found.",
|
|
192
|
+
}, 404);
|
|
193
|
+
}
|
|
194
|
+
const errorMessage = payment["last_payment_error"]?.["message"];
|
|
195
|
+
const update: { [key: string]: any } = {};
|
|
196
|
+
switch (status) {
|
|
197
|
+
case "requires_payment_method":
|
|
198
|
+
case "requires_confirmation": {
|
|
199
|
+
update["confirm"] = false;
|
|
200
|
+
update["verify"] = false;
|
|
201
|
+
update["capture"] = false;
|
|
202
|
+
update["success"] = false;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case "requires_action": {
|
|
206
|
+
update["confirm"] = true;
|
|
207
|
+
update["verify"] = false;
|
|
208
|
+
update["capture"] = false;
|
|
209
|
+
update["success"] = false;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case "requires_capture": {
|
|
213
|
+
update["confirm"] = true;
|
|
214
|
+
update["verify"] = true;
|
|
215
|
+
update["capture"] = false;
|
|
216
|
+
update["success"] = false;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
update["updatedTime"] = new Date();
|
|
221
|
+
update["error"] = true;
|
|
222
|
+
update["errorMessage"] = errorMessage;
|
|
223
|
+
await store.savePurchase(purchase.orderId, update);
|
|
224
|
+
return c.json({
|
|
225
|
+
"success": true,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
case "payment_method.detached":
|
|
229
|
+
case "payment_method.updated": {
|
|
230
|
+
const payment = event.data.object as {
|
|
231
|
+
[key: string]: any
|
|
232
|
+
};
|
|
233
|
+
const previous = event.data.previous_attributes as {
|
|
234
|
+
[key: string]: any
|
|
235
|
+
};
|
|
236
|
+
let customerId = payment["customer"];
|
|
237
|
+
if (!customerId) {
|
|
238
|
+
customerId = previous?.["customer"];
|
|
239
|
+
}
|
|
240
|
+
if (!customerId) {
|
|
241
|
+
return c.json({
|
|
242
|
+
"error": "The customer id is not found.",
|
|
243
|
+
}, 404);
|
|
244
|
+
}
|
|
245
|
+
const user = await store.findUserByCustomerId(customerId);
|
|
246
|
+
if (!user) {
|
|
247
|
+
return c.json({
|
|
248
|
+
"error": "The account data is not found.",
|
|
249
|
+
}, 404);
|
|
250
|
+
}
|
|
251
|
+
await syncStripePayment({
|
|
252
|
+
stripeClient,
|
|
253
|
+
store,
|
|
254
|
+
userId: user.userId,
|
|
255
|
+
customerId,
|
|
256
|
+
});
|
|
257
|
+
return c.json({
|
|
258
|
+
"success": true,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
case "customer.updated": {
|
|
262
|
+
const customer = event.data.object as {
|
|
263
|
+
[key: string]: any
|
|
264
|
+
};
|
|
265
|
+
const customerId = customer["id"];
|
|
266
|
+
if (!customerId) {
|
|
267
|
+
return c.json({
|
|
268
|
+
"error": "The customer id is not found.",
|
|
269
|
+
}, 404);
|
|
270
|
+
}
|
|
271
|
+
const user = await store.findUserByCustomerId(customerId);
|
|
272
|
+
if (!user) {
|
|
273
|
+
return c.json({
|
|
274
|
+
"error": "The account data is not found.",
|
|
275
|
+
}, 404);
|
|
276
|
+
}
|
|
277
|
+
await syncStripePayment({
|
|
278
|
+
stripeClient,
|
|
279
|
+
store,
|
|
280
|
+
userId: user.userId,
|
|
281
|
+
customerId,
|
|
282
|
+
});
|
|
283
|
+
return c.json({
|
|
284
|
+
"success": true,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
case "checkout.session.completed": {
|
|
288
|
+
const session = event.data.object as {
|
|
289
|
+
[key: string]: any
|
|
290
|
+
};
|
|
291
|
+
const customerId = session["customer"];
|
|
292
|
+
if (!customerId) {
|
|
293
|
+
return c.json({
|
|
294
|
+
"error": "The customer id is not found.",
|
|
295
|
+
}, 404);
|
|
296
|
+
}
|
|
297
|
+
const setupIntent = session["setup_intent"];
|
|
298
|
+
if (!setupIntent) {
|
|
299
|
+
return c.json({
|
|
300
|
+
"error": "The setup intent is not found.",
|
|
301
|
+
}, 404);
|
|
302
|
+
}
|
|
303
|
+
const user = await store.findUserByCustomerId(customerId);
|
|
304
|
+
if (!user) {
|
|
305
|
+
return c.json({
|
|
306
|
+
"error": "The account data is not found.",
|
|
307
|
+
}, 404);
|
|
308
|
+
}
|
|
309
|
+
await store.saveUser(user.userId, {
|
|
310
|
+
setupIntent: session["setup_intent"],
|
|
311
|
+
});
|
|
312
|
+
await syncStripePayment({
|
|
313
|
+
stripeClient,
|
|
314
|
+
store,
|
|
315
|
+
userId: user.userId,
|
|
316
|
+
customerId,
|
|
317
|
+
});
|
|
318
|
+
return c.json({
|
|
319
|
+
"success": true,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
case "customer.subscription.trial_will_end":
|
|
323
|
+
case "customer.subscription.updated":
|
|
324
|
+
case "customer.subscription.created": {
|
|
325
|
+
const now = new Date();
|
|
326
|
+
const update: { [key: string]: any } = {};
|
|
327
|
+
const subscription = event.data.object as {
|
|
328
|
+
[key: string]: any
|
|
329
|
+
};
|
|
330
|
+
const status = subscription["status"];
|
|
331
|
+
if (status != "active") {
|
|
332
|
+
return c.json({
|
|
333
|
+
"success": "Subscription is not active.",
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
const endDate = new Date(subscription["current_period_end"] as number * 1000);
|
|
337
|
+
const id = subscription["id"];
|
|
338
|
+
const userId = subscription["metadata"]?.["userId"] as string;
|
|
339
|
+
const orderId = (subscription["metadata"]?.["orderId"] as string) ?? id;
|
|
340
|
+
const plan = subscription["plan"] as {
|
|
341
|
+
[key: string]: any
|
|
342
|
+
} ?? {};
|
|
343
|
+
|
|
344
|
+
const existing = await store.findPurchaseBySubscriptionId(id);
|
|
345
|
+
const targetOrderId = existing?.orderId ?? orderId;
|
|
346
|
+
update["expired"] = now >= endDate;
|
|
347
|
+
if (userId) {
|
|
348
|
+
update["user"] = userId;
|
|
349
|
+
}
|
|
350
|
+
update["@uid"] = orderId;
|
|
351
|
+
update["@time"] = new Date();
|
|
352
|
+
update["subscription"] = id;
|
|
353
|
+
update["application"] = subscription["application"];
|
|
354
|
+
update["application_fee_percent"] = subscription["application_fee_percent"];
|
|
355
|
+
update["cancel_at"] = subscription["cancel_at"];
|
|
356
|
+
update["cancel_at_period_end"] = subscription["cancel_at_period_end"];
|
|
357
|
+
update["canceled_at"] = subscription["canceled_at"];
|
|
358
|
+
update["collection_method"] = subscription["collection_method"];
|
|
359
|
+
update["currency"] = subscription["currency"];
|
|
360
|
+
update["current_period_start"] = subscription["current_period_start"];
|
|
361
|
+
update["current_period_end"] = subscription["current_period_end"];
|
|
362
|
+
update["customer"] = subscription["customer"];
|
|
363
|
+
update["default_payment_method"] = subscription["default_payment_method"];
|
|
364
|
+
update["ended_at"] = subscription["ended_at"];
|
|
365
|
+
update["latest_invoice"] = subscription["latest_invoice"];
|
|
366
|
+
update["price_id"] = plan["id"];
|
|
367
|
+
update["active"] = plan["active"];
|
|
368
|
+
update["amount"] = plan["amount"];
|
|
369
|
+
update["billing_scheme"] = plan["billing_scheme"];
|
|
370
|
+
update["interval"] = plan["interval"];
|
|
371
|
+
update["interval_count"] = plan["interval_count"];
|
|
372
|
+
update["usage_type"] = plan["usage_type"];
|
|
373
|
+
update["quantity"] = subscription["quantity"];
|
|
374
|
+
update["start_date"] = subscription["start_date"];
|
|
375
|
+
console.log(`Subscription status is ${status}.`);
|
|
376
|
+
await store.savePurchase(targetOrderId, update, {
|
|
377
|
+
userId: userId || undefined,
|
|
378
|
+
});
|
|
379
|
+
return c.json({
|
|
380
|
+
"success": "Subscription is active.",
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
case "customer.subscription.deleted": {
|
|
384
|
+
const subscription = event.data.object as {
|
|
385
|
+
[key: string]: any
|
|
386
|
+
};
|
|
387
|
+
const status = subscription["status"];
|
|
388
|
+
const id = subscription["id"];
|
|
389
|
+
console.log(`Subscription status is ${status} expired.`);
|
|
390
|
+
const existing = id ? await store.findPurchaseBySubscriptionId(id) : null;
|
|
391
|
+
if (existing) {
|
|
392
|
+
await store.savePurchase(existing.orderId, {
|
|
393
|
+
expired: true,
|
|
394
|
+
ended_at: subscription["ended_at"],
|
|
395
|
+
canceled_at: subscription["canceled_at"],
|
|
396
|
+
"@time": new Date(),
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
return c.json({
|
|
400
|
+
"success": true,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
default: {
|
|
404
|
+
return c.json({
|
|
405
|
+
"error": "Event is not found.",
|
|
406
|
+
}, 404);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
} catch (err) {
|
|
410
|
+
return jsonError(c, err);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
return hono;
|
|
414
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { jsonError } from "@mathrunet/masamune_cloudflare/dist/lib/src/http_error";
|
|
3
|
+
import {
|
|
4
|
+
resolveStripeClient,
|
|
5
|
+
resolveStripePurchaseStore,
|
|
6
|
+
resolveStripeSecretKey,
|
|
7
|
+
resolveStripeWebhookConnectSecret,
|
|
8
|
+
StripePurchaseWorkersOptions,
|
|
9
|
+
} from "../lib/options";
|
|
10
|
+
import { constructStripeEvent } from "../lib/stripe_client";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Receive and process webhooks for Stripe Connect (parity with the
|
|
14
|
+
* `stripeWebhookConnect` function of
|
|
15
|
+
* `@mathrunet/masamune_firebase_purchase_stripe`).
|
|
16
|
+
* If you do not use Stripe Connect, do not configure it as a Webhook.
|
|
17
|
+
*
|
|
18
|
+
* Stripe Connect用のWebhookを受信して処理します
|
|
19
|
+
* (`@mathrunet/masamune_firebase_purchase_stripe`の`stripeWebhookConnect`と同等)。
|
|
20
|
+
* Stripe Connectを利用しない場合はWebhookとして設定しないでください。
|
|
21
|
+
*
|
|
22
|
+
* @param {string} PURCHASE_STRIPE_SECRETKEY
|
|
23
|
+
* API key (secret key) to connect to Stripe.
|
|
24
|
+
* Stripeへ接続するためのAPIキー(シークレットキー)。
|
|
25
|
+
*
|
|
26
|
+
* @param {string} PURCHASE_STRIPE_WEBHOOKCONNECTSECRET
|
|
27
|
+
* Specify the **Signature Secret** after setting it up as a webhook.
|
|
28
|
+
* Webhookとして設定したあとの**署名シークレット**を指定します。
|
|
29
|
+
*/
|
|
30
|
+
module.exports = (
|
|
31
|
+
hono: Hono,
|
|
32
|
+
options: StripePurchaseWorkersOptions,
|
|
33
|
+
data: { [key: string]: any },
|
|
34
|
+
) => {
|
|
35
|
+
hono.post("/", async (c) => {
|
|
36
|
+
try {
|
|
37
|
+
const apiKey = resolveStripeSecretKey(c, options);
|
|
38
|
+
const webhookConnectSecret = resolveStripeWebhookConnectSecret(c, options);
|
|
39
|
+
const store = resolveStripePurchaseStore(c, options);
|
|
40
|
+
const stripeClient = resolveStripeClient(c, options, apiKey);
|
|
41
|
+
const signature = c.req.header("stripe-signature");
|
|
42
|
+
if (!signature || !webhookConnectSecret) {
|
|
43
|
+
return c.json({
|
|
44
|
+
"error": "Access denied.",
|
|
45
|
+
}, 403);
|
|
46
|
+
}
|
|
47
|
+
const payload = await c.req.text();
|
|
48
|
+
let event;
|
|
49
|
+
try {
|
|
50
|
+
event = await constructStripeEvent({
|
|
51
|
+
client: stripeClient,
|
|
52
|
+
payload,
|
|
53
|
+
signature,
|
|
54
|
+
secret: webhookConnectSecret,
|
|
55
|
+
});
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.warn(err);
|
|
58
|
+
return c.json({
|
|
59
|
+
"error": "Access denied.",
|
|
60
|
+
}, 403);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
switch (event.type) {
|
|
64
|
+
case "account.updated": {
|
|
65
|
+
const account = event.data.object as {
|
|
66
|
+
[key: string]: any
|
|
67
|
+
};
|
|
68
|
+
const id = account["id"];
|
|
69
|
+
if (!id) {
|
|
70
|
+
return c.json({
|
|
71
|
+
"error": "The account id is not found.",
|
|
72
|
+
}, 404);
|
|
73
|
+
}
|
|
74
|
+
const user = await store.findUserByAccountId(id);
|
|
75
|
+
if (!user) {
|
|
76
|
+
return c.json({
|
|
77
|
+
"error": "The account data is not found.",
|
|
78
|
+
}, 404);
|
|
79
|
+
}
|
|
80
|
+
const update: { [key: string]: any } = {};
|
|
81
|
+
if (account["capabilities"]) {
|
|
82
|
+
const capability: { [key: string]: any } = {};
|
|
83
|
+
if (account["capabilities"]["card_payments"]) {
|
|
84
|
+
capability["card_payments"] = true;
|
|
85
|
+
}
|
|
86
|
+
if (account["capabilities"]["transfers"]) {
|
|
87
|
+
capability["transfers"] = true;
|
|
88
|
+
}
|
|
89
|
+
update["capability"] = capability;
|
|
90
|
+
}
|
|
91
|
+
await store.saveUser(user.userId, update);
|
|
92
|
+
return c.json({
|
|
93
|
+
"success": true,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
default: {
|
|
97
|
+
return c.json({
|
|
98
|
+
"error": `Event ${event.type} is not found.`,
|
|
99
|
+
}, 404);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
return jsonError(c, err);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return hono;
|
|
107
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { jsonError } from "@mathrunet/masamune_cloudflare/dist/lib/src/http_error";
|
|
3
|
+
import "@mathrunet/masamune";
|
|
4
|
+
import {
|
|
5
|
+
resolveStripeClient,
|
|
6
|
+
resolveStripePurchaseStore,
|
|
7
|
+
resolveStripeSecretKey,
|
|
8
|
+
StripePurchaseWorkersOptions,
|
|
9
|
+
} from "../lib/options";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Webhook for proper redirection when 3D Secure authentication is required
|
|
13
|
+
* (parity with the `stripeWebhookSecure` function of
|
|
14
|
+
* `@mathrunet/masamune_firebase_purchase_stripe`).
|
|
15
|
+
* Please set here for `returnUrl`.
|
|
16
|
+
*
|
|
17
|
+
* 3Dセキュア認証が必要な場合、適切なリダイレクトを行うためのWebhookです
|
|
18
|
+
* (`@mathrunet/masamune_firebase_purchase_stripe`の`stripeWebhookSecure`と同等)。
|
|
19
|
+
* `returnUrl`にこちらを設定してください。
|
|
20
|
+
*
|
|
21
|
+
* @param {string} PURCHASE_STRIPE_SECRETKEY
|
|
22
|
+
* API key (secret key) to connect to Stripe.
|
|
23
|
+
* Stripeへ接続するためのAPIキー(シークレットキー)。
|
|
24
|
+
*/
|
|
25
|
+
module.exports = (
|
|
26
|
+
hono: Hono,
|
|
27
|
+
options: StripePurchaseWorkersOptions,
|
|
28
|
+
data: { [key: string]: any },
|
|
29
|
+
) => {
|
|
30
|
+
hono.get("/", async (c) => {
|
|
31
|
+
try {
|
|
32
|
+
const apiKey = resolveStripeSecretKey(c, options);
|
|
33
|
+
const store = resolveStripePurchaseStore(c, options);
|
|
34
|
+
const stripeClient = resolveStripeClient(c, options, apiKey);
|
|
35
|
+
const token = c.req.query("token");
|
|
36
|
+
if (!token || typeof token !== "string") {
|
|
37
|
+
return c.json({
|
|
38
|
+
"error": "Invalid parameters",
|
|
39
|
+
}, 403);
|
|
40
|
+
}
|
|
41
|
+
let param: { [key: string]: any };
|
|
42
|
+
try {
|
|
43
|
+
param = JSON.parse(await token.decrypt({
|
|
44
|
+
key: apiKey.slice(0, 32),
|
|
45
|
+
ivKey: apiKey.slice(-16),
|
|
46
|
+
}));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
return c.json({
|
|
49
|
+
"error": "Invalid parameters",
|
|
50
|
+
}, 403);
|
|
51
|
+
}
|
|
52
|
+
if (!param["userId"] || !param["orderId"] || !param["successUrl"] || !param["failureUrl"]) {
|
|
53
|
+
return c.json({
|
|
54
|
+
"error": "Invalid parameters",
|
|
55
|
+
}, 403);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const userId = param["userId"];
|
|
59
|
+
const orderId = param["orderId"];
|
|
60
|
+
const successUrl = param["successUrl"];
|
|
61
|
+
const failureUrl = param["failureUrl"];
|
|
62
|
+
|
|
63
|
+
const purchaseDoc = await store.getPurchase(orderId, userId);
|
|
64
|
+
// Firebase実装は`paymentId`を参照するが、購入作成時に保存されるキーは
|
|
65
|
+
// `purchaseId`のためそちらを正とし、`paymentId`はフォールバックに残す。
|
|
66
|
+
const purchaseId = purchaseDoc?.data["purchaseId"] ?? purchaseDoc?.data["paymentId"];
|
|
67
|
+
if (!purchaseDoc || !purchaseId) {
|
|
68
|
+
return c.json({
|
|
69
|
+
"error": "The purchase data is not found.",
|
|
70
|
+
}, 404);
|
|
71
|
+
}
|
|
72
|
+
const purchase = await stripeClient.paymentIntents.retrieve(
|
|
73
|
+
purchaseId,
|
|
74
|
+
);
|
|
75
|
+
if (!purchase) {
|
|
76
|
+
return c.json({
|
|
77
|
+
"error": "The purchase data is not found.",
|
|
78
|
+
}, 404);
|
|
79
|
+
}
|
|
80
|
+
const status = purchase.status;
|
|
81
|
+
if (status === "requires_capture" || status === "succeeded" || status === "processing") {
|
|
82
|
+
return c.redirect(successUrl);
|
|
83
|
+
} else {
|
|
84
|
+
return c.redirect(failureUrl);
|
|
85
|
+
}
|
|
86
|
+
} catch (err) {
|
|
87
|
+
return jsonError(c, err);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return hono;
|
|
91
|
+
};
|