@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.
Files changed (91) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/LICENSE +21 -0
  3. package/README.md +174 -0
  4. package/dist/functions.d.ts +44 -0
  5. package/dist/functions.js +47 -0
  6. package/dist/functions.js.map +1 -0
  7. package/dist/index.d.ts +25 -0
  8. package/dist/index.js +42 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/lib/meter/d1_usage_event_store.d.ts +128 -0
  11. package/dist/lib/meter/d1_usage_event_store.js +78 -0
  12. package/dist/lib/meter/d1_usage_event_store.js.map +1 -0
  13. package/dist/lib/meter/flush_meter.d.ts +19 -0
  14. package/dist/lib/meter/flush_meter.js +38 -0
  15. package/dist/lib/meter/flush_meter.js.map +1 -0
  16. package/dist/lib/meter/interface.d.ts +109 -0
  17. package/dist/lib/meter/interface.js +3 -0
  18. package/dist/lib/meter/interface.js.map +1 -0
  19. package/dist/lib/meter/kv_usage_buffer.d.ts +56 -0
  20. package/dist/lib/meter/kv_usage_buffer.js +50 -0
  21. package/dist/lib/meter/kv_usage_buffer.js.map +1 -0
  22. package/dist/lib/meter/record_usage.d.ts +29 -0
  23. package/dist/lib/meter/record_usage.js +72 -0
  24. package/dist/lib/meter/record_usage.js.map +1 -0
  25. package/dist/lib/meter/stripe_meter_client.d.ts +76 -0
  26. package/dist/lib/meter/stripe_meter_client.js +74 -0
  27. package/dist/lib/meter/stripe_meter_client.js.map +1 -0
  28. package/dist/lib/options.d.ts +179 -0
  29. package/dist/lib/options.js +132 -0
  30. package/dist/lib/options.js.map +1 -0
  31. package/dist/lib/purchase/d1_purchase_store.d.ts +78 -0
  32. package/dist/lib/purchase/d1_purchase_store.js +194 -0
  33. package/dist/lib/purchase/d1_purchase_store.js.map +1 -0
  34. package/dist/lib/purchase/helpers.d.ts +36 -0
  35. package/dist/lib/purchase/helpers.js +64 -0
  36. package/dist/lib/purchase/helpers.js.map +1 -0
  37. package/dist/lib/purchase/interface.d.ts +182 -0
  38. package/dist/lib/purchase/interface.js +27 -0
  39. package/dist/lib/purchase/interface.js.map +1 -0
  40. package/dist/lib/purchase/sync_payment.d.ts +15 -0
  41. package/dist/lib/purchase/sync_payment.js +79 -0
  42. package/dist/lib/purchase/sync_payment.js.map +1 -0
  43. package/dist/lib/stripe_client.d.ts +55 -0
  44. package/dist/lib/stripe_client.js +50 -0
  45. package/dist/lib/stripe_client.js.map +1 -0
  46. package/dist/meter.d.ts +21 -0
  47. package/dist/meter.js +38 -0
  48. package/dist/meter.js.map +1 -0
  49. package/dist/purchase.d.ts +58 -0
  50. package/dist/purchase.js +48 -0
  51. package/dist/purchase.js.map +1 -0
  52. package/dist/workers/stripe.d.ts +1 -0
  53. package/dist/workers/stripe.js +861 -0
  54. package/dist/workers/stripe.js.map +1 -0
  55. package/dist/workers/stripe_webhook.d.ts +1 -0
  56. package/dist/workers/stripe_webhook.js +384 -0
  57. package/dist/workers/stripe_webhook.js.map +1 -0
  58. package/dist/workers/stripe_webhook_connect.d.ts +1 -0
  59. package/dist/workers/stripe_webhook_connect.js +97 -0
  60. package/dist/workers/stripe_webhook_connect.js.map +1 -0
  61. package/dist/workers/stripe_webhook_secure.d.ts +1 -0
  62. package/dist/workers/stripe_webhook_secure.js +82 -0
  63. package/dist/workers/stripe_webhook_secure.js.map +1 -0
  64. package/jest.config.json +19 -0
  65. package/package.json +56 -0
  66. package/src/functions.ts +48 -0
  67. package/src/index.ts +25 -0
  68. package/src/lib/meter/d1_usage_event_store.ts +176 -0
  69. package/src/lib/meter/flush_meter.ts +45 -0
  70. package/src/lib/meter/interface.ts +118 -0
  71. package/src/lib/meter/kv_usage_buffer.ts +79 -0
  72. package/src/lib/meter/record_usage.ts +87 -0
  73. package/src/lib/meter/stripe_meter_client.ts +117 -0
  74. package/src/lib/options.ts +299 -0
  75. package/src/lib/purchase/d1_purchase_store.ts +268 -0
  76. package/src/lib/purchase/helpers.ts +77 -0
  77. package/src/lib/purchase/interface.ts +215 -0
  78. package/src/lib/purchase/sync_payment.ts +95 -0
  79. package/src/lib/stripe_client.ts +76 -0
  80. package/src/meter.ts +21 -0
  81. package/src/purchase.ts +66 -0
  82. package/src/workers/stripe.ts +881 -0
  83. package/src/workers/stripe_webhook.ts +414 -0
  84. package/src/workers/stripe_webhook_connect.ts +107 -0
  85. package/src/workers/stripe_webhook_secure.ts +91 -0
  86. package/test/helpers/mem_store.ts +97 -0
  87. package/test/meter.test.ts +217 -0
  88. package/test/purchase_store.test.ts +154 -0
  89. package/test/stripe.test.ts +146 -0
  90. package/test/stripe_webhook.test.ts +174 -0
  91. package/tsconfig.json +14 -0
@@ -0,0 +1,881 @@
1
+ import Stripe from "stripe";
2
+ import { Hono } from "hono";
3
+ import { HttpError, jsonError } from "@mathrunet/masamune_cloudflare/dist/lib/src/http_error";
4
+ import "@mathrunet/masamune";
5
+ import {
6
+ resolveStripeClient,
7
+ resolveStripeEmailSender,
8
+ resolveStripePurchaseStore,
9
+ resolveStripeSecretKey,
10
+ StripePurchaseWorkersOptions,
11
+ } from "../lib/options";
12
+ import { resolveDefaultPayment, resolveUserEmail } from "../lib/purchase/helpers";
13
+
14
+ /**
15
+ * Performs various Stripe processes (parity with the `stripe` function of
16
+ * `@mathrunet/masamune_firebase_purchase_stripe`).
17
+ *
18
+ * POST a JSON body with a `mode` and the parameters of that mode. Purchase and
19
+ * user data are stored through [StripePurchaseStore] (D1 by default).
20
+ *
21
+ * Stripeの各種処理を実行します(`@mathrunet/masamune_firebase_purchase_stripe`の
22
+ * `stripe`ファンクションと同等)。
23
+ *
24
+ * `mode`とそのモードのパラメーターを持つJSONボディをPOSTします。購入・ユーザー
25
+ * データは[StripePurchaseStore](デフォルトはD1)を通して保存されます。
26
+ *
27
+ * @param {string} PURCHASE_STRIPE_SECRETKEY
28
+ * API key (secret key) to connect to Stripe.
29
+ * Stripeへ接続するためのAPIキー(シークレットキー)。
30
+ *
31
+ * @param {string} PURCHASE_STRIPE_EMAILPROVIDER
32
+ * Mail provider when sending mail. (sendgrid)
33
+ * メールを送信する際のメールプロバイダー。(sendgrid)
34
+ *
35
+ * @param {string} mode
36
+ * [required]
37
+ * Specifies the mode in which Stripe will run.
38
+ * Stripeの実行するモードを指定します。
39
+ *
40
+ * - create_account / delete_account / get_account / dashboard_account
41
+ * - create_customer_and_payment / set_customer_default_payment / delete_payment / delete_customer
42
+ * - authorization / confirm_authorization
43
+ * - create_purchase / confirm_purchase / capture_purchase / refresh_purchase / cancel_purchase / refund_purchase
44
+ * - create_subscription / delete_subscription
45
+ */
46
+ module.exports = (
47
+ hono: Hono,
48
+ options: StripePurchaseWorkersOptions,
49
+ data: { [key: string]: any },
50
+ ) => {
51
+ hono.post("/", async (c) => {
52
+ try {
53
+ const apiKey = resolveStripeSecretKey(c, options);
54
+ if (!apiKey) {
55
+ throw new HttpError(500, "The Stripe secret key is not configured.");
56
+ }
57
+ const store = resolveStripePurchaseStore(c, options);
58
+ const sendEmail = resolveStripeEmailSender(c, options);
59
+ const stripeClient = resolveStripeClient(c, options, apiKey);
60
+ const query = await c.req.json().catch(() => ({})) as { [key: string]: any };
61
+ switch (query.mode) {
62
+ case "create_account": {
63
+ const userId = query.userId;
64
+ const locale = query.locale;
65
+ const refreshUrl = query.refreshUrl;
66
+ const returnUrl = query.returnUrl;
67
+ if (!locale) {
68
+ throw new HttpError(400, "The locale is empty.");
69
+ }
70
+ if (!userId) {
71
+ throw new HttpError(400, "The user id is empty.");
72
+ }
73
+ const country = locale.split("_")[1];
74
+ const user = await store.getUser(userId);
75
+ if (!user || !user.data["account"]) {
76
+ const account = await stripeClient.accounts.create({
77
+ type: "express",
78
+ country: country ?? "JP",
79
+ });
80
+ await store.saveUser(userId, {
81
+ "@uid": userId,
82
+ "@time": new Date(),
83
+ user: userId,
84
+ account: account.id,
85
+ });
86
+ const endpoint = await stripeClient.accountLinks.create({
87
+ type: "account_onboarding",
88
+ account: account.id,
89
+ refresh_url: refreshUrl,
90
+ return_url: returnUrl,
91
+ });
92
+ return c.json({
93
+ next: "registration",
94
+ endpoint: endpoint.url,
95
+ accountId: account.id,
96
+ });
97
+ } else {
98
+ if (user.data["capability"] && user.data["capability"]["transfers"]) {
99
+ return c.json({
100
+ next: "none",
101
+ });
102
+ }
103
+ const res = await stripeClient.accounts.retrieve(user.data["account"]);
104
+ if (res.capabilities && res.capabilities.transfers === "active") {
105
+ await store.saveUser(userId, {
106
+ capability: {
107
+ transfers: true,
108
+ },
109
+ });
110
+ return c.json({
111
+ next: "none",
112
+ });
113
+ } else {
114
+ const endpoint = await stripeClient.accountLinks.create({
115
+ type: "account_onboarding",
116
+ account: user.data["account"],
117
+ refresh_url: refreshUrl,
118
+ return_url: returnUrl,
119
+ });
120
+ return c.json({
121
+ next: "registration",
122
+ endpoint: endpoint.url,
123
+ accountId: user.data["account"],
124
+ });
125
+ }
126
+ }
127
+ }
128
+ case "delete_account": {
129
+ const userId = query.userId;
130
+ if (!userId) {
131
+ throw new HttpError(400, "The user id is empty.");
132
+ }
133
+ const user = await store.getUser(userId);
134
+ if (!user || !user.data["account"]) {
135
+ throw new HttpError(404, "Account id is not found.");
136
+ }
137
+ await stripeClient.accounts.del(user.data["account"]);
138
+ await store.saveUser(userId, {
139
+ account: null,
140
+ capability: null,
141
+ });
142
+ return c.json({
143
+ success: true,
144
+ });
145
+ }
146
+ case "get_account": {
147
+ const userId = query.userId;
148
+ if (!userId) {
149
+ throw new HttpError(400, "The user id is empty.");
150
+ }
151
+ const user = await store.getUser(userId);
152
+ if (!user || !user.data["account"]) {
153
+ throw new HttpError(404, "Account id is not found.");
154
+ }
155
+ const res = await stripeClient.accounts.retrieve(user.data["account"]);
156
+ return c.json(res);
157
+ }
158
+ case "dashboard_account": {
159
+ const userId = query.userId;
160
+ if (!userId) {
161
+ throw new HttpError(400, "The user id is empty.");
162
+ }
163
+ const user = await store.getUser(userId);
164
+ if (!user || !user.data["account"]) {
165
+ throw new HttpError(404, "Account id is not found.");
166
+ }
167
+ const res = await stripeClient.accounts.createLoginLink(user.data["account"]);
168
+ return c.json({
169
+ endpoint: res.url,
170
+ });
171
+ }
172
+ case "create_customer_and_payment": {
173
+ const userId = query.userId;
174
+ const successUrl = query.successUrl;
175
+ const cancelUrl = query.cancelUrl;
176
+ if (!userId) {
177
+ throw new HttpError(400, "The user id is empty.");
178
+ }
179
+ const user = await store.getUser(userId);
180
+ if (!user || !user.data["customer"]) {
181
+ let email: string | null | undefined = options.resolveUserEmail
182
+ ? await options.resolveUserEmail(c, userId)
183
+ : undefined;
184
+ if (!email && user && typeof user.data["email"] === "string") {
185
+ email = user.data["email"];
186
+ }
187
+ const customer = await stripeClient.customers.create({
188
+ metadata: {
189
+ "user_id": userId,
190
+ },
191
+ email: email ?? undefined,
192
+ });
193
+ await store.saveUser(userId, {
194
+ "@uid": userId,
195
+ "@time": new Date(),
196
+ user: userId,
197
+ customer: customer.id,
198
+ });
199
+ const session = await stripeClient.checkout.sessions.create({
200
+ payment_method_types: ["card"],
201
+ mode: "setup",
202
+ customer: customer.id,
203
+ success_url: successUrl,
204
+ cancel_url: cancelUrl,
205
+ metadata: {
206
+ "user_id": userId,
207
+ },
208
+ });
209
+ return c.json({
210
+ endpoint: session.url,
211
+ customerId: customer.id,
212
+ });
213
+ } else {
214
+ const session = await stripeClient.checkout.sessions.create({
215
+ payment_method_types: ["card"],
216
+ mode: "setup",
217
+ customer: user.data["customer"],
218
+ success_url: successUrl,
219
+ cancel_url: cancelUrl,
220
+ });
221
+ return c.json({
222
+ endpoint: session.url,
223
+ customerId: user.data["customer"],
224
+ });
225
+ }
226
+ }
227
+ case "set_customer_default_payment": {
228
+ const userId = query.userId;
229
+ const paymentId = query.paymentId;
230
+ if (!paymentId) {
231
+ throw new HttpError(400, "The payment id is empty.");
232
+ }
233
+ if (!userId) {
234
+ throw new HttpError(400, "The user id is empty.");
235
+ }
236
+ const user = await store.getUser(userId);
237
+ if (!user || !user.data["customer"]) {
238
+ throw new HttpError(404, "The customer is empty.");
239
+ }
240
+ const payments = await store.listPayments(userId);
241
+ const payment = payments.find((item) => item.paymentId === paymentId);
242
+ if (!payment || !payment.data["id"]) {
243
+ throw new HttpError(404, "The payment method is empty.");
244
+ }
245
+ await stripeClient.customers.update(
246
+ user.data["customer"],
247
+ {
248
+ invoice_settings: {
249
+ default_payment_method: payment.data["id"],
250
+ },
251
+ },
252
+ );
253
+ if (user.data["defaultPayment"] !== payment.data["id"]) {
254
+ await store.saveUser(userId, {
255
+ defaultPayment: payment.data["id"],
256
+ });
257
+ }
258
+ return c.json({
259
+ success: true,
260
+ });
261
+ }
262
+ case "delete_payment": {
263
+ const userId = query.userId;
264
+ const paymentId = query.paymentId;
265
+ if (!paymentId) {
266
+ throw new HttpError(400, "The payment id is empty.");
267
+ }
268
+ if (!userId) {
269
+ throw new HttpError(400, "The user id is empty.");
270
+ }
271
+ const user = await store.getUser(userId);
272
+ if (!user || !user.data["customer"]) {
273
+ throw new HttpError(404, "The customer is empty.");
274
+ }
275
+ const payments = await store.listPayments(userId);
276
+ const payment = payments.find((item) => item.paymentId === paymentId);
277
+ if (!payment || !payment.data["id"]) {
278
+ throw new HttpError(404, "The payment method is empty.");
279
+ }
280
+ await stripeClient.paymentMethods.detach(
281
+ payment.data["id"],
282
+ );
283
+ if (user.data["defaultPayment"] === payment.data["id"]) {
284
+ await store.saveUser(userId, {
285
+ defaultPayment: null,
286
+ });
287
+ }
288
+ return c.json({
289
+ success: true,
290
+ });
291
+ }
292
+ case "delete_customer": {
293
+ const userId = query.userId;
294
+ if (!userId) {
295
+ throw new HttpError(400, "The user id is empty.");
296
+ }
297
+ const user = await store.getUser(userId);
298
+ if (!user || !user.data["customer"]) {
299
+ throw new HttpError(404, "Customer id is not found.");
300
+ }
301
+ await stripeClient.customers.del(user.data["customer"]);
302
+ await store.saveUser(userId, {
303
+ customer: null,
304
+ });
305
+ return c.json({
306
+ success: true,
307
+ });
308
+ }
309
+ case "authorization": {
310
+ const amount = parseFloat(query.amount);
311
+ const currency = query.currency ?? "jpy";
312
+ const returnUrl = query.returnUrl;
313
+ const online = query.online == "true" || query.online === true;
314
+ const emailFrom = query.from;
315
+ const emailTitle = query.title;
316
+ const emailContent = query.content;
317
+ const userId = query.userId;
318
+ if (!userId) {
319
+ throw new HttpError(400, "The user id is empty.");
320
+ }
321
+ const user = await store.getUser(userId);
322
+ if (!user || !user.data["customer"]) {
323
+ throw new HttpError(404, "The customer id is not found.");
324
+ }
325
+ const defaultPayment = await resolveDefaultPayment({ stripeClient, store, user });
326
+ const email = await resolveUserEmail({
327
+ context: c,
328
+ workersOptions: options,
329
+ stripeClient,
330
+ user,
331
+ defaultPayment,
332
+ });
333
+ const paymentIntent = await stripeClient.paymentIntents.create({
334
+ payment_method_types: ["card"],
335
+ amount: amount,
336
+ confirm: false,
337
+ capture_method: "manual",
338
+ payment_method: defaultPayment,
339
+ description: "",
340
+ customer: user.data["customer"],
341
+ receipt_email: email,
342
+ currency: currency ?? "usd",
343
+ setup_future_usage: "off_session",
344
+ });
345
+ if (!paymentIntent) {
346
+ throw new HttpError(500, "The payment is failed.");
347
+ }
348
+ const confirmedPaymentIntent = await stripeClient.paymentIntents.confirm(
349
+ paymentIntent.id,
350
+ {
351
+ return_url: returnUrl,
352
+ },
353
+ );
354
+ const nextActionUrl = confirmedPaymentIntent.next_action?.redirect_to_url?.url ?? "";
355
+ if (nextActionUrl && !online) {
356
+ if (emailFrom && email && emailTitle && emailContent && sendEmail) {
357
+ await sendEmail({
358
+ from: emailFrom,
359
+ to: email,
360
+ subject: emailTitle,
361
+ text: emailContent.replace("{url}", nextActionUrl),
362
+ });
363
+ } else {
364
+ throw new HttpError(503, "3D Secure authentication is required, but the user is offline and no email settings have been configured.");
365
+ }
366
+ }
367
+ return c.json({
368
+ url: online ? nextActionUrl : "",
369
+ returnUrl: online ? confirmedPaymentIntent.next_action?.redirect_to_url?.return_url ?? "" : "",
370
+ authorizedId: paymentIntent.id,
371
+ });
372
+ }
373
+ case "confirm_authorization": {
374
+ const authorizedId = query.authorizedId;
375
+ if (!authorizedId) {
376
+ throw new HttpError(400, "The authorized id is empty.");
377
+ }
378
+ await stripeClient.paymentIntents.cancel(
379
+ authorizedId,
380
+ );
381
+ return c.json({
382
+ success: true,
383
+ });
384
+ }
385
+ case "create_purchase": {
386
+ const amount = parseFloat(query.amount);
387
+ const revenue = parseFloat(query.revenueRatio ?? 0);
388
+ const currency = query.currency ?? "jpy";
389
+ const userId = query.userId;
390
+ const targetUserId = query.targetUserId;
391
+ const orderId = query.orderId;
392
+ const description = query.description;
393
+ const emailFrom = query.emailFrom;
394
+ const emailTitle = query.emailTitle;
395
+ const emailContent = query.emailContent;
396
+ const locale = query.locale;
397
+ if (!orderId) {
398
+ throw new HttpError(400, "The order id is empty.");
399
+ }
400
+ if (!userId) {
401
+ throw new HttpError(400, "The user id is empty.");
402
+ }
403
+ const user = await store.getUser(userId);
404
+ if (!user || !user.data["customer"]) {
405
+ throw new HttpError(404, "The customer id is not found.");
406
+ }
407
+ const defaultPayment = await resolveDefaultPayment({ stripeClient, store, user });
408
+ const email = await resolveUserEmail({
409
+ context: c,
410
+ workersOptions: options,
411
+ stripeClient,
412
+ user,
413
+ defaultPayment,
414
+ });
415
+ let paymentIntent: Stripe.PaymentIntent;
416
+ let targetAccount: string | undefined;
417
+ if (targetUserId) {
418
+ const target = await store.getUser(targetUserId);
419
+ if (!target || !target.data["account"]) {
420
+ throw new HttpError(404, "The target data is not found.");
421
+ }
422
+ targetAccount = target.data["account"];
423
+ paymentIntent = await stripeClient.paymentIntents.create({
424
+ payment_method_types: ["card"],
425
+ amount: amount,
426
+ confirm: false,
427
+ capture_method: "manual",
428
+ payment_method: defaultPayment,
429
+ description: description,
430
+ customer: user.data["customer"],
431
+ metadata: {
432
+ "order_id": orderId,
433
+ },
434
+ receipt_email: email,
435
+ currency: currency,
436
+ setup_future_usage: "off_session",
437
+ application_fee_amount: amount * revenue,
438
+ transfer_data: {
439
+ destination: target.data["account"],
440
+ },
441
+ });
442
+ } else {
443
+ paymentIntent = await stripeClient.paymentIntents.create({
444
+ payment_method_types: ["card"],
445
+ amount: amount,
446
+ confirm: false,
447
+ capture_method: "manual",
448
+ payment_method: defaultPayment,
449
+ description: description,
450
+ customer: user.data["customer"],
451
+ metadata: {
452
+ "order_id": orderId,
453
+ },
454
+ receipt_email: email,
455
+ currency: currency,
456
+ setup_future_usage: "off_session",
457
+ });
458
+ }
459
+ if (!paymentIntent) {
460
+ throw new HttpError(500, "The payment is failed.");
461
+ }
462
+ await store.savePurchase(orderId, {
463
+ "@uid": orderId,
464
+ "@time": new Date(),
465
+ orderId: orderId,
466
+ purchaseId: paymentIntent.id,
467
+ paymentMethodId: defaultPayment,
468
+ confirm: false,
469
+ verify: false,
470
+ capture: false,
471
+ success: false,
472
+ user: userId,
473
+ target: targetUserId ?? undefined,
474
+ nextAction: {
475
+ url: paymentIntent.next_action?.redirect_to_url?.url ?? "",
476
+ returnUrl: paymentIntent.next_action?.redirect_to_url?.return_url ?? "",
477
+ },
478
+ targetAccount: targetAccount ?? undefined,
479
+ customer: user.data["customer"],
480
+ amount: paymentIntent.amount,
481
+ application: paymentIntent.application,
482
+ applicationFeeAmount: paymentIntent.application_fee_amount,
483
+ transferAmount: paymentIntent.transfer_data?.amount ?? 0,
484
+ transferDistination: paymentIntent.transfer_data?.destination ?? "",
485
+ currency: paymentIntent.currency,
486
+ clientSecret: paymentIntent.client_secret,
487
+ createdTime: new Date(paymentIntent.created * 1000),
488
+ updatedTime: new Date(),
489
+ emailFrom: emailFrom,
490
+ emailTo: email,
491
+ emailTitle: emailTitle,
492
+ emailContent: emailContent,
493
+ locale: locale,
494
+ }, { userId });
495
+ return c.json({
496
+ purchaseId: paymentIntent.id,
497
+ });
498
+ }
499
+ case "confirm_purchase": {
500
+ const userId = query.userId;
501
+ const orderId = query.orderId;
502
+ const successUrl = query.successUrl;
503
+ const failureUrl = query.failureUrl;
504
+ let returnUrl = query.returnUrl;
505
+ const online = query.online == "true" || query.online === true;
506
+ if (!orderId) {
507
+ throw new HttpError(400, "The order id is empty.");
508
+ }
509
+ if (!userId) {
510
+ throw new HttpError(400, "The user id is empty.");
511
+ }
512
+ if (!online) {
513
+ returnUrl = returnUrl + "?token=" + await JSON.stringify({
514
+ userId: userId,
515
+ orderId: orderId,
516
+ successUrl: successUrl,
517
+ failureUrl: failureUrl,
518
+ }).encrypt({
519
+ key: apiKey.slice(0, 32),
520
+ ivKey: apiKey.slice(-16),
521
+ });
522
+ }
523
+ const purchase = await store.getPurchase(orderId, userId);
524
+ if (!purchase || !purchase.data["purchaseId"]) {
525
+ throw new HttpError(404, "The purchase data is invalid.");
526
+ }
527
+ const purchaseData = purchase.data;
528
+ if (purchaseData["error"]) {
529
+ throw new HttpError(409, "The purchase data has some errors");
530
+ }
531
+ if (purchaseData["cancel"]) {
532
+ throw new HttpError(409, "The purchase data is already canceled.");
533
+ }
534
+ if (purchaseData["confirm"]) {
535
+ if (purchaseData["verify"]) {
536
+ return c.json({
537
+ url: "",
538
+ returnUrl: "",
539
+ purchaseId: purchaseData["purchaseId"],
540
+ });
541
+ }
542
+ try {
543
+ const paymentIntent = await stripeClient.paymentIntents.retrieve(
544
+ purchaseData["purchaseId"],
545
+ );
546
+ const nextActionUrl = paymentIntent.next_action?.redirect_to_url?.url ?? "";
547
+ const update: { [key: string]: any } = {};
548
+ if (nextActionUrl) {
549
+ if (!online) {
550
+ if (purchaseData["emailFrom"] && purchaseData["emailTo"] && purchaseData["emailTitle"] && purchaseData["emailContent"] && sendEmail) {
551
+ await sendEmail({
552
+ from: purchaseData["emailFrom"],
553
+ to: purchaseData["emailTo"],
554
+ subject: purchaseData["emailTitle"],
555
+ text: purchaseData["emailContent"].replace("{url}", nextActionUrl),
556
+ });
557
+ } else {
558
+ update["error"] = true;
559
+ update["errorMessage"] = "3D Secure authentication is required, but the user is offline and no email settings have been configured.";
560
+ }
561
+ }
562
+ update["nextAction"] = {
563
+ url: nextActionUrl,
564
+ returnUrl: paymentIntent.next_action?.redirect_to_url?.return_url ?? "",
565
+ };
566
+ await store.savePurchase(orderId, update, { userId });
567
+ return c.json({
568
+ url: online ? nextActionUrl : "",
569
+ returnUrl: online ? paymentIntent.next_action?.redirect_to_url?.return_url ?? "" : "",
570
+ purchaseId: purchaseData["purchaseId"],
571
+ });
572
+ } else {
573
+ update["verify"] = true;
574
+ update["nextAction"] = null;
575
+ await store.savePurchase(orderId, update, { userId });
576
+ return c.json({
577
+ url: "",
578
+ returnUrl: "",
579
+ purchaseId: purchaseData["purchaseId"],
580
+ });
581
+ }
582
+ } catch (err) {
583
+ if (err instanceof HttpError) {
584
+ throw err;
585
+ }
586
+ await store.savePurchase(orderId, {
587
+ error: true,
588
+ errorMessage: "The Purchase confirmation failed. Please replace the billing information and Refresh.",
589
+ }, { userId });
590
+ throw err;
591
+ }
592
+ } else {
593
+ try {
594
+ const paymentIntent = await stripeClient.paymentIntents.confirm(
595
+ purchaseData["purchaseId"],
596
+ {
597
+ return_url: returnUrl,
598
+ },
599
+ );
600
+ const nextActionUrl = paymentIntent.next_action?.redirect_to_url?.url ?? "";
601
+ const update: { [key: string]: any } = {};
602
+ if (nextActionUrl && !online) {
603
+ if (purchaseData["emailFrom"] && purchaseData["emailTo"] && purchaseData["emailTitle"] && purchaseData["emailContent"] && sendEmail) {
604
+ await sendEmail({
605
+ from: purchaseData["emailFrom"],
606
+ to: purchaseData["emailTo"],
607
+ subject: purchaseData["emailTitle"],
608
+ text: purchaseData["emailContent"].replace("{url}", nextActionUrl),
609
+ });
610
+ } else {
611
+ update["error"] = true;
612
+ update["errorMessage"] = "3D Secure authentication is required, but the user is offline and no email settings have been configured.";
613
+ }
614
+ }
615
+ update["nextAction"] = {
616
+ url: nextActionUrl,
617
+ returnUrl: paymentIntent.next_action?.redirect_to_url?.return_url ?? "",
618
+ };
619
+ await store.savePurchase(orderId, update, { userId });
620
+ return c.json({
621
+ url: online ? nextActionUrl : "",
622
+ returnUrl: online ? paymentIntent.next_action?.redirect_to_url?.return_url ?? "" : "",
623
+ purchaseId: purchaseData["purchaseId"],
624
+ });
625
+ } catch (err) {
626
+ if (err instanceof HttpError) {
627
+ throw err;
628
+ }
629
+ await store.savePurchase(orderId, {
630
+ error: true,
631
+ errorMessage: "The Purchase confirmation failed. Please replace the billing information and Refresh.",
632
+ }, { userId });
633
+ throw err;
634
+ }
635
+ }
636
+ }
637
+ case "capture_purchase": {
638
+ const userId = query.userId;
639
+ const orderId = query.orderId;
640
+ const amount = parseFloat(query.amount ?? 0.0);
641
+ if (!orderId) {
642
+ throw new HttpError(400, "The order id is empty.");
643
+ }
644
+ if (!userId) {
645
+ throw new HttpError(400, "The user id is empty.");
646
+ }
647
+ const purchase = await store.getPurchase(orderId, userId);
648
+ if (!purchase || !purchase.data["purchaseId"]) {
649
+ throw new HttpError(404, "The purchase data is invalid.");
650
+ }
651
+ const purchaseData = purchase.data;
652
+ if (purchaseData["error"]) {
653
+ throw new HttpError(409, "The purchase data has some errors");
654
+ }
655
+ if (purchaseData["cancel"]) {
656
+ throw new HttpError(409, "This purchase data has already been cancelled.");
657
+ }
658
+ if (!purchaseData["confirm"] || !purchaseData["verify"]) {
659
+ throw new HttpError(412, "The purchase data is not confirmed.");
660
+ }
661
+ if (purchaseData["capture"]) {
662
+ return c.json({
663
+ purchaseId: purchaseData["purchaseId"],
664
+ });
665
+ }
666
+ if (purchaseData["amount"] < amount) {
667
+ throw new HttpError(400, "You cannot capture an amount higher than the billing amount already saved.");
668
+ }
669
+ try {
670
+ const paymentIntent = await (amount > 0 ? stripeClient.paymentIntents.capture(
671
+ purchaseData["purchaseId"],
672
+ {
673
+ amount_to_capture: amount,
674
+ },
675
+ ) : stripeClient.paymentIntents.capture(
676
+ purchaseData["purchaseId"],
677
+ ));
678
+ if (paymentIntent.status !== "succeeded") {
679
+ throw new HttpError(409, "The Payment capture failed.");
680
+ }
681
+ return c.json({
682
+ purchaseId: purchaseData["purchaseId"],
683
+ });
684
+ } catch (err) {
685
+ await store.savePurchase(orderId, {
686
+ error: true,
687
+ errorMessage: "The Purchase capture failed. Please replace the billing information and Refresh.",
688
+ }, { userId });
689
+ throw err;
690
+ }
691
+ }
692
+ case "refresh_purchase": {
693
+ const orderId = query.orderId;
694
+ const userId = query.userId;
695
+ if (!orderId) {
696
+ throw new HttpError(400, "The order id is empty.");
697
+ }
698
+ if (!userId) {
699
+ throw new HttpError(400, "The user id is empty.");
700
+ }
701
+ const purchase = await store.getPurchase(orderId, userId);
702
+ if (!purchase || !purchase.data["purchaseId"]) {
703
+ throw new HttpError(404, "The purchase data is invalid.");
704
+ }
705
+ const purchaseData = purchase.data;
706
+ if (purchaseData["success"]) {
707
+ throw new HttpError(409, "The payment has already been succeed.");
708
+ }
709
+ if (!purchaseData["error"]) {
710
+ return c.json({
711
+ success: true,
712
+ });
713
+ }
714
+ const user = await store.getUser(userId);
715
+ if (!user || !user.data["customer"]) {
716
+ throw new HttpError(404, "The customer id is not found.");
717
+ }
718
+ const defaultPayment = await resolveDefaultPayment({ stripeClient, store, user });
719
+ if (defaultPayment === purchaseData["payment_method"]) {
720
+ throw new HttpError(412, "There was no change in the Payment method.");
721
+ }
722
+ await stripeClient.paymentIntents.update(
723
+ purchaseData["purchaseId"],
724
+ {
725
+ payment_method: defaultPayment,
726
+ },
727
+ );
728
+ await store.savePurchase(orderId, {
729
+ paymentMethodId: defaultPayment,
730
+ error: null,
731
+ errorMessage: null,
732
+ }, { userId });
733
+ return c.json({
734
+ success: true,
735
+ });
736
+ }
737
+ case "cancel_purchase": {
738
+ const orderId = query.orderId;
739
+ const userId = query.userId;
740
+ if (!orderId) {
741
+ throw new HttpError(400, "The order id is empty.");
742
+ }
743
+ if (!userId) {
744
+ throw new HttpError(400, "The user id is empty.");
745
+ }
746
+ const purchase = await store.getPurchase(orderId, userId);
747
+ if (!purchase || !purchase.data["purchaseId"]) {
748
+ throw new HttpError(404, "The purchase data is invalid.");
749
+ }
750
+ const purchaseData = purchase.data;
751
+ if (purchaseData["cancel"]) {
752
+ return c.json({
753
+ success: true,
754
+ });
755
+ }
756
+ if (purchaseData["capture"] || purchaseData["success"]) {
757
+ throw new HttpError(412, "The payment has already been completed.");
758
+ }
759
+ await stripeClient.paymentIntents.cancel(
760
+ purchaseData["purchaseId"],
761
+ );
762
+ await store.savePurchase(orderId, {
763
+ cancel: true,
764
+ error: null,
765
+ errorMessage: null,
766
+ }, { userId });
767
+ return c.json({
768
+ success: true,
769
+ });
770
+ }
771
+ case "refund_purchase": {
772
+ const orderId = query.orderId;
773
+ const userId = query.userId;
774
+ const amount = parseFloat(query.amount ?? 0.0);
775
+ if (!orderId) {
776
+ throw new HttpError(400, "The order id is empty.");
777
+ }
778
+ if (!userId) {
779
+ throw new HttpError(400, "The user id is empty.");
780
+ }
781
+ const purchase = await store.getPurchase(orderId, userId);
782
+ if (!purchase || !purchase.data["purchaseId"]) {
783
+ throw new HttpError(404, "The purchase data is invalid.");
784
+ }
785
+ const purchaseData = purchase.data;
786
+ if (!purchaseData["capture"] || !purchaseData["success"]) {
787
+ throw new HttpError(412, "The payment is not yet in your jurisdiction.");
788
+ }
789
+ if (purchaseData["amount"] < amount) {
790
+ throw new HttpError(400, "The amount to be refunded exceeds the original amount.");
791
+ }
792
+ try {
793
+ if (amount > 0) {
794
+ await stripeClient.refunds.create({
795
+ payment_intent: purchaseData["purchaseId"],
796
+ amount: amount,
797
+ });
798
+ } else {
799
+ await stripeClient.refunds.create({
800
+ payment_intent: purchaseData["purchaseId"],
801
+ });
802
+ }
803
+ await store.savePurchase(orderId, {
804
+ refund: true,
805
+ cancel: true,
806
+ }, { userId });
807
+ return c.json({
808
+ success: true,
809
+ });
810
+ } catch (err) {
811
+ await store.savePurchase(orderId, {
812
+ error: true,
813
+ errorMessage: "The Purchase confirmation failed. Please replace the billing information and Refresh.",
814
+ }, { userId });
815
+ throw err;
816
+ }
817
+ }
818
+ case "create_subscription": {
819
+ const productId = query.productId;
820
+ const orderId = query.orderId;
821
+ const userId = query.userId;
822
+ const count = query.count ?? 1;
823
+ const successUrl = query.successUrl;
824
+ const cancelUrl = query.cancelUrl;
825
+ if (!orderId) {
826
+ throw new HttpError(400, "The order id is empty.");
827
+ }
828
+ if (!userId) {
829
+ throw new HttpError(400, "The user id is empty.");
830
+ }
831
+ if (!productId) {
832
+ throw new HttpError(400, "The product id is empty.");
833
+ }
834
+ const res = await stripeClient.checkout.sessions.create({
835
+ billing_address_collection: "auto",
836
+ subscription_data: {
837
+ metadata: {
838
+ "userId": userId,
839
+ "orderId": orderId,
840
+ },
841
+ },
842
+ line_items: [
843
+ {
844
+ price: productId,
845
+ quantity: count,
846
+ },
847
+ ],
848
+ mode: "subscription",
849
+ success_url: successUrl,
850
+ cancel_url: cancelUrl,
851
+ });
852
+ return c.json({
853
+ endpoint: res.url,
854
+ });
855
+ }
856
+ case "delete_subscription": {
857
+ const orderId = query.orderId;
858
+ if (!orderId) {
859
+ throw new HttpError(400, "The order id is empty.");
860
+ }
861
+ const purchase = await store.getPurchase(orderId);
862
+ if (!purchase || !purchase.data["subscription"]) {
863
+ throw new HttpError(404, "The orderId data is not found");
864
+ }
865
+ const res = await stripeClient.subscriptions.update(purchase.data["subscription"], {
866
+ cancel_at_period_end: true,
867
+ });
868
+ return c.json({
869
+ success: res.cancel_at_period_end,
870
+ });
871
+ }
872
+ default: {
873
+ throw new HttpError(404, "There is no mode:" + query.mode);
874
+ }
875
+ }
876
+ } catch (err) {
877
+ return jsonError(c, err);
878
+ }
879
+ });
880
+ return hono;
881
+ };