payment-kit 1.29.0 → 1.29.2

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 (312) hide show
  1. package/api/dev.ts +41 -2
  2. package/api/hono.d.ts +42 -0
  3. package/api/node-sqlite.d.ts +12 -0
  4. package/api/src/bootstrap.ts +36 -0
  5. package/api/src/crons/base.ts +3 -3
  6. package/api/src/crons/currency.ts +1 -1
  7. package/api/src/crons/index.ts +27 -24
  8. package/api/src/crons/metering-subscription-detection.ts +1 -1
  9. package/api/src/crons/overdue-detection.ts +2 -2
  10. package/api/src/crons/retry-pending-events.ts +6 -0
  11. package/api/src/index.ts +22 -161
  12. package/api/src/integrations/app-store/client.ts +3 -4
  13. package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
  14. package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
  15. package/api/src/integrations/arcblock/token.ts +21 -7
  16. package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
  17. package/api/src/integrations/google-play/handlers/voided.ts +2 -2
  18. package/api/src/integrations/google-play/verify.ts +3 -2
  19. package/api/src/integrations/iap-reconcile.ts +3 -5
  20. package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
  21. package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
  22. package/api/src/libs/archive/query.ts +19 -0
  23. package/api/src/libs/audit.ts +61 -4
  24. package/api/src/libs/auth.ts +99 -38
  25. package/api/src/libs/context.ts +78 -1
  26. package/api/src/libs/currency.ts +2 -2
  27. package/api/src/libs/dayjs.ts +8 -2
  28. package/api/src/libs/drivers/auth-storage.ts +118 -0
  29. package/api/src/libs/drivers/cron.ts +264 -0
  30. package/api/src/libs/drivers/db.ts +170 -0
  31. package/api/src/libs/drivers/identity.ts +81 -0
  32. package/api/src/libs/drivers/index.ts +40 -0
  33. package/api/src/libs/drivers/locks.ts +226 -0
  34. package/api/src/libs/drivers/migrate-runner.ts +70 -0
  35. package/api/src/libs/drivers/queue.ts +104 -0
  36. package/api/src/libs/drivers/secrets.ts +194 -0
  37. package/api/src/libs/env.ts +170 -54
  38. package/api/src/libs/exchange-rate/service.ts +7 -6
  39. package/api/src/libs/http-fetch-adapter.ts +50 -0
  40. package/api/src/libs/invoice.ts +1 -1
  41. package/api/src/libs/lock.ts +51 -47
  42. package/api/src/libs/logger.ts +48 -8
  43. package/api/src/libs/notification/index.ts +1 -1
  44. package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
  45. package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
  46. package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
  47. package/api/src/libs/overdraft-protection.ts +1 -1
  48. package/api/src/libs/payout.ts +1 -1
  49. package/api/src/libs/queue/index.ts +259 -52
  50. package/api/src/libs/queue/runtime.ts +175 -0
  51. package/api/src/libs/resource.ts +3 -3
  52. package/api/src/libs/secrets.ts +38 -0
  53. package/api/src/libs/session.ts +3 -2
  54. package/api/src/libs/subscription.ts +5 -5
  55. package/api/src/libs/tenant.ts +92 -0
  56. package/api/src/libs/url.ts +3 -3
  57. package/api/src/libs/util.ts +21 -13
  58. package/api/src/middlewares/hono/cdn.ts +63 -0
  59. package/api/src/middlewares/hono/context.ts +73 -0
  60. package/api/src/middlewares/hono/csrf.ts +72 -0
  61. package/api/src/middlewares/hono/fallback.ts +194 -0
  62. package/api/src/middlewares/hono/pipeline.ts +73 -0
  63. package/api/src/middlewares/hono/resource-mount.ts +42 -0
  64. package/api/src/middlewares/hono/resource.ts +63 -0
  65. package/api/src/middlewares/hono/security.ts +214 -0
  66. package/api/src/middlewares/hono/session.ts +114 -0
  67. package/api/src/middlewares/hono/xss.ts +61 -0
  68. package/api/src/queues/auto-recharge.ts +12 -10
  69. package/api/src/queues/checkout-session.ts +17 -12
  70. package/api/src/queues/credit-consume.ts +40 -36
  71. package/api/src/queues/credit-grant.ts +25 -18
  72. package/api/src/queues/credit-reconciliation.ts +7 -5
  73. package/api/src/queues/discount-status.ts +9 -6
  74. package/api/src/queues/event.ts +12 -4
  75. package/api/src/queues/exchange-rate-health.ts +49 -30
  76. package/api/src/queues/invoice.ts +18 -15
  77. package/api/src/queues/notification.ts +14 -7
  78. package/api/src/queues/payment.ts +41 -28
  79. package/api/src/queues/payout.ts +9 -5
  80. package/api/src/queues/refund.ts +18 -12
  81. package/api/src/queues/subscription.ts +83 -53
  82. package/api/src/queues/token-transfer.ts +15 -10
  83. package/api/src/queues/usage-record.ts +8 -5
  84. package/api/src/queues/vendors/commission.ts +7 -5
  85. package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
  86. package/api/src/queues/vendors/fulfillment.ts +4 -2
  87. package/api/src/queues/vendors/return-processor.ts +5 -3
  88. package/api/src/queues/vendors/return-scanner.ts +5 -4
  89. package/api/src/queues/vendors/status-check.ts +10 -7
  90. package/api/src/queues/webhook.ts +60 -32
  91. package/api/src/routes/connect/shared.ts +1 -2
  92. package/api/src/routes/connect/subscribe.ts +3 -3
  93. package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
  94. package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
  95. package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
  96. package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
  97. package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
  98. package/api/src/routes/hono/credit-tokens.ts +43 -0
  99. package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
  100. package/api/src/routes/{customers.ts → hono/customers.ts} +193 -223
  101. package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
  102. package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
  103. package/api/src/routes/{events.ts → hono/events.ts} +107 -71
  104. package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
  105. package/api/src/routes/hono/exchange-rates.ts +77 -0
  106. package/api/src/routes/hono/index.ts +115 -0
  107. package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
  108. package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
  109. package/api/src/routes/hono/integrations/stripe.ts +74 -0
  110. package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
  111. package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
  112. package/api/src/routes/hono/meters.ts +288 -0
  113. package/api/src/routes/hono/passports.ts +73 -0
  114. package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
  115. package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
  116. package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
  117. package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
  118. package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
  119. package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
  120. package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
  121. package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
  122. package/api/src/routes/{products.ts → hono/products.ts} +172 -159
  123. package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
  124. package/api/src/routes/hono/redirect.ts +24 -0
  125. package/api/src/routes/{refunds.ts → hono/refunds.ts} +96 -80
  126. package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
  127. package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
  128. package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
  129. package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
  130. package/api/src/routes/hono/tool.ts +69 -0
  131. package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
  132. package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
  133. package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
  134. package/api/src/routes/hono/webhook-endpoints.ts +126 -0
  135. package/api/src/service.ts +667 -0
  136. package/api/src/store/migrations/20230911-seeding.ts +2 -1
  137. package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
  138. package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
  139. package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
  140. package/api/src/store/models/auto-recharge-config.ts +22 -10
  141. package/api/src/store/models/checkout-session.ts +15 -14
  142. package/api/src/store/models/coupon.ts +29 -20
  143. package/api/src/store/models/credit-grant.ts +38 -29
  144. package/api/src/store/models/credit-transaction.ts +32 -21
  145. package/api/src/store/models/customer.ts +19 -17
  146. package/api/src/store/models/discount.ts +11 -2
  147. package/api/src/store/models/entitlement-grant.ts +21 -9
  148. package/api/src/store/models/entitlement-product.ts +21 -9
  149. package/api/src/store/models/entitlement.ts +19 -10
  150. package/api/src/store/models/event.ts +18 -9
  151. package/api/src/store/models/exchange-rate-provider.ts +17 -4
  152. package/api/src/store/models/invoice-item.ts +18 -9
  153. package/api/src/store/models/invoice.ts +16 -8
  154. package/api/src/store/models/meter-event.ts +27 -9
  155. package/api/src/store/models/meter.ts +31 -22
  156. package/api/src/store/models/payment-currency.ts +25 -8
  157. package/api/src/store/models/payment-intent.ts +15 -6
  158. package/api/src/store/models/payment-link.ts +15 -6
  159. package/api/src/store/models/payment-method.ts +38 -22
  160. package/api/src/store/models/payment-stat.ts +18 -9
  161. package/api/src/store/models/payout.ts +15 -6
  162. package/api/src/store/models/price-quote.ts +17 -8
  163. package/api/src/store/models/price.ts +24 -12
  164. package/api/src/store/models/pricing-table.ts +29 -20
  165. package/api/src/store/models/product-vendor.ts +20 -10
  166. package/api/src/store/models/product.ts +15 -6
  167. package/api/src/store/models/promotion-code.ts +14 -6
  168. package/api/src/store/models/refund.ts +15 -6
  169. package/api/src/store/models/revenue-snapshot.ts +21 -9
  170. package/api/src/store/models/setting.ts +18 -9
  171. package/api/src/store/models/setup-intent.ts +36 -27
  172. package/api/src/store/models/subscription-item.ts +21 -9
  173. package/api/src/store/models/subscription-schedule.ts +21 -9
  174. package/api/src/store/models/subscription.ts +21 -10
  175. package/api/src/store/models/tax-rate.ts +29 -21
  176. package/api/src/store/models/usage-record.ts +11 -2
  177. package/api/src/store/models/webhook-attempt.ts +18 -9
  178. package/api/src/store/models/webhook-endpoint.ts +18 -9
  179. package/api/src/store/scoped-core.ts +55 -0
  180. package/api/src/store/scoped.ts +247 -0
  181. package/api/src/store/sequelize.ts +66 -22
  182. package/api/src/store/sql-migrations.ts +20 -0
  183. package/api/src/store/tenant-backfill.ts +260 -0
  184. package/api/src/store/tenant-model.ts +124 -0
  185. package/api/src/store/tenant-tables.ts +50 -0
  186. package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
  187. package/api/tests/fixtures/bare-query-violation.ts +13 -0
  188. package/api/tests/fixtures/core-env-violation.ts +10 -0
  189. package/api/tests/fixtures/host-read-violation.ts +19 -0
  190. package/api/tests/fixtures/tenants.ts +4 -0
  191. package/api/tests/integrations/iap-tenant.spec.ts +284 -0
  192. package/api/tests/libs/archive-query.spec.ts +26 -0
  193. package/api/tests/libs/audit-tenant.spec.ts +153 -0
  194. package/api/tests/libs/context.spec.ts +204 -0
  195. package/api/tests/libs/core-config.spec.ts +115 -0
  196. package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
  197. package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
  198. package/api/tests/libs/lock-tenant.spec.ts +66 -0
  199. package/api/tests/libs/scoped.spec.ts +222 -0
  200. package/api/tests/libs/secrets-facade.spec.ts +52 -0
  201. package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
  202. package/api/tests/libs/tenant-middleware.spec.ts +42 -0
  203. package/api/tests/libs/tenant-scanner.spec.ts +120 -0
  204. package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
  205. package/api/tests/middlewares/hono/context.spec.ts +113 -0
  206. package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
  207. package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
  208. package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
  209. package/api/tests/middlewares/hono/security.spec.ts +181 -0
  210. package/api/tests/middlewares/hono/session.spec.ts +42 -0
  211. package/api/tests/middlewares/hono/xss.spec.ts +81 -0
  212. package/api/tests/models/tenant-backfill.spec.ts +287 -0
  213. package/api/tests/models/tenant-columns-model.spec.ts +46 -0
  214. package/api/tests/models/tenant-columns.spec.ts +161 -0
  215. package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
  216. package/api/tests/queues/credit-consume.spec.ts +8 -1
  217. package/api/tests/queues/event-tenant.spec.ts +236 -0
  218. package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
  219. package/api/tests/queues/queue-parity.spec.ts +249 -0
  220. package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
  221. package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
  222. package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
  223. package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
  224. package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
  225. package/api/tests/service/collapse.spec.ts +96 -0
  226. package/api/tests/store/tenant-crosscut.spec.ts +202 -0
  227. package/api/tests/store/tenant-model-spike.spec.ts +177 -0
  228. package/api/tests/store/tenant-model.spec.ts +162 -0
  229. package/api/tests/store/tenant-residual.spec.ts +196 -0
  230. package/api/third.d.ts +4 -0
  231. package/blocklet.yml +1 -1
  232. package/cloudflare/README.md +26 -6
  233. package/cloudflare/build.ts +28 -13
  234. package/cloudflare/did-connect-auth.ts +0 -217
  235. package/cloudflare/docs/2026-06-10-bundle-size-analysis.md +288 -0
  236. package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
  237. package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
  238. package/cloudflare/migrations/0008_schema_parity.sql +16 -0
  239. package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
  240. package/cloudflare/queue-runtime-mode.ts +13 -0
  241. package/cloudflare/run-build.js +31 -56
  242. package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
  243. package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
  244. package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
  245. package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
  246. package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
  247. package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
  248. package/cloudflare/shims/blocklet-sdk/util-csrf.ts +13 -0
  249. package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
  250. package/cloudflare/shims/cron.ts +38 -158
  251. package/cloudflare/shims/events.ts +124 -0
  252. package/cloudflare/shims/fastq.ts +15 -1
  253. package/cloudflare/shims/nedb-storage.ts +16 -8
  254. package/cloudflare/shims/node-fetch.ts +35 -0
  255. package/cloudflare/shims/xss.ts +8 -0
  256. package/cloudflare/tenant-middleware.ts +36 -0
  257. package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
  258. package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
  259. package/cloudflare/worker.ts +204 -433
  260. package/cloudflare/wrangler.local-e2e.jsonc +26 -0
  261. package/jest.config.js +3 -1
  262. package/package.json +33 -38
  263. package/scripts/core-env-whitelist.json +1 -0
  264. package/scripts/e2e-12b-runtime.ts +149 -0
  265. package/scripts/e2e-core-config.ts +125 -0
  266. package/scripts/e2e-d1-tenancy.ts +116 -0
  267. package/scripts/e2e-d2-cron-queue.ts +139 -0
  268. package/scripts/e2e-d3-embedded-multi.ts +171 -0
  269. package/scripts/e2e-hono-s2.ts +125 -0
  270. package/scripts/e2e-hono-s3e.ts +135 -0
  271. package/scripts/e2e-hono-s4.ts +114 -0
  272. package/scripts/e2e-migration-contract.ts +100 -0
  273. package/scripts/e2e-s0.ts +61 -0
  274. package/scripts/e2e-s1.ts +107 -0
  275. package/scripts/e2e-s2.ts +178 -0
  276. package/scripts/e2e-s3.ts +110 -0
  277. package/scripts/e2e-s4.ts +191 -0
  278. package/scripts/e2e-s5.ts +139 -0
  279. package/scripts/e2e-s6.ts +127 -0
  280. package/scripts/e2e-tenant-model.ts +119 -0
  281. package/scripts/e2e-tenant-worker.ts +199 -0
  282. package/scripts/gen-sql-migrations.js +46 -0
  283. package/scripts/phase8-codemod.js +219 -0
  284. package/scripts/phase9a-env-getters-codemod.js +82 -0
  285. package/scripts/scan-core-env.js +109 -0
  286. package/scripts/scan-tenant-queries.js +235 -0
  287. package/scripts/schema-drift-guard.ts +210 -0
  288. package/scripts/tenant-scan-whitelist.json +1 -0
  289. package/src/env.d.ts +13 -1
  290. package/tsconfig.json +1 -1
  291. package/api/src/libs/did-space.ts +0 -235
  292. package/api/src/libs/middleware.ts +0 -50
  293. package/api/src/libs/security.ts +0 -192
  294. package/api/src/queues/space.ts +0 -662
  295. package/api/src/routes/credit-tokens.ts +0 -38
  296. package/api/src/routes/exchange-rates.ts +0 -87
  297. package/api/src/routes/index.ts +0 -142
  298. package/api/src/routes/integrations/stripe.ts +0 -61
  299. package/api/src/routes/meters.ts +0 -274
  300. package/api/src/routes/passports.ts +0 -68
  301. package/api/src/routes/redirect.ts +0 -20
  302. package/api/src/routes/tool.ts +0 -65
  303. package/api/src/routes/webhook-endpoints.ts +0 -126
  304. package/api/tests/routes/credit-grants.spec.ts +0 -1261
  305. package/cloudflare/shims/did-space-js.ts +0 -17
  306. package/cloudflare/shims/did-space.ts +0 -11
  307. package/cloudflare/shims/express-compat/index.ts +0 -80
  308. package/cloudflare/shims/express-compat/types.ts +0 -41
  309. package/cloudflare/shims/lock.ts +0 -115
  310. package/cloudflare/shims/queue.ts +0 -611
  311. package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
  312. package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
@@ -1,21 +1,26 @@
1
- import { Router } from 'express';
1
+ // Phase 3 (express→hono) hono fork of routes/settings.ts. Sub-app with
2
+ // routes relative to /api/settings (mounted via mountResourceGroup). The
3
+ // business logic is unchanged; only the express plumbing becomes hono:
4
+ // req.body → c.get('sanitizedBody'); res.status(n).json(x) → c.json(x, n).
5
+ import { Hono } from 'hono';
2
6
  import pick from 'lodash/pick';
3
7
  import { Op, type WhereOptions } from 'sequelize';
4
8
 
5
9
  import Joi from 'joi';
6
10
  import merge from 'lodash/merge';
7
- import { PaymentCurrency } from '../store/models/payment-currency';
8
- import { PaymentMethod } from '../store/models/payment-method';
9
- import { authenticate } from '../libs/security';
10
- import { Setting } from '../store/models';
11
- import logger from '../libs/logger';
12
- import { createListParamSchema, getOrder, getWhereFromKvQuery } from '../libs/api';
11
+ import { PaymentCurrency } from '../../store/models/payment-currency';
12
+ import { PaymentMethod } from '../../store/models/payment-method';
13
+ import { authenticate } from '../../middlewares/hono/security';
14
+ import { Setting } from '../../store/models';
15
+ import logger from '../../libs/logger';
16
+ import { createListParamSchema, getOrder, getWhereFromKvQuery } from '../../libs/api';
13
17
 
14
- const router = Router();
18
+ const app = new Hono();
15
19
  const authAdmin = authenticate<Setting>({ component: true, roles: ['owner', 'admin'] });
16
- router.get('/', async (req, res) => {
20
+
21
+ app.get('/', async (c) => {
17
22
  const attributes = ['id', 'name', 'symbol', 'decimal', 'logo', 'payment_method_id', 'maximum_precision', 'type'];
18
- const where: WhereOptions<PaymentMethod> = { livemode: req.livemode, active: true };
23
+ const where: WhereOptions<PaymentMethod> = { livemode: c.get('livemode'), active: true };
19
24
 
20
25
  const methods = await PaymentMethod.findAll({
21
26
  where,
@@ -30,13 +35,13 @@ router.get('/', async (req, res) => {
30
35
  method.payment_currencies = method.payment_currencies?.map((x) => pick(x, attributes));
31
36
  });
32
37
 
33
- res.json({
38
+ return c.json({
34
39
  paymentMethods: methods.map((x) => ({
35
40
  ...pick(x, ['id', 'name', 'type', 'logo', 'payment_currencies', 'default_currency_id', 'maximum_precision']),
36
41
  api_host: x.settings?.arcblock?.api_host,
37
42
  })),
38
43
  baseCurrency: await PaymentCurrency.findOne({
39
- where: { is_base_currency: true, livemode: req.livemode },
44
+ where: { is_base_currency: true, livemode: c.get('livemode') },
40
45
  attributes,
41
46
  }),
42
47
  });
@@ -54,10 +59,10 @@ const donateListSchema = createListParamSchema<{
54
59
  componentDid: Joi.string().empty(''),
55
60
  });
56
61
 
57
- router.get('/donate', async (req, res) => {
62
+ app.get('/donate', async (c) => {
58
63
  try {
59
64
  const { page, pageSize, mountLocation, description, componentDid, ...query } = await donateListSchema.validateAsync(
60
- req.query,
65
+ c.req.query(),
61
66
  { stripUnknown: false, allowUnknown: true }
62
67
  );
63
68
 
@@ -81,34 +86,34 @@ router.get('/donate', async (req, res) => {
81
86
 
82
87
  const { rows: list, count } = await Setting.findAndCountAll({
83
88
  where,
84
- order: getOrder(req.query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
89
+ order: getOrder(c.req.query(), [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
85
90
  offset: (page - 1) * pageSize,
86
91
  limit: pageSize,
87
92
  distinct: true,
88
93
  });
89
94
 
90
- return res.json({ count, list, paging: { page, pageSize } });
95
+ return c.json({ count, list, paging: { page, pageSize } });
91
96
  } catch (err) {
92
97
  logger.error(err);
93
- return res.status(400).json({ error: err.message });
98
+ return c.json({ error: err.message }, 400);
94
99
  }
95
100
  });
96
101
 
97
- router.get('/donate/:mountLocationOrId', async (req, res) => {
102
+ app.get('/donate/:mountLocationOrId', async (c) => {
98
103
  try {
99
- if (!req.params.mountLocationOrId) {
100
- return res.status(400).json({ error: 'mountLocation is required' });
104
+ if (!c.req.param('mountLocationOrId')) {
105
+ return c.json({ error: 'mountLocation is required' }, 400);
101
106
  }
102
107
  const settings = await Setting.findOne({
103
108
  where: {
104
109
  type: 'donate',
105
- [Op.or]: [{ mount_location: req.params.mountLocationOrId }, { id: req.params.mountLocationOrId }],
110
+ [Op.or]: [{ mount_location: c.req.param('mountLocationOrId') }, { id: c.req.param('mountLocationOrId') }],
106
111
  },
107
112
  });
108
- return res.json(settings);
113
+ return c.json(settings);
109
114
  } catch (err) {
110
115
  logger.error(err);
111
- return res.status(400).json({ error: err.message });
116
+ return c.json({ error: err.message }, 400);
112
117
  }
113
118
  });
114
119
 
@@ -121,13 +126,15 @@ const SettingSchema = Joi.object({
121
126
  livemode: Joi.boolean().optional(),
122
127
  componentDid: Joi.string().optional(),
123
128
  }).unknown(true);
124
- router.post('/', async (req, res) => {
129
+
130
+ app.post('/', async (c) => {
125
131
  try {
126
- const { error } = SettingSchema.validate(req.body);
132
+ const body = c.get('sanitizedBody') ?? {};
133
+ const { error } = SettingSchema.validate(body);
127
134
  if (error) {
128
- return res.status(400).json({ error: error.message });
135
+ return c.json({ error: error.message }, 400);
129
136
  }
130
- const { type, mountLocation, settings = {}, active = true, description, livemode = true, componentDid } = req.body;
137
+ const { type, mountLocation, settings = {}, active = true, description, livemode = true, componentDid } = body;
131
138
  const raw = {
132
139
  type,
133
140
  mount_location: mountLocation,
@@ -163,7 +170,7 @@ router.post('/', async (req, res) => {
163
170
 
164
171
  const { error: amountError } = amountSchema.validate(settings.amount);
165
172
  if (amountError) {
166
- return res.status(400).json({ error: amountError.message });
173
+ return c.json({ error: amountError.message }, 400);
167
174
  }
168
175
  }
169
176
 
@@ -180,7 +187,7 @@ router.post('/', async (req, res) => {
180
187
  });
181
188
  const { error: notificationError, value: notificationSettings } = notificationSchema.validate(settings);
182
189
  if (notificationError) {
183
- return res.status(400).json({ error: notificationError.message });
190
+ return c.json({ error: notificationError.message }, 400);
184
191
  }
185
192
  raw.settings = notificationSettings;
186
193
  }
@@ -196,13 +203,13 @@ router.post('/', async (req, res) => {
196
203
  component_did: componentDid,
197
204
  });
198
205
  }
199
- return res.json(exist);
206
+ return c.json(exist);
200
207
  }
201
208
  const doc = await Setting.create(raw);
202
- return res.json(doc);
209
+ return c.json(doc);
203
210
  } catch (err) {
204
211
  logger.error(err);
205
- return res.status(400).json({ error: err.message });
212
+ return c.json({ error: err.message }, 400);
206
213
  }
207
214
  });
208
215
 
@@ -211,13 +218,15 @@ const UpdateSettingSchema = Joi.object({
211
218
  active: Joi.boolean().optional(),
212
219
  description: Joi.string().optional(),
213
220
  }).unknown(true);
214
- router.put('/:mountLocationOrId', authAdmin, async (req, res) => {
221
+
222
+ app.put('/:mountLocationOrId', authAdmin, async (c) => {
215
223
  try {
216
- const { error } = UpdateSettingSchema.validate(req.body);
224
+ const body = c.get('sanitizedBody') ?? {};
225
+ const { error } = UpdateSettingSchema.validate(body);
217
226
  if (error) {
218
- return res.status(400).json({ error: error.message });
227
+ return c.json({ error: error.message }, 400);
219
228
  }
220
- const { settings = {}, active = true, description } = req.body;
229
+ const { settings = {}, active = true, description } = body;
221
230
  const raw: {
222
231
  active: boolean;
223
232
  settings: Record<string, any>;
@@ -233,16 +242,16 @@ router.put('/:mountLocationOrId', authAdmin, async (req, res) => {
233
242
  where: {
234
243
  [Op.or]: [
235
244
  {
236
- id: req.params.mountLocationOrId,
245
+ id: c.req.param('mountLocationOrId'),
237
246
  },
238
247
  {
239
- mount_location: req.params.mountLocationOrId,
248
+ mount_location: c.req.param('mountLocationOrId'),
240
249
  },
241
250
  ],
242
251
  },
243
252
  });
244
253
  if (!setting) {
245
- return res.status(404).json({ error: 'Setting not found' });
254
+ return c.json({ error: 'Setting not found' }, 404);
246
255
  }
247
256
  raw.settings = merge({}, setting.settings, settings);
248
257
  if (setting.type === 'donate') {
@@ -259,7 +268,7 @@ router.put('/:mountLocationOrId', authAdmin, async (req, res) => {
259
268
 
260
269
  const { error: amountError } = amountSchema.validate(settings.amount);
261
270
  if (amountError) {
262
- return res.status(400).json({ error: amountError.message });
271
+ return c.json({ error: amountError.message }, 400);
263
272
  }
264
273
  raw.settings.amount = settings.amount;
265
274
  }
@@ -272,62 +281,62 @@ router.put('/:mountLocationOrId', authAdmin, async (req, res) => {
272
281
  });
273
282
  const { error: notificationError, value: notificationSettings } = notificationSchema.validate(settings);
274
283
  if (notificationError) {
275
- return res.status(400).json({ error: notificationError.message });
284
+ return c.json({ error: notificationError.message }, 400);
276
285
  }
277
286
  raw.settings = notificationSettings;
278
287
  }
279
288
  const doc = await setting.update(raw);
280
- return res.json(doc);
289
+ return c.json(doc);
281
290
  } catch (err) {
282
291
  logger.error(err);
283
- return res.status(400).json({ error: err.message });
292
+ return c.json({ error: err.message }, 400);
284
293
  }
285
294
  });
286
295
 
287
- router.delete('/:mountLocationOrId', authAdmin, async (req, res) => {
296
+ app.delete('/:mountLocationOrId', authAdmin, async (c) => {
288
297
  try {
289
298
  const setting = await Setting.findOne({
290
299
  where: {
291
300
  [Op.or]: [
292
301
  {
293
- id: req.params.mountLocationOrId,
302
+ id: c.req.param('mountLocationOrId'),
294
303
  },
295
304
  {
296
- mount_location: req.params.mountLocationOrId,
305
+ mount_location: c.req.param('mountLocationOrId'),
297
306
  },
298
307
  ],
299
308
  },
300
309
  });
301
310
  if (!setting) {
302
- return res.status(404).json({ error: `Setting ${req.params.mountLocationOrId} not found` });
311
+ return c.json({ error: `Setting ${c.req.param('mountLocationOrId')} not found` }, 404);
303
312
  }
304
313
  await setting.destroy();
305
- return res.json({ message: `Setting ${req.params.mountLocationOrId} deleted` });
314
+ return c.json({ message: `Setting ${c.req.param('mountLocationOrId')} deleted` });
306
315
  } catch (err) {
307
316
  logger.error(err);
308
- return res.status(400).json({ error: err.message });
317
+ return c.json({ error: err.message }, 400);
309
318
  }
310
319
  });
311
320
 
312
- router.get('/:mountLocationOrId', authAdmin, async (req, res) => {
321
+ app.get('/:mountLocationOrId', authAdmin, async (c) => {
313
322
  try {
314
323
  const setting = await Setting.findOne({
315
324
  where: {
316
325
  [Op.or]: [
317
326
  {
318
- id: req.params.mountLocationOrId,
327
+ id: c.req.param('mountLocationOrId'),
319
328
  },
320
329
  {
321
- mount_location: req.params.mountLocationOrId,
330
+ mount_location: c.req.param('mountLocationOrId'),
322
331
  },
323
332
  ],
324
333
  },
325
334
  });
326
- return res.json(setting);
335
+ return c.json(setting);
327
336
  } catch (err) {
328
337
  logger.error(err);
329
- return res.status(400).json({ error: err.message });
338
+ return c.json({ error: err.message }, 400);
330
339
  }
331
340
  });
332
341
 
333
- export default router;
342
+ export default app;
@@ -1,25 +1,30 @@
1
+ // Phase 3 (express→hono) — hono fork of routes/subscription-items.ts. Sub-app with
2
+ // routes relative to /api/subscription-items (mounted via mountResourceGroup). The
3
+ // business logic is unchanged; only the express plumbing becomes hono:
4
+ // req.body → c.get('sanitizedBody'); res.status(n).json(x) → c.json(x, n).
1
5
  import dayjs from 'dayjs';
2
- import { Router } from 'express';
6
+ import { Hono } from 'hono';
3
7
  import Joi from 'joi';
4
8
  import pick from 'lodash/pick';
5
9
  import type { WhereOptions } from 'sequelize';
6
10
 
7
- import { createListParamSchema, getOrder, MetadataSchema } from '../libs/api';
8
- import { authenticate } from '../libs/security';
9
- import { expandLineItems } from '../libs/session';
10
- import { formatMetadata } from '../libs/util';
11
- import { Price, Product, Subscription, SubscriptionItem, UsageRecord } from '../store/models';
12
- import { forwardUsageRecordToStripe } from '../integrations/stripe/resource';
13
- import { usageRecordQueue } from '../queues/usage-record';
14
- import logger from '../libs/logger';
11
+ import { createListParamSchema, getOrder, MetadataSchema } from '../../libs/api';
12
+ import { authenticate } from '../../middlewares/hono/security';
13
+ import { expandLineItems } from '../../libs/session';
14
+ import { formatMetadata } from '../../libs/util';
15
+ import { Price, Product, Subscription, SubscriptionItem, UsageRecord } from '../../store/models';
16
+ import { forwardUsageRecordToStripe } from '../../integrations/stripe/resource';
17
+ import { usageRecordQueue } from '../../queues/usage-record';
18
+ import logger from '../../libs/logger';
15
19
 
16
- const router = Router();
20
+ const app = new Hono();
17
21
  const auth = authenticate<SubscriptionItem>({ component: true, roles: ['owner', 'admin'] });
18
22
 
19
23
  // FIXME: handle payment_behavior, proration_behavior
20
24
  // @link https://stripe.com/docs/api/subscription_items/create
21
- router.post('/', auth, async (req, res) => {
22
- const raw: Partial<SubscriptionItem> = pick(req.body, [
25
+ app.post('/', auth, async (c) => {
26
+ const body = c.get('sanitizedBody') ?? {};
27
+ const raw: Partial<SubscriptionItem> = pick(body, [
23
28
  'subscription_id',
24
29
  'price_id',
25
30
  'quantity',
@@ -30,14 +35,14 @@ router.post('/', auth, async (req, res) => {
30
35
  where: { price_id: raw.price_id, subscription_id: raw.subscription_id },
31
36
  });
32
37
  if (exist) {
33
- return res.status(400).json({ error: `SubscriptionItem already exist: ${exist.id}` });
38
+ return c.json({ error: `SubscriptionItem already exist: ${exist.id}` }, 400);
34
39
  }
35
40
 
36
- raw.livemode = req.livemode;
41
+ raw.livemode = c.get('livemode');
37
42
  if (raw.metadata) {
38
43
  const { error: metadataError } = MetadataSchema.validate(raw.metadata);
39
44
  if (metadataError) {
40
- return res.status(400).json({ error: `metadata invalid: ${metadataError.message}` });
45
+ return c.json({ error: `metadata invalid: ${metadataError.message}` }, 400);
41
46
  }
42
47
  raw.metadata = formatMetadata(raw.metadata);
43
48
  }
@@ -48,9 +53,9 @@ router.post('/', auth, async (req, res) => {
48
53
  subscriptionId: doc.subscription_id,
49
54
  priceId: doc.price_id,
50
55
  quantity: doc.quantity,
51
- requestedBy: req.user?.did,
56
+ requestedBy: c.get('user')?.did,
52
57
  });
53
- return res.json(doc);
58
+ return c.json(doc);
54
59
  });
55
60
 
56
61
  // @link https://stripe.com/docs/api/subscription_items/list
@@ -61,8 +66,8 @@ const schema = createListParamSchema<{
61
66
  subscription_id: Joi.string(),
62
67
  price_id: Joi.string(),
63
68
  });
64
- router.get('/', auth, async (req, res) => {
65
- const { page, pageSize, ...query } = await schema.validateAsync(req.query, { stripUnknown: true });
69
+ app.get('/', auth, async (c) => {
70
+ const { page, pageSize, ...query } = await schema.validateAsync(c.req.query(), { stripUnknown: true });
66
71
  const where: WhereOptions<SubscriptionItem> = {};
67
72
 
68
73
  if (query.subscription_id) {
@@ -80,7 +85,7 @@ router.get('/', auth, async (req, res) => {
80
85
  try {
81
86
  const { rows, count } = await SubscriptionItem.findAndCountAll({
82
87
  where,
83
- order: getOrder(req.query, [['created_at', 'DESC']]),
88
+ order: getOrder(c.req.query(), [['created_at', 'DESC']]),
84
89
  offset: (page - 1) * pageSize,
85
90
  limit: pageSize,
86
91
  include: [],
@@ -101,45 +106,45 @@ router.get('/', auth, async (req, res) => {
101
106
  // @ts-ignore
102
107
  expandLineItems(list, Array.from(productMap.values()), pricesJson);
103
108
  }
104
- res.json({ count, list, paging: { page, pageSize } });
109
+ return c.json({ count, list, paging: { page, pageSize } });
105
110
  } catch (err) {
106
111
  logger.error(err);
107
- res.json({ count: 0, list: [], paging: { page, pageSize } });
112
+ return c.json({ count: 0, list: [], paging: { page, pageSize } });
108
113
  }
109
114
  });
110
115
 
111
116
  // @link https://stripe.com/docs/api/subscription_items/retrieve
112
- router.get('/:id', auth, async (req, res) => {
117
+ app.get('/:id', auth, async (c) => {
113
118
  try {
114
119
  const doc = await SubscriptionItem.findOne({
115
- where: { id: req.params.id },
120
+ where: { id: c.req.param('id') },
116
121
  include: [{ model: Price, as: 'price' }],
117
122
  });
118
123
  if (doc) {
119
- res.json(doc);
120
- } else {
121
- res.status(404).json(null);
124
+ return c.json(doc);
122
125
  }
126
+ return c.json(null, 404);
123
127
  } catch (err) {
124
128
  logger.error(err);
125
- res.status(500).json({ error: `Failed to get subscription item: ${err.message}` });
129
+ return c.json({ error: `Failed to get subscription item: ${(err as any).message}` }, 500);
126
130
  }
127
131
  });
128
132
 
129
- router.put('/:id', auth, async (req, res) => {
130
- const doc = await SubscriptionItem.findByPk(req.params.id);
133
+ app.put('/:id', auth, async (c) => {
134
+ const body = c.get('sanitizedBody') ?? {};
135
+ const doc = await SubscriptionItem.findByPk(c.req.param('id'));
131
136
 
132
137
  if (!doc) {
133
- return res.status(404).json({ error: `SubscriptionItem not found: ${req.params.id}` });
138
+ return c.json({ error: `SubscriptionItem not found: ${c.req.param('id')}` }, 404);
134
139
  }
135
140
 
136
- const updates: Partial<SubscriptionItem> = pick(req.body, ['price_id', 'quantity', 'billing_thresholds', 'metadata']);
141
+ const updates: Partial<SubscriptionItem> = pick(body, ['price_id', 'quantity', 'billing_thresholds', 'metadata']);
137
142
  if (updates.price_id) {
138
143
  const exist = await SubscriptionItem.findOne({
139
144
  where: { price_id: updates.price_id, subscription_id: doc.subscription_id },
140
145
  });
141
146
  if (exist) {
142
- return res.status(400).json({ error: `SubscriptionItem already exist: ${exist.id}` });
147
+ return c.json({ error: `SubscriptionItem already exist: ${exist.id}` }, 400);
143
148
  }
144
149
 
145
150
  if (!updates.quantity) {
@@ -150,7 +155,7 @@ router.put('/:id', auth, async (req, res) => {
150
155
  if (updates.metadata) {
151
156
  const { error: metadataError } = MetadataSchema.validate(updates.metadata);
152
157
  if (metadataError) {
153
- return res.status(400).json({ error: `metadata invalid: ${metadataError.message}` });
158
+ return c.json({ error: `metadata invalid: ${metadataError.message}` }, 400);
154
159
  }
155
160
  updates.metadata = formatMetadata(updates.metadata);
156
161
  }
@@ -160,21 +165,22 @@ router.put('/:id', auth, async (req, res) => {
160
165
  id: doc.id,
161
166
  subscriptionId: doc.subscription_id,
162
167
  updatedFields: Object.keys(updates),
163
- requestedBy: req.user?.did,
168
+ requestedBy: c.get('user')?.did,
164
169
  });
165
- return res.json(doc);
170
+ return c.json(doc);
166
171
  });
167
172
 
168
173
  // TODO: handle proration_behavior
169
174
  // @link https://stripe.com/docs/api/subscription_items/delete
170
- router.delete('/:id', auth, async (req, res) => {
171
- const doc = await SubscriptionItem.findByPk(req.params.id);
175
+ app.delete('/:id', auth, async (c) => {
176
+ const body = c.get('sanitizedBody') ?? {};
177
+ const doc = await SubscriptionItem.findByPk(c.req.param('id'));
172
178
 
173
179
  if (!doc) {
174
- return res.status(404).json({ error: 'subscription item not found' });
180
+ return c.json({ error: 'subscription item not found' }, 404);
175
181
  }
176
182
 
177
- if (req.body.clear_usage) {
183
+ if (body.clear_usage) {
178
184
  const price = await Price.findByPk(doc.price_id);
179
185
  if (price?.recurring?.usage_type === 'metered') {
180
186
  await UsageRecord.destroy({ where: { subscription_item_id: doc.id } });
@@ -185,41 +191,42 @@ router.delete('/:id', auth, async (req, res) => {
185
191
  logger.info('SubscriptionItem deleted', {
186
192
  id: doc.id,
187
193
  subscriptionId: doc.subscription_id,
188
- clearUsage: req.body.clear_usage,
189
- requestedBy: req.user?.did,
194
+ clearUsage: body.clear_usage,
195
+ requestedBy: c.get('user')?.did,
190
196
  });
191
- return res.json(doc);
197
+ return c.json(doc);
192
198
  });
193
199
 
194
- router.post('/:id/add-usage-quantity', auth, async (req, res) => {
195
- const { livemode } = req;
200
+ app.post('/:id/add-usage-quantity', auth, async (c) => {
201
+ const livemode = c.get('livemode');
196
202
  if (livemode) {
197
- return res.status(403).json({ error: 'add usage quantity not allowed in livemode' });
203
+ return c.json({ error: 'add usage quantity not allowed in livemode' }, 403);
198
204
  }
199
205
 
200
- const quantity = Number(req.body.quantity);
206
+ const body = c.get('sanitizedBody') ?? {};
207
+ const quantity = Number(body.quantity);
201
208
  if (quantity <= 0) {
202
- return res.status(400).json({ error: 'quantity must be positive' });
209
+ return c.json({ error: 'quantity must be positive' }, 400);
203
210
  }
204
211
 
205
212
  try {
206
- const subscriptionItem = await SubscriptionItem.findByPk(req.params.id);
213
+ const subscriptionItem = await SubscriptionItem.findByPk(c.req.param('id'));
207
214
  if (!subscriptionItem) {
208
- return res.status(404).json({ error: `SubscriptionItem(${req.params.id}) item not found` });
215
+ return c.json({ error: `SubscriptionItem(${c.req.param('id')}) item not found` }, 404);
209
216
  }
210
217
  const subscription = await Subscription.findByPk(subscriptionItem.subscription_id);
211
218
  if (!subscription) {
212
- return res.status(400).json({ error: `Subscription not found: ${subscriptionItem.subscription_id}` });
219
+ return c.json({ error: `Subscription not found: ${subscriptionItem.subscription_id}` }, 400);
213
220
  }
214
221
 
215
222
  if (subscription.isImmutable()) {
216
- return res.status(400).json({ error: `subscription(${subscription.id}) is immutable` });
223
+ return c.json({ error: `subscription(${subscription.id}) is immutable` }, 400);
217
224
  }
218
225
 
219
226
  const now = dayjs().unix();
220
227
 
221
228
  if (now < subscription.current_period_start || now > subscription.current_period_end) {
222
- return res.status(400).json({ error: 'timestamp must be within the current period' });
229
+ return c.json({ error: 'timestamp must be within the current period' }, 400);
223
230
  }
224
231
 
225
232
  const usageRecord = await UsageRecord.create({
@@ -234,7 +241,7 @@ router.post('/:id/add-usage-quantity', auth, async (req, res) => {
234
241
  subscriptionId: subscription.id,
235
242
  quantity,
236
243
  timestamp: now,
237
- requestedBy: req.user?.did,
244
+ requestedBy: c.get('user')?.did,
238
245
  });
239
246
 
240
247
  if (subscription.billing_thresholds?.amount_gte) {
@@ -260,11 +267,11 @@ router.post('/:id/add-usage-quantity', auth, async (req, res) => {
260
267
  action: 'increment',
261
268
  });
262
269
 
263
- return res.json(usageRecord);
270
+ return c.json(usageRecord);
264
271
  } catch (err) {
265
272
  logger.error(err);
266
- return res.status(400).json({ error: `Failed to add usage quantity: ${err.message}` });
273
+ return c.json({ error: `Failed to add usage quantity: ${(err as any).message}` }, 400);
267
274
  }
268
275
  });
269
276
 
270
- export default router;
277
+ export default app;