payment-kit 1.29.1 → 1.29.3

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 (343) 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 +47 -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 +41 -37
  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/crons/tenant-fanout.ts +82 -0
  12. package/api/src/host-node/did-connect-runtime-node.ts +33 -0
  13. package/api/src/host-node/serve-static-arc.ts +68 -0
  14. package/api/src/host-node/serve-static.ts +41 -0
  15. package/api/src/index.ts +22 -161
  16. package/api/src/integrations/app-store/client.ts +3 -4
  17. package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
  18. package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
  19. package/api/src/integrations/arcblock/token.ts +21 -7
  20. package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
  21. package/api/src/integrations/google-play/handlers/voided.ts +2 -2
  22. package/api/src/integrations/google-play/verify.ts +3 -2
  23. package/api/src/integrations/iap-reconcile.ts +3 -5
  24. package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
  25. package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
  26. package/api/src/libs/archive/query.ts +19 -0
  27. package/api/src/libs/audit.ts +61 -4
  28. package/api/src/libs/auth.ts +247 -47
  29. package/api/src/libs/context.ts +89 -1
  30. package/api/src/libs/currency.ts +2 -2
  31. package/api/src/libs/dayjs.ts +8 -2
  32. package/api/src/libs/did-connect/runtime-did-connect-js.ts +88 -0
  33. package/api/src/libs/did-connect/tenant-identity.ts +221 -0
  34. package/api/src/libs/drivers/auth-storage.ts +118 -0
  35. package/api/src/libs/drivers/cron.ts +264 -0
  36. package/api/src/libs/drivers/db.ts +170 -0
  37. package/api/src/libs/drivers/identity.ts +142 -0
  38. package/api/src/libs/drivers/index.ts +40 -0
  39. package/api/src/libs/drivers/locks.ts +226 -0
  40. package/api/src/libs/drivers/migrate-runner.ts +70 -0
  41. package/api/src/libs/drivers/queue.ts +104 -0
  42. package/api/src/libs/drivers/secrets.ts +194 -0
  43. package/api/src/libs/env.ts +170 -54
  44. package/api/src/libs/exchange-rate/service.ts +7 -6
  45. package/api/src/libs/http-fetch-adapter.ts +60 -0
  46. package/api/src/libs/invoice.ts +1 -1
  47. package/api/src/libs/lock.ts +51 -47
  48. package/api/src/libs/logger.ts +48 -8
  49. package/api/src/libs/notification/index.ts +1 -1
  50. package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
  51. package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
  52. package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
  53. package/api/src/libs/overdraft-protection.ts +1 -1
  54. package/api/src/libs/payout.ts +1 -1
  55. package/api/src/libs/queue/index.ts +271 -52
  56. package/api/src/libs/queue/runtime.ts +175 -0
  57. package/api/src/libs/resource.ts +3 -3
  58. package/api/src/libs/secrets.ts +38 -0
  59. package/api/src/libs/session.ts +3 -2
  60. package/api/src/libs/subscription.ts +5 -5
  61. package/api/src/libs/tenant.ts +92 -0
  62. package/api/src/libs/url.ts +3 -3
  63. package/api/src/libs/util.ts +21 -13
  64. package/api/src/middlewares/hono/cdn.ts +63 -0
  65. package/api/src/middlewares/hono/context.ts +80 -0
  66. package/api/src/middlewares/hono/csrf.ts +83 -0
  67. package/api/src/middlewares/hono/fallback.ts +194 -0
  68. package/api/src/middlewares/hono/pipeline.ts +73 -0
  69. package/api/src/middlewares/hono/resource-mount.ts +42 -0
  70. package/api/src/middlewares/hono/resource.ts +63 -0
  71. package/api/src/middlewares/hono/security.ts +209 -0
  72. package/api/src/middlewares/hono/session.ts +114 -0
  73. package/api/src/middlewares/hono/xss.ts +61 -0
  74. package/api/src/queues/auto-recharge.ts +12 -10
  75. package/api/src/queues/checkout-session.ts +38 -21
  76. package/api/src/queues/credit-consume.ts +40 -36
  77. package/api/src/queues/credit-grant.ts +25 -18
  78. package/api/src/queues/credit-reconciliation.ts +7 -5
  79. package/api/src/queues/discount-status.ts +9 -6
  80. package/api/src/queues/event.ts +41 -11
  81. package/api/src/queues/exchange-rate-health.ts +49 -30
  82. package/api/src/queues/invoice.ts +18 -15
  83. package/api/src/queues/notification.ts +14 -7
  84. package/api/src/queues/payment.ts +64 -37
  85. package/api/src/queues/payout.ts +37 -21
  86. package/api/src/queues/refund.ts +36 -18
  87. package/api/src/queues/subscription.ts +83 -53
  88. package/api/src/queues/token-transfer.ts +15 -10
  89. package/api/src/queues/usage-record.ts +8 -5
  90. package/api/src/queues/vendors/commission.ts +7 -5
  91. package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
  92. package/api/src/queues/vendors/fulfillment.ts +4 -2
  93. package/api/src/queues/vendors/return-processor.ts +5 -3
  94. package/api/src/queues/vendors/return-scanner.ts +5 -4
  95. package/api/src/queues/vendors/status-check.ts +10 -7
  96. package/api/src/queues/webhook.ts +60 -32
  97. package/api/src/routes/connect/shared.ts +1 -2
  98. package/api/src/routes/connect/subscribe.ts +3 -3
  99. package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
  100. package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
  101. package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
  102. package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
  103. package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
  104. package/api/src/routes/hono/credit-tokens.ts +43 -0
  105. package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
  106. package/api/src/routes/{customers.ts → hono/customers.ts} +199 -224
  107. package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
  108. package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
  109. package/api/src/routes/{events.ts → hono/events.ts} +107 -71
  110. package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
  111. package/api/src/routes/hono/exchange-rates.ts +77 -0
  112. package/api/src/routes/hono/index.ts +115 -0
  113. package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
  114. package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
  115. package/api/src/routes/hono/integrations/stripe.ts +74 -0
  116. package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
  117. package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
  118. package/api/src/routes/hono/meters.ts +288 -0
  119. package/api/src/routes/hono/passports.ts +73 -0
  120. package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
  121. package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
  122. package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
  123. package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
  124. package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
  125. package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
  126. package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
  127. package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
  128. package/api/src/routes/{products.ts → hono/products.ts} +172 -159
  129. package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
  130. package/api/src/routes/hono/redirect.ts +24 -0
  131. package/api/src/routes/{refunds.ts → hono/refunds.ts} +98 -83
  132. package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
  133. package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
  134. package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
  135. package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
  136. package/api/src/routes/hono/tool.ts +69 -0
  137. package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
  138. package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
  139. package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
  140. package/api/src/routes/hono/webhook-endpoints.ts +126 -0
  141. package/api/src/service.ts +814 -0
  142. package/api/src/store/migrations/20230911-seeding.ts +2 -1
  143. package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
  144. package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
  145. package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
  146. package/api/src/store/models/auto-recharge-config.ts +22 -10
  147. package/api/src/store/models/checkout-session.ts +15 -14
  148. package/api/src/store/models/coupon.ts +29 -20
  149. package/api/src/store/models/credit-grant.ts +38 -29
  150. package/api/src/store/models/credit-transaction.ts +32 -21
  151. package/api/src/store/models/customer.ts +19 -17
  152. package/api/src/store/models/discount.ts +11 -2
  153. package/api/src/store/models/entitlement-grant.ts +21 -9
  154. package/api/src/store/models/entitlement-product.ts +21 -9
  155. package/api/src/store/models/entitlement.ts +19 -10
  156. package/api/src/store/models/event.ts +18 -9
  157. package/api/src/store/models/exchange-rate-provider.ts +17 -4
  158. package/api/src/store/models/invoice-item.ts +18 -9
  159. package/api/src/store/models/invoice.ts +16 -8
  160. package/api/src/store/models/meter-event.ts +27 -9
  161. package/api/src/store/models/meter.ts +31 -22
  162. package/api/src/store/models/payment-currency.ts +25 -8
  163. package/api/src/store/models/payment-intent.ts +15 -6
  164. package/api/src/store/models/payment-link.ts +15 -6
  165. package/api/src/store/models/payment-method.ts +38 -22
  166. package/api/src/store/models/payment-stat.ts +18 -9
  167. package/api/src/store/models/payout.ts +15 -6
  168. package/api/src/store/models/price-quote.ts +17 -8
  169. package/api/src/store/models/price.ts +24 -12
  170. package/api/src/store/models/pricing-table.ts +29 -20
  171. package/api/src/store/models/product-vendor.ts +20 -10
  172. package/api/src/store/models/product.ts +15 -6
  173. package/api/src/store/models/promotion-code.ts +14 -6
  174. package/api/src/store/models/refund.ts +15 -6
  175. package/api/src/store/models/revenue-snapshot.ts +21 -9
  176. package/api/src/store/models/setting.ts +18 -9
  177. package/api/src/store/models/setup-intent.ts +36 -27
  178. package/api/src/store/models/subscription-item.ts +21 -9
  179. package/api/src/store/models/subscription-schedule.ts +21 -9
  180. package/api/src/store/models/subscription.ts +21 -10
  181. package/api/src/store/models/tax-rate.ts +29 -21
  182. package/api/src/store/models/usage-record.ts +11 -2
  183. package/api/src/store/models/webhook-attempt.ts +18 -9
  184. package/api/src/store/models/webhook-endpoint.ts +18 -9
  185. package/api/src/store/scoped-core.ts +55 -0
  186. package/api/src/store/scoped.ts +247 -0
  187. package/api/src/store/sequelize.ts +82 -23
  188. package/api/src/store/sql-migrations.ts +20 -0
  189. package/api/src/store/tenant-backfill.ts +260 -0
  190. package/api/src/store/tenant-model.ts +124 -0
  191. package/api/src/store/tenant-tables.ts +50 -0
  192. package/api/tests/bootstrap/bootstrap.spec.ts +162 -0
  193. package/api/tests/crons/tenant-fanout.spec.ts +158 -0
  194. package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
  195. package/api/tests/fixtures/bare-query-violation.ts +13 -0
  196. package/api/tests/fixtures/core-env-violation.ts +10 -0
  197. package/api/tests/fixtures/host-read-violation.ts +19 -0
  198. package/api/tests/fixtures/tenants.ts +4 -0
  199. package/api/tests/integrations/iap-tenant.spec.ts +284 -0
  200. package/api/tests/libs/archive-query.spec.ts +26 -0
  201. package/api/tests/libs/audit-tenant.spec.ts +153 -0
  202. package/api/tests/libs/context.spec.ts +204 -0
  203. package/api/tests/libs/core-config.spec.ts +115 -0
  204. package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
  205. package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
  206. package/api/tests/libs/did-connect-runtime-js.spec.ts +98 -0
  207. package/api/tests/libs/did-connect-tenant-identity.spec.ts +159 -0
  208. package/api/tests/libs/lock-tenant.spec.ts +66 -0
  209. package/api/tests/libs/scoped.spec.ts +222 -0
  210. package/api/tests/libs/secrets-facade.spec.ts +52 -0
  211. package/api/tests/libs/service-host.spec.ts +37 -0
  212. package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
  213. package/api/tests/libs/tenant-middleware.spec.ts +42 -0
  214. package/api/tests/libs/tenant-scanner.spec.ts +120 -0
  215. package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
  216. package/api/tests/middlewares/hono/context.spec.ts +113 -0
  217. package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
  218. package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
  219. package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
  220. package/api/tests/middlewares/hono/security.spec.ts +181 -0
  221. package/api/tests/middlewares/hono/session.spec.ts +42 -0
  222. package/api/tests/middlewares/hono/xss.spec.ts +81 -0
  223. package/api/tests/models/tenant-backfill.spec.ts +287 -0
  224. package/api/tests/models/tenant-columns-model.spec.ts +46 -0
  225. package/api/tests/models/tenant-columns.spec.ts +161 -0
  226. package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
  227. package/api/tests/queues/credit-consume.spec.ts +8 -1
  228. package/api/tests/queues/event-tenant.spec.ts +292 -0
  229. package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
  230. package/api/tests/queues/queue-parity.spec.ts +249 -0
  231. package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
  232. package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
  233. package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
  234. package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
  235. package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
  236. package/api/tests/service/collapse.spec.ts +96 -0
  237. package/api/tests/service/didconnect-storage-slot.spec.ts +60 -0
  238. package/api/tests/service/fail-closed-http.spec.ts +79 -0
  239. package/api/tests/service/static-arc-handler.spec.ts +101 -0
  240. package/api/tests/service/static-externalized.spec.ts +48 -0
  241. package/api/tests/store/tenant-crosscut.spec.ts +202 -0
  242. package/api/tests/store/tenant-model-spike.spec.ts +177 -0
  243. package/api/tests/store/tenant-model.spec.ts +162 -0
  244. package/api/tests/store/tenant-residual.spec.ts +196 -0
  245. package/api/third.d.ts +4 -0
  246. package/blocklet.yml +1 -1
  247. package/cloudflare/MIGRATION-RUNBOOK.md +3 -8
  248. package/cloudflare/README.md +34 -27
  249. package/cloudflare/STAGING-MIGRATION-GUIDE.md +3 -15
  250. package/cloudflare/build.ts +33 -13
  251. package/cloudflare/cf-adapter.ts +419 -0
  252. package/cloudflare/did-connect-runtime.ts +96 -0
  253. package/cloudflare/did-connect-token-storage.ts +151 -0
  254. package/cloudflare/esbuild-cf-config.cjs +407 -0
  255. package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
  256. package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
  257. package/cloudflare/migrations/0008_schema_parity.sql +16 -0
  258. package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
  259. package/cloudflare/queue-runtime-mode.ts +13 -0
  260. package/cloudflare/run-build.js +33 -403
  261. package/cloudflare/scripts/cf-package-import-probe.mjs +90 -0
  262. package/cloudflare/scripts/didconnect-mock-smoke.mjs +140 -0
  263. package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
  264. package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
  265. package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
  266. package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
  267. package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
  268. package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
  269. package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +16 -1
  270. package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +18 -3
  271. package/cloudflare/shims/cron.ts +38 -158
  272. package/cloudflare/shims/events.ts +124 -0
  273. package/cloudflare/shims/fastq.ts +15 -1
  274. package/cloudflare/shims/nedb-storage.ts +16 -8
  275. package/cloudflare/shims/xss.ts +8 -0
  276. package/cloudflare/tenant-middleware.ts +36 -0
  277. package/cloudflare/tests/cf-adapter.spec.ts +244 -0
  278. package/cloudflare/tests/did-connect-token-storage.spec.ts +105 -0
  279. package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
  280. package/cloudflare/tests/worker-handler-gate.spec.ts +69 -0
  281. package/cloudflare/vite.config.ts +53 -45
  282. package/cloudflare/worker.ts +261 -448
  283. package/cloudflare/wrangler.json +0 -6
  284. package/cloudflare/wrangler.jsonc +0 -6
  285. package/cloudflare/wrangler.local-e2e.jsonc +25 -0
  286. package/cloudflare/wrangler.staging.json +0 -6
  287. package/jest.config.js +3 -1
  288. package/package.json +33 -38
  289. package/scripts/bootstrap-inject.ts +166 -0
  290. package/scripts/core-env-whitelist.json +1 -0
  291. package/scripts/e2e-12b-runtime.ts +149 -0
  292. package/scripts/e2e-core-config.ts +125 -0
  293. package/scripts/e2e-d1-tenancy.ts +116 -0
  294. package/scripts/e2e-d2-cron-queue.ts +139 -0
  295. package/scripts/e2e-d3-embedded-multi.ts +171 -0
  296. package/scripts/e2e-hono-s2.ts +125 -0
  297. package/scripts/e2e-hono-s3e.ts +135 -0
  298. package/scripts/e2e-hono-s4.ts +114 -0
  299. package/scripts/e2e-migration-contract.ts +100 -0
  300. package/scripts/e2e-s0.ts +61 -0
  301. package/scripts/e2e-s1.ts +107 -0
  302. package/scripts/e2e-s2.ts +178 -0
  303. package/scripts/e2e-s3.ts +110 -0
  304. package/scripts/e2e-s4.ts +191 -0
  305. package/scripts/e2e-s5.ts +139 -0
  306. package/scripts/e2e-s6.ts +127 -0
  307. package/scripts/e2e-tenant-model.ts +119 -0
  308. package/scripts/e2e-tenant-worker.ts +199 -0
  309. package/scripts/gen-sql-migrations.js +46 -0
  310. package/scripts/phase8-codemod.js +219 -0
  311. package/scripts/phase9a-env-getters-codemod.js +82 -0
  312. package/scripts/scan-core-env.js +109 -0
  313. package/scripts/scan-tenant-queries.js +235 -0
  314. package/scripts/schema-drift-guard.ts +210 -0
  315. package/scripts/tenant-scan-whitelist.json +1 -0
  316. package/src/app.tsx +2 -1
  317. package/src/env.d.ts +13 -1
  318. package/src/libs/service-host.ts +13 -0
  319. package/tsconfig.json +1 -1
  320. package/vite.arc.config.ts +159 -0
  321. package/api/src/libs/did-space.ts +0 -235
  322. package/api/src/libs/middleware.ts +0 -50
  323. package/api/src/libs/security.ts +0 -192
  324. package/api/src/queues/space.ts +0 -662
  325. package/api/src/routes/credit-tokens.ts +0 -38
  326. package/api/src/routes/exchange-rates.ts +0 -87
  327. package/api/src/routes/index.ts +0 -142
  328. package/api/src/routes/integrations/stripe.ts +0 -61
  329. package/api/src/routes/meters.ts +0 -274
  330. package/api/src/routes/passports.ts +0 -68
  331. package/api/src/routes/redirect.ts +0 -20
  332. package/api/src/routes/tool.ts +0 -65
  333. package/api/src/routes/webhook-endpoints.ts +0 -126
  334. package/api/tests/routes/credit-grants.spec.ts +0 -1261
  335. package/cloudflare/did-connect-auth.ts +0 -527
  336. package/cloudflare/shims/did-space-js.ts +0 -17
  337. package/cloudflare/shims/did-space.ts +0 -11
  338. package/cloudflare/shims/express-compat/index.ts +0 -80
  339. package/cloudflare/shims/express-compat/types.ts +0 -41
  340. package/cloudflare/shims/lock.ts +0 -115
  341. package/cloudflare/shims/queue.ts +0 -611
  342. package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
  343. package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
@@ -1,19 +1,26 @@
1
+ // Phase 3 (express→hono) — hono fork of routes/donations.ts. Sub-app with
2
+ // routes relative to /api/donations (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 Joi from 'joi';
2
- import { Router } from 'express';
6
+ import { Hono } from 'hono';
3
7
 
4
8
  import { BN } from '@ocap/util';
5
- import { createListParamSchema, getOrder } from '../libs/api';
6
- import logger from '../libs/logger';
7
- import { CheckoutSession } from '../store/models/checkout-session';
8
- import { Customer } from '../store/models/customer';
9
- import { PaymentLink } from '../store/models/payment-link';
10
- import { PaymentMethod } from '../store/models/payment-method';
11
- import { Price } from '../store/models/price';
12
- import type { DonationSettings } from '../store/models/types';
9
+ import { createListParamSchema, getOrder } from '../../libs/api';
10
+ import logger from '../../libs/logger';
11
+ import { authenticate } from '../../middlewares/hono/security';
12
+ import { CheckoutSession } from '../../store/models/checkout-session';
13
+ import { Customer } from '../../store/models/customer';
14
+ import { PaymentLink } from '../../store/models/payment-link';
15
+ import { PaymentMethod } from '../../store/models/payment-method';
16
+ import { Price } from '../../store/models/price';
17
+ import type { DonationSettings } from '../../store/models/types';
13
18
  import { createPaymentLink } from './payment-links';
14
19
  import { createProductAndPrices } from './products';
15
20
 
16
- const router = Router();
21
+ const app = new Hono();
22
+
23
+ const auth = authenticate({ component: true, roles: ['owner', 'admin'] });
17
24
 
18
25
  // FIXME: add more custom validation here for amount
19
26
  const donationSchema = Joi.object<DonationSettings>({
@@ -44,12 +51,14 @@ const donationSchema = Joi.object<DonationSettings>({
44
51
  summary: Joi.string().optional(),
45
52
  }),
46
53
  });
54
+
47
55
  // prepare donation payment links
48
- router.post('/', async (req, res) => {
56
+ app.post('/', auth, async (c) => {
49
57
  try {
50
- const payload = await donationSchema.validateAsync(req.body, { stripUnknown: true, convert: true });
58
+ const body = c.get('sanitizedBody') ?? {};
59
+ const payload = await donationSchema.validateAsync(body, { stripUnknown: true, convert: true });
51
60
  const link = await PaymentLink.findOne({
52
- where: { 'donation_settings.target': payload.target, livemode: !!req.livemode },
61
+ where: { 'donation_settings.target': payload.target, livemode: !!c.get('livemode') },
53
62
  });
54
63
  if (link) {
55
64
  await link.update({
@@ -64,21 +73,20 @@ router.post('/', async (req, res) => {
64
73
  },
65
74
  });
66
75
  logger.info('Payment link updated successfully', { linkId: link.id });
67
- res.json(link.toJSON());
68
- return;
76
+ return c.json(link.toJSON());
69
77
  }
70
78
 
71
79
  logger.info('No existing payment link found, creating new one');
72
- const lookupKey = `${payload.target}-${req.livemode ? 'live' : 'test'}`;
80
+ const lookupKey = `${payload.target}-${c.get('livemode') ? 'live' : 'test'}`;
73
81
  let price = await Price.findByPkOrLookupKey(lookupKey);
74
82
  if (!price) {
75
83
  logger.info('No existing price found, creating new product and price');
76
84
  const result = await createProductAndPrices({
77
85
  type: 'service',
78
- livemode: !!req.livemode,
86
+ livemode: !!c.get('livemode'),
79
87
  name: payload.title,
80
88
  description: payload.description,
81
- currency_id: req.baseCurrency.id,
89
+ currency_id: c.get('baseCurrency').id,
82
90
  via: 'donation',
83
91
  prices: [
84
92
  {
@@ -101,9 +109,9 @@ router.post('/', async (req, res) => {
101
109
  }
102
110
 
103
111
  const result = await createPaymentLink({
104
- livemode: !!req.livemode,
105
- created_via: req.user?.via,
106
- currency_id: req.baseCurrency.id,
112
+ livemode: !!c.get('livemode'),
113
+ created_via: c.get('user')?.via,
114
+ currency_id: c.get('baseCurrency').id,
107
115
  name: payload.title,
108
116
  submit_type: 'donate',
109
117
  line_items: [{ price_id: price.id, quantity: 1 }],
@@ -116,23 +124,24 @@ router.post('/', async (req, res) => {
116
124
  },
117
125
  });
118
126
  logger.info('New payment link created', { linkId: result.id });
119
- res.json(result);
127
+ return c.json(result);
120
128
  } catch (err) {
121
129
  logger.error('Failed to prepare payment link for donation', err);
122
- res.status(400).json({ error: err.message });
130
+ return c.json({ error: (err as any).message }, 400);
123
131
  }
124
132
  });
125
133
 
126
134
  // get donations by target
127
135
  const paginationSchema = createListParamSchema<{ target: string }>({ target: Joi.string().required() }, 20);
128
- router.get('/', async (req, res) => {
136
+ app.get('/', auth, async (c) => {
129
137
  try {
130
- const { page, pageSize, target } = await paginationSchema.validateAsync(req.query, {
138
+ const query = c.req.query();
139
+ const { page, pageSize, target } = await paginationSchema.validateAsync(query, {
131
140
  convert: true,
132
141
  stripUnknown: true,
133
142
  });
134
143
  const { rows, count } = await CheckoutSession.findAndCountAll({
135
- where: { payment_link_id: target, status: 'complete', livemode: req.livemode },
144
+ where: { payment_link_id: target, status: 'complete', livemode: c.get('livemode') },
136
145
  attributes: [
137
146
  'id',
138
147
  'customer_id',
@@ -143,30 +152,30 @@ router.get('/', async (req, res) => {
143
152
  'created_at',
144
153
  'updated_at',
145
154
  ],
146
- order: getOrder(req.query, [['created_at', 'DESC']]),
155
+ order: getOrder(query, [['created_at', 'DESC']]),
147
156
  offset: (page - 1) * pageSize,
148
157
  include: [{ model: Customer, as: 'customer', attributes: ['id', 'did', 'name', 'metadata'] }],
149
158
  limit: pageSize,
150
159
  });
151
160
 
152
- const method = await PaymentMethod.findByPk(req.baseCurrency.payment_method_id);
161
+ const method = await PaymentMethod.findByPk(c.get('baseCurrency').payment_method_id);
153
162
 
154
163
  const totalAmount: string = rows
155
164
  .map((x) => x.toJSON())
156
165
  .reduce((total, x) => total.add(new BN(x.amount_total)), new BN('0'))
157
166
  .toString();
158
167
 
159
- res.json({
168
+ return c.json({
160
169
  supporters: rows,
161
- currency: req.baseCurrency,
170
+ currency: c.get('baseCurrency'),
162
171
  method,
163
172
  total: count,
164
173
  totalAmount,
165
174
  paging: { page, pageSize },
166
175
  });
167
176
  } catch (err) {
168
- res.status(400).json({ error: err.message, supporters: [] });
177
+ return c.json({ error: (err as any).message, supporters: [] }, 400);
169
178
  }
170
179
  });
171
180
 
172
- export default router;
181
+ export default app;
@@ -1,3 +1,8 @@
1
+ // Phase 3 (express→hono) — hono fork of routes/entitlements.ts. Sub-app with
2
+ // routes relative to /api/entitlements (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
+ //
1
6
  // Cross-channel entitlement query API.
2
7
  //
3
8
  // Endpoints:
@@ -10,15 +15,15 @@
10
15
  // - Logged-in end users (mobile demo, web SPA): `mine: true` lets them in iff
11
16
  // their DID matches the query's customer_did — enforced in the handler.
12
17
 
13
- import { Router } from 'express';
18
+ import { Hono } from 'hono';
14
19
  import Joi from 'joi';
15
20
 
16
- import { checkEntitlement, listEntitlements } from '../libs/entitlement';
17
- import logger from '../libs/logger';
18
- import { authenticate } from '../libs/security';
19
- import { PaymentMethod } from '../store/models';
21
+ import { checkEntitlement, listEntitlements } from '../../libs/entitlement';
22
+ import logger from '../../libs/logger';
23
+ import { authenticate } from '../../middlewares/hono/security';
24
+ import { PaymentMethod } from '../../store/models';
20
25
 
21
- const router = Router();
26
+ const app = new Hono();
22
27
  // component+owner/admin for cross-blocklet calls; ensureLogin for end users —
23
28
  // handler then enforces that non-admin users can only query their own DID.
24
29
  const auth = authenticate<PaymentMethod>({ component: true, roles: ['owner', 'admin'], ensureLogin: true });
@@ -35,8 +40,8 @@ function canonicalDid(did: string | undefined | null): string {
35
40
  return did.startsWith('did:abt:') ? did.slice('did:abt:'.length) : did;
36
41
  }
37
42
 
38
- function isSelf(req: any, customerDid: string): boolean {
39
- const a = canonicalDid(req.user?.did);
43
+ function isSelf(userDid: string | undefined, customerDid: string): boolean {
44
+ const a = canonicalDid(userDid);
40
45
  const b = canonicalDid(customerDid);
41
46
  return !!a && a === b;
42
47
  }
@@ -66,40 +71,38 @@ function parseLivemode(value: string | boolean | undefined, fallback: boolean):
66
71
  return fallback;
67
72
  }
68
73
 
69
- router.get('/check', auth, async (req, res) => {
74
+ app.get('/check', auth, async (c) => {
70
75
  try {
71
- const input = await checkQuerySchema.validateAsync(req.query, { stripUnknown: true });
72
- if (!isAdminUser((req as any).user?.role) && !isSelf(req, input.customer_did)) {
73
- res.status(403).json({ error: 'Cannot query entitlements for other customers' });
74
- return;
76
+ const input = await checkQuerySchema.validateAsync(c.req.query(), { stripUnknown: true });
77
+ if (!isAdminUser(c.get('user')?.role) && !isSelf(c.get('user')?.did, input.customer_did)) {
78
+ return c.json({ error: 'Cannot query entitlements for other customers' }, 403);
75
79
  }
76
- const livemode = parseLivemode(input.livemode, !!req.livemode);
80
+ const livemode = parseLivemode(input.livemode, !!c.get('livemode'));
77
81
  const result = await checkEntitlement({
78
82
  customer_did: input.customer_did,
79
83
  product_id: input.product_id,
80
84
  livemode,
81
85
  });
82
- res.json(result);
86
+ return c.json(result);
83
87
  } catch (err: any) {
84
88
  logger.error('entitlements/check failed', { error: err?.message, stack: err?.stack });
85
- res.status(400).json({ error: err?.message ?? 'check failed' });
89
+ return c.json({ error: err?.message ?? 'check failed' }, 400);
86
90
  }
87
91
  });
88
92
 
89
- router.get('/list', auth, async (req, res) => {
93
+ app.get('/list', auth, async (c) => {
90
94
  try {
91
- const input = await listQuerySchema.validateAsync(req.query, { stripUnknown: true });
92
- if (!isAdminUser((req as any).user?.role) && !isSelf(req, input.customer_did)) {
93
- res.status(403).json({ error: 'Cannot list entitlements for other customers' });
94
- return;
95
+ const input = await listQuerySchema.validateAsync(c.req.query(), { stripUnknown: true });
96
+ if (!isAdminUser(c.get('user')?.role) && !isSelf(c.get('user')?.did, input.customer_did)) {
97
+ return c.json({ error: 'Cannot list entitlements for other customers' }, 403);
95
98
  }
96
- const livemode = parseLivemode(input.livemode, !!req.livemode);
99
+ const livemode = parseLivemode(input.livemode, !!c.get('livemode'));
97
100
  const list = await listEntitlements({ customer_did: input.customer_did, livemode });
98
- res.json({ list });
101
+ return c.json({ list });
99
102
  } catch (err: any) {
100
103
  logger.error('entitlements/list failed', { error: err?.message, stack: err?.stack });
101
- res.status(400).json({ error: err?.message ?? 'list failed' });
104
+ return c.json({ error: err?.message ?? 'list failed' }, 400);
102
105
  }
103
106
  });
104
107
 
105
- export default router;
108
+ export default app;
@@ -1,20 +1,42 @@
1
- import { Router } from 'express';
1
+ // Phase 3 (express→hono) hono fork of routes/events.ts. Sub-app with
2
+ // routes relative to /api/events (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 Joi from 'joi';
3
7
  import type { WhereOptions } from 'sequelize';
4
8
  import { Op } from 'sequelize';
5
9
 
6
- import { createListParamSchema, getOrder } from '../libs/api';
7
- import { authenticate } from '../libs/security';
8
- import { Event } from '../store/models/event';
9
- import { blocklet } from '../libs/auth';
10
- import logger from '../libs/logger';
11
- import { addWebhookJob } from '../queues/webhook';
12
- import { WebhookEndpoint } from '../store/models/webhook-endpoint';
13
- import { Subscription } from '../store/models/subscription';
14
-
15
- const router = Router();
10
+ import { createListParamSchema, getOrder } from '../../libs/api';
11
+ import { context } from '../../libs/context';
12
+ import { authenticate } from '../../middlewares/hono/security';
13
+ import { TENANT_MISMATCH, TenantError, resolveRowTenant } from '../../libs/tenant';
14
+ import { Event } from '../../store/models/event';
15
+ import { blocklet } from '../../libs/auth';
16
+ import logger from '../../libs/logger';
17
+ import { addWebhookJob } from '../../queues/webhook';
18
+ import { WebhookEndpoint } from '../../store/models/webhook-endpoint';
19
+ import { Subscription } from '../../store/models/subscription';
20
+
21
+ const app = new Hono();
16
22
  const auth = authenticate<Event>({ component: true, roles: ['owner', 'admin'] });
17
23
 
24
+ // Phase 4 (W1-3): manual retry endpoints are tenant-guarded — the caller's
25
+ // tenant context must match the event's tenant or the request dies with 4xx
26
+ // before any attempt is scheduled. Exported for unit tests.
27
+ export function assertEventTenantAccessible(event: { instance_did?: string | null }): void {
28
+ const callerTenant = context.getInstanceDid(); // throws TENANT_CONTEXT_MISSING in multi mode without context
29
+ const eventTenant = resolveRowTenant(event);
30
+ if (callerTenant !== eventTenant) {
31
+ throw new TenantError(TENANT_MISMATCH, 'event belongs to another tenant');
32
+ }
33
+ }
34
+
35
+ function tenantErrorStatus(err: any): number | null {
36
+ if (err instanceof TenantError) return err.code === TENANT_MISMATCH ? 403 : 401;
37
+ return null;
38
+ }
39
+
18
40
  const schema = createListParamSchema<{
19
41
  type?: string;
20
42
  object_id?: string;
@@ -22,42 +44,6 @@ const schema = createListParamSchema<{
22
44
  type: Joi.string().empty(''),
23
45
  object_id: Joi.string().empty(''),
24
46
  });
25
- router.get('/', auth, async (req, res) => {
26
- const { page, pageSize, ...query } = await schema.validateAsync(req.query, { stripUnknown: true });
27
- const where: WhereOptions<Event> = {};
28
-
29
- if (query.type) {
30
- where.type = query.type
31
- .split(',')
32
- .map((x) => x.trim())
33
- .filter(Boolean);
34
- }
35
- if (query.object_id) {
36
- where.object_id = query.object_id
37
- .split(',')
38
- .map((x) => x.trim())
39
- .filter(Boolean);
40
- }
41
- if (typeof query.livemode === 'boolean') {
42
- where.livemode = query.livemode;
43
- }
44
-
45
- try {
46
- const { rows: list, count } = await Event.findAndCountAll({
47
- where,
48
- attributes: { exclude: ['data', 'request'] },
49
- order: getOrder(req.query, [['created_at', 'DESC']]),
50
- offset: (page - 1) * pageSize,
51
- limit: pageSize,
52
- include: [],
53
- });
54
-
55
- res.json({ count, list, paging: { page, pageSize } });
56
- } catch (err) {
57
- logger.error(err);
58
- res.json({ count: 0, list: [], paging: { page, pageSize } });
59
- }
60
- });
61
47
 
62
48
  const retryWebhooksSchema = createListParamSchema<{
63
49
  eventType?: string;
@@ -77,14 +63,17 @@ const retryWebhooksSchema = createListParamSchema<{
77
63
  latestOnly: Joi.boolean().optional(),
78
64
  });
79
65
 
80
- router.get('/retry-webhooks', auth, async (req, res) => {
66
+ // static route before /:id
67
+ app.get('/retry-webhooks', auth, async (c) => {
81
68
  try {
82
69
  const { eventType, objectType, objectId, objectIds, eventIds, subscriptionStatus, latestOnly } =
83
- await retryWebhooksSchema.validateAsync(req.query, {
70
+ await retryWebhooksSchema.validateAsync(c.req.query(), {
84
71
  stripUnknown: true,
85
72
  });
86
73
 
87
- const where: WhereOptions<Event> = { livemode: req.livemode };
74
+ // scope the whole batch to the caller's tenant (fail-closed in multi mode)
75
+ const where: WhereOptions<Event> = { livemode: c.get('livemode') };
76
+ (where as any).instance_did = context.getInstanceDid();
88
77
  let targetObjectIds: string[] = [];
89
78
 
90
79
  // Handle subscription status filter
@@ -92,13 +81,13 @@ router.get('/retry-webhooks', auth, async (req, res) => {
92
81
  const subscriptions = await Subscription.findAll({
93
82
  where: {
94
83
  status: subscriptionStatus,
95
- livemode: req.livemode,
84
+ livemode: c.get('livemode'),
96
85
  },
97
86
  attributes: ['id'],
98
87
  });
99
88
 
100
89
  if (subscriptions.length === 0) {
101
- return res.json({
90
+ return c.json({
102
91
  message: `No subscriptions found with status: ${subscriptionStatus}`,
103
92
  scheduled: 0,
104
93
  eventsProcessed: 0,
@@ -147,7 +136,7 @@ router.get('/retry-webhooks', auth, async (req, res) => {
147
136
  });
148
137
 
149
138
  if (events.length === 0) {
150
- return res.json({
139
+ return c.json({
151
140
  message: 'No events found matching the criteria',
152
141
  scheduled: 0,
153
142
  eventsProcessed: 0,
@@ -167,11 +156,11 @@ router.get('/retry-webhooks', auth, async (req, res) => {
167
156
  }
168
157
 
169
158
  const webhooks = await WebhookEndpoint.findAll({
170
- where: { status: 'enabled', livemode: req.livemode },
159
+ where: { status: 'enabled', livemode: c.get('livemode'), instance_did: context.getInstanceDid() },
171
160
  });
172
161
 
173
162
  if (webhooks.length === 0) {
174
- return res.json({
163
+ return c.json({
175
164
  message: 'No enabled webhook endpoints found',
176
165
  scheduled: 0,
177
166
  eventsProcessed: events.length,
@@ -199,21 +188,62 @@ router.get('/retry-webhooks', auth, async (req, res) => {
199
188
  criteria: { eventType, objectType, objectId, subscriptionStatus, latestOnly },
200
189
  });
201
190
 
202
- return res.json({
191
+ return c.json({
203
192
  message: `Successfully scheduled ${scheduled} webhooks for retry across ${events.length} events`,
204
193
  scheduled,
205
194
  eventsProcessed: events.length,
206
195
  });
207
196
  } catch (err: any) {
197
+ const status = tenantErrorStatus(err);
198
+ if (status) {
199
+ return c.json({ error: err.message, code: err.code }, status as any);
200
+ }
208
201
  logger.error('Failed to batch retry webhooks', err);
209
- return res.status(500).json({ error: `Failed to retry webhooks: ${err.message}` });
202
+ return c.json({ error: `Failed to retry webhooks: ${err.message}` }, 500);
210
203
  }
211
204
  });
212
205
 
213
- router.get('/:id', auth, async (req, res) => {
206
+ app.get('/', auth, async (c) => {
207
+ const { page, pageSize, ...query } = await schema.validateAsync(c.req.query(), { stripUnknown: true });
208
+ const where: WhereOptions<Event> = {};
209
+
210
+ if (query.type) {
211
+ where.type = query.type
212
+ .split(',')
213
+ .map((x) => x.trim())
214
+ .filter(Boolean);
215
+ }
216
+ if (query.object_id) {
217
+ where.object_id = query.object_id
218
+ .split(',')
219
+ .map((x) => x.trim())
220
+ .filter(Boolean);
221
+ }
222
+ if (typeof query.livemode === 'boolean') {
223
+ where.livemode = query.livemode;
224
+ }
225
+
226
+ try {
227
+ const { rows: list, count } = await Event.findAndCountAll({
228
+ where,
229
+ attributes: { exclude: ['data', 'request'] },
230
+ order: getOrder(c.req.query(), [['created_at', 'DESC']]),
231
+ offset: (page - 1) * pageSize,
232
+ limit: pageSize,
233
+ include: [],
234
+ });
235
+
236
+ return c.json({ count, list, paging: { page, pageSize } });
237
+ } catch (err) {
238
+ logger.error(err);
239
+ return c.json({ count: 0, list: [], paging: { page, pageSize } });
240
+ }
241
+ });
242
+
243
+ app.get('/:id', auth, async (c) => {
214
244
  try {
215
245
  const doc = await Event.findOne({
216
- where: { id: req.params.id },
246
+ where: { id: c.req.param('id') },
217
247
  include: [],
218
248
  });
219
249
 
@@ -221,34 +251,36 @@ router.get('/:id', auth, async (req, res) => {
221
251
  const requestedBy = doc.request?.requested_by || 'system';
222
252
  const { user } = await blocklet.getUser(requestedBy as string);
223
253
  if (user) {
224
- return res.json({ ...doc.toJSON(), requestInfo: user });
254
+ return c.json({ ...doc.toJSON(), requestInfo: user });
225
255
  }
226
- return res.json(doc);
256
+ return c.json(doc);
227
257
  }
228
- return res.status(404).json(null);
258
+ return c.json(null, 404);
229
259
  } catch (err) {
230
260
  logger.error(err);
231
- return res.status(400).json({ error: `Failed to get event: ${err.message}` });
261
+ return c.json({ error: `Failed to get event: ${(err as any).message}` }, 400);
232
262
  }
233
263
  });
234
264
 
235
- router.post('/:id/retry-webhooks', auth, async (req, res) => {
265
+ app.post('/:id/retry-webhooks', auth, async (c) => {
236
266
  try {
237
267
  const event = await Event.findOne({
238
- where: { id: req.params.id },
268
+ where: { id: c.req.param('id') },
239
269
  });
240
270
 
241
271
  if (!event) {
242
- return res.status(404).json({ error: 'Event not found' });
272
+ return c.json({ error: 'Event not found' }, 404);
243
273
  }
244
274
 
275
+ assertEventTenantAccessible(event);
276
+
245
277
  const webhooks = await WebhookEndpoint.findAll({
246
- where: { status: 'enabled', livemode: event.livemode },
278
+ where: { status: 'enabled', livemode: event.livemode, instance_did: resolveRowTenant(event) },
247
279
  });
248
280
  const eventWebhooks = webhooks.filter((webhook) => webhook.enabled_events.includes(event.type));
249
281
 
250
282
  if (eventWebhooks.length === 0) {
251
- return res.json({ message: 'No enabled webhook endpoints found for this event type', scheduled: 0 });
283
+ return c.json({ message: 'No enabled webhook endpoints found for this event type', scheduled: 0 });
252
284
  }
253
285
 
254
286
  let scheduled = 0;
@@ -262,15 +294,19 @@ router.post('/:id/retry-webhooks', auth, async (req, res) => {
262
294
  }
263
295
  }
264
296
 
265
- return res.json({
297
+ return c.json({
266
298
  message: `Successfully scheduled ${scheduled} webhooks for retry`,
267
299
  scheduled,
268
300
  total: eventWebhooks.length,
269
301
  });
270
302
  } catch (err: any) {
303
+ const status = tenantErrorStatus(err);
304
+ if (status) {
305
+ return c.json({ error: err.message, code: err.code }, status as any);
306
+ }
271
307
  logger.error('Failed to retry webhooks for event', err);
272
- return res.status(500).json({ error: `Failed to retry webhooks: ${err.message}` });
308
+ return c.json({ error: `Failed to retry webhooks: ${err.message}` }, 500);
273
309
  }
274
310
  });
275
311
 
276
- export default router;
312
+ export default app;