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,18 +1,32 @@
1
- import { sessionMiddleware } from '@blocklet/sdk/lib/middlewares/session';
2
- import { Router } from 'express';
1
+ // Phase 3 (express→hono) hono fork of routes/customers.ts. Sub-app with
2
+ // routes relative to /api/customers (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';
3
6
  import Joi from 'joi';
4
7
  import pick from 'lodash/pick';
5
- import isEmail from 'validator/es/lib/isEmail';
8
+ // Use the CommonJS build (`lib`), not the ESM `es` build: `es/lib/isEmail.js`
9
+ // uses extensionless relative imports (`./util/assertString`) that Node ESM
10
+ // strict resolution rejects with ERR_MODULE_NOT_FOUND when payment-core is
11
+ // embedded in a Node host like arc. The `lib` build is plain CJS and resolves
12
+ // everywhere. Same default export.
13
+ import isEmail from 'validator/lib/isEmail';
6
14
 
7
15
  import { Op, type WhereOptions } from 'sequelize';
8
16
  import { BN } from '@ocap/util';
9
- import { getStakeSummaryByDid, getTokenSummaryByDid, getTokenByAddress } from '../integrations/arcblock/stake';
10
- import { createListParamSchema, getOrder, getWhereFromKvQuery, getWhereFromQuery, MetadataSchema } from '../libs/api';
11
- import { authenticate } from '../libs/security';
12
- import { formatMetadata } from '../libs/util';
13
- import { Customer } from '../store/models/customer';
14
- import { blocklet } from '../libs/auth';
15
- import logger from '../libs/logger';
17
+ import { getStakeSummaryByDid, getTokenSummaryByDid, getTokenByAddress } from '../../integrations/arcblock/stake';
18
+ import {
19
+ createListParamSchema,
20
+ getOrder,
21
+ getWhereFromKvQuery,
22
+ getWhereFromQuery,
23
+ MetadataSchema,
24
+ } from '../../libs/api';
25
+ import { authenticate } from '../../middlewares/hono/security';
26
+ import { formatMetadata } from '../../libs/util';
27
+ import { Customer } from '../../store/models/customer';
28
+ import { blocklet } from '../../libs/auth';
29
+ import logger from '../../libs/logger';
16
30
  import {
17
31
  Invoice,
18
32
  PaymentCurrency,
@@ -21,14 +35,13 @@ import {
21
35
  Product,
22
36
  Subscription,
23
37
  SubscriptionItem,
24
- } from '../store/models';
25
- import { getSubscriptionPaymentAddress, calculateRecommendedRechargeAmount } from '../libs/subscription';
26
- import { expandLineItems } from '../libs/session';
27
- import { handleNotificationPreferenceChange } from '../queues/notification';
28
- import { getEndpointAndSpaceDid } from '../libs/did-space';
29
- import { spaceQueue } from '../queues/space';
30
-
31
- const router = Router();
38
+ } from '../../store/models';
39
+ import { getSubscriptionPaymentAddress, calculateRecommendedRechargeAmount } from '../../libs/subscription';
40
+ import { expandLineItems } from '../../libs/session';
41
+ import { handleNotificationPreferenceChange } from '../../queues/notification';
42
+ import { sessionMiddleware } from '../../middlewares/hono/session';
43
+
44
+ const app = new Hono();
32
45
  const auth = authenticate<Customer>({ component: true, roles: ['owner', 'admin'] });
33
46
  const authPortal = authenticate<Customer>({
34
47
  component: true,
@@ -42,8 +55,8 @@ const authPortal = authenticate<Customer>({
42
55
  });
43
56
 
44
57
  const schema = createListParamSchema<{ did?: string }>({ did: Joi.string().empty('') });
45
- router.get('/', auth, async (req, res) => {
46
- const { page, pageSize, ...query } = await schema.validateAsync(req.query, { stripUnknown: true });
58
+ app.get('/', auth, async (c) => {
59
+ const { page, pageSize, ...query } = await schema.validateAsync(c.req.query(), { stripUnknown: true });
47
60
  const where = getWhereFromKvQuery(query.q);
48
61
 
49
62
  if (query.did) {
@@ -59,21 +72,23 @@ router.get('/', auth, async (req, res) => {
59
72
  include: [],
60
73
  });
61
74
 
62
- res.json({ count, list, paging: { page, pageSize } });
75
+ return c.json({ count, list, paging: { page, pageSize } });
63
76
  } catch (err) {
64
77
  logger.error(err);
65
- res.json({ count: 0, list: [], paging: { page, pageSize } });
78
+ return c.json({ count: 0, list: [], paging: { page, pageSize } });
66
79
  }
67
80
  });
68
81
 
69
82
  // search customers
83
+ // Static path — registered before /:id so hono matches it first.
70
84
  const searchSchema = createListParamSchema<{
71
85
  query: string;
72
86
  }>({
73
87
  query: Joi.string(),
74
88
  });
75
- router.get('/search', auth, async (req, res) => {
76
- const { page, pageSize, query, livemode, q, o } = await searchSchema.validateAsync(req.query, {
89
+ app.get('/search', auth, async (c) => {
90
+ const rawQuery = c.req.query();
91
+ const { page, pageSize, query, livemode, q, o } = await searchSchema.validateAsync(rawQuery, {
77
92
  stripUnknown: false,
78
93
  allowUnknown: true,
79
94
  });
@@ -84,35 +99,35 @@ router.get('/search', auth, async (req, res) => {
84
99
  }
85
100
  const { rows: list, count } = await Customer.findAndCountAll({
86
101
  where,
87
- order: getOrder(req.query, [['created_at', o === 'asc' ? 'ASC' : 'DESC']]),
102
+ order: getOrder(rawQuery, [['created_at', o === 'asc' ? 'ASC' : 'DESC']]),
88
103
  offset: (page - 1) * pageSize,
89
104
  limit: pageSize,
90
105
  include: [],
91
106
  });
92
107
 
93
- res.json({ count, list, paging: { page, pageSize } });
108
+ return c.json({ count, list, paging: { page, pageSize } });
94
109
  });
95
110
 
96
- // eslint-disable-next-line consistent-return
97
- router.get('/me', sessionMiddleware({ accessKey: true }), async (req, res) => {
98
- if (!req.user) {
99
- return res.status(403).json({ error: 'Unauthorized' });
111
+ // Static path — registered before /:id so hono matches it first.
112
+ app.get('/me', sessionMiddleware({ accessKey: true }), async (c) => {
113
+ if (!c.get('user')) {
114
+ return c.json({ error: 'Unauthorized' }, 403);
100
115
  }
101
116
 
102
117
  try {
103
- let doc = await Customer.findByPkOrDid(req.user.did as string);
104
- const livemode = req.query.livemode ? !!req?.livemode : !!doc?.livemode;
118
+ let doc = await Customer.findByPkOrDid(c.get('user').did as string);
119
+ const livemode = c.req.query('livemode') ? !!c.get('livemode') : !!doc?.livemode;
105
120
  if (!doc) {
106
- if (req.query.fallback) {
107
- const result = await blocklet.getUser(req.user.did);
108
- return res.json({ ...result.user, address: Customer.formatAddressFromUser(result.user), livemode });
121
+ if (c.req.query('fallback')) {
122
+ const result = await blocklet.getUser(c.get('user').did);
123
+ return c.json({ ...result.user, address: Customer.formatAddressFromUser(result.user), livemode });
109
124
  }
110
- if (req.query.create) {
125
+ if (c.req.query('create')) {
111
126
  // create customer
112
- const { user } = await blocklet.getUser(req.user.did);
127
+ const { user } = await blocklet.getUser(c.get('user').did);
113
128
  const customer = await Customer.create({
114
129
  livemode: true,
115
- did: req.user.did,
130
+ did: c.get('user').did,
116
131
  name: user.fullName,
117
132
  email: user.email,
118
133
  phone: user.phone,
@@ -130,11 +145,11 @@ router.get('/me', sessionMiddleware({ accessKey: true }), async (req, res) => {
130
145
  });
131
146
  doc = customer;
132
147
  } else {
133
- return res.json({ error: 'Customer not found' });
148
+ return c.json({ error: 'Customer not found' });
134
149
  }
135
150
  }
136
- if (req.query.skipSummary) {
137
- return res.json({ ...doc.toJSON(), livemode });
151
+ if (c.req.query('skipSummary')) {
152
+ return c.json({ ...doc.toJSON(), livemode });
138
153
  }
139
154
  try {
140
155
  const [summary, stake, token] = await Promise.all([
@@ -142,11 +157,11 @@ router.get('/me', sessionMiddleware({ accessKey: true }), async (req, res) => {
142
157
  getStakeSummaryByDid(doc.did, livemode),
143
158
  getTokenSummaryByDid(doc.did, livemode),
144
159
  ]);
145
- res.json({ ...doc.toJSON(), summary: { ...summary, stake, token }, livemode });
160
+ return c.json({ ...doc.toJSON(), summary: { ...summary, stake, token }, livemode });
146
161
  } catch (summaryErr) {
147
162
  logger.error('get customer summary failed', summaryErr);
148
- if (req.query.skipError) {
149
- return res.json({
163
+ if (c.req.query('skipError')) {
164
+ return c.json({
150
165
  ...doc.toJSON(),
151
166
  summary: { stake: {}, token: {} },
152
167
  livemode,
@@ -158,147 +173,42 @@ router.get('/me', sessionMiddleware({ accessKey: true }), async (req, res) => {
158
173
  }
159
174
  } catch (err) {
160
175
  logger.error('get customer failed', err);
161
- if (req.query.skipError) {
162
- return res.json({
176
+ if (c.req.query('skipError')) {
177
+ return c.json({
163
178
  error: `Failed to get customer: ${err.message}`,
164
- did: req.user?.did,
165
- name: req.user?.fullName,
179
+ did: c.get('user')?.did,
180
+ name: c.get('user')?.fullName,
166
181
  address: {},
167
- livemode: !!req.query.livemode,
182
+ livemode: !!c.req.query('livemode'),
168
183
  summary: { stake: {}, token: {} },
169
184
  });
170
185
  }
171
- return res.status(500).json({ error: `Failed to get customer: ${err.message}` });
186
+ return c.json({ error: `Failed to get customer: ${err.message}` }, 500);
172
187
  }
173
188
  });
174
189
 
175
- router.post('/sync-to-space', sessionMiddleware(), async (req, res) => {
176
- if (!req.user) {
177
- return res.status(403).json({ error: 'Unauthorized' });
190
+ // Static path registered before /:id so hono matches it first.
191
+ app.get('/recharge', sessionMiddleware({ accessKey: true }), async (c) => {
192
+ if (!c.get('user')) {
193
+ return c.json({ error: 'Unauthorized' }, 403);
178
194
  }
179
- try {
180
- const userDid = req.user.did;
181
- const jobId = `space-${userDid}`;
182
- const { endpoint } = await getEndpointAndSpaceDid(userDid);
183
- if (endpoint) {
184
- const mainTask = await spaceQueue.get(jobId);
185
- if (mainTask) {
186
- return res.json({
187
- success: true,
188
- message: 'Billing data sync already in progress',
189
- });
190
- }
191
- spaceQueue.push({
192
- id: jobId,
193
- job: {
194
- type: 'customer',
195
- data: {
196
- id: userDid,
197
- },
198
- },
199
- delay: 60, // delay 1min
200
- });
201
- logger.info('Queued billing sync to DID Space for user:', { did: req.user.did });
202
-
203
- return res.json({
204
- success: true,
205
- message: 'Billing data sync will start soon',
206
- });
207
- }
208
- return res.json({
209
- success: false,
210
- message: 'No endpoint found for the user',
211
- });
212
- } catch (error) {
213
- return res.json({
214
- success: false,
215
- message: error.message,
216
- });
195
+ if (!c.req.query('currencyId')) {
196
+ return c.json({ error: 'Currency ID is required' }, 400);
217
197
  }
218
- });
219
-
220
- // get overdue invoices
221
- router.get('/:id/overdue/invoices', authPortal, async (req, res) => {
222
198
  try {
223
- const doc = await Customer.findByPkOrDid(req.params.id as string);
224
- if (!doc) {
225
- return res.status(404).json({ error: 'Customer not found' });
226
- }
227
- const where: WhereOptions<Invoice> = {
228
- customer_id: doc.id,
229
- status: ['uncollectible'],
230
- amount_remaining: { [Op.gt]: '0' },
231
- };
232
- if (typeof req.livemode === 'boolean') {
233
- where.livemode = !!req.livemode;
234
- }
235
- const { rows: invoices, count } = await Invoice.findAndCountAll({
236
- where,
237
- include: [
238
- { model: PaymentCurrency, as: 'paymentCurrency' },
239
- { model: PaymentMethod, as: 'paymentMethod' },
240
- ],
241
- });
242
- if (count === 0) {
243
- return res.json({
244
- summary: null,
245
- invoices: [],
246
- subscriptionCount: 0,
247
- });
248
- }
249
- const summary: Record<string, { amount: string; currency: PaymentCurrency; method: PaymentMethod }> = {};
250
- invoices.forEach((invoice) => {
251
- const key = invoice.currency_id;
252
- if (!summary[key]) {
253
- summary[key] = {
254
- amount: '0',
255
- // @ts-ignore
256
- currency: invoice.paymentCurrency,
257
- // @ts-ignore
258
- method: invoice.paymentMethod,
259
- };
260
- }
261
- if (invoice && summary[key]) {
262
- // @ts-ignore
263
- summary[key].amount = new BN(summary[key]?.amount || '0')
264
- .add(new BN(invoice.amount_remaining || '0'))
265
- .toString();
266
- }
267
- });
268
- const subscriptionCount = new Set(invoices.map((x) => x.subscription_id)).size;
269
- return res.json({
270
- summary,
271
- invoices,
272
- subscriptionCount,
273
- customer: doc,
274
- });
275
- } catch (err) {
276
- logger.error(err);
277
- return res.status(500).json({ error: `Failed to get overdue invoices: ${err.message}` });
278
- }
279
- });
280
-
281
- router.get('/recharge', sessionMiddleware({ accessKey: true }), async (req, res) => {
282
- if (!req.user) {
283
- return res.status(403).json({ error: 'Unauthorized' });
284
- }
285
- if (!req.query.currencyId) {
286
- return res.status(400).json({ error: 'Currency ID is required' });
287
- }
288
- try {
289
- const customer = await Customer.findByPkOrDid(req.user.did as string);
199
+ const customer = await Customer.findByPkOrDid(c.get('user').did as string);
290
200
  if (!customer) {
291
- return res.status(404).json({ error: 'Customer not found' });
201
+ return c.json({ error: 'Customer not found' }, 404);
292
202
  }
293
203
 
294
- const paymentCurrency = await PaymentCurrency.findByPk(req.query.currencyId as string);
204
+ const paymentCurrency = await PaymentCurrency.findByPk(c.req.query('currencyId') as string);
295
205
  if (!paymentCurrency) {
296
- return res.status(404).json({ error: 'Currency not found' });
206
+ return c.json({ error: 'Currency not found' }, 404);
297
207
  }
298
208
 
299
209
  const paymentMethod = await PaymentMethod.findByPk(paymentCurrency.payment_method_id);
300
210
  if (!paymentMethod) {
301
- return res.status(404).json({ error: 'Payment method not found' });
211
+ return c.json({ error: 'Payment method not found' }, 404);
302
212
  }
303
213
 
304
214
  let subscriptions = await Subscription.findAll({
@@ -338,7 +248,7 @@ router.get('/recharge', sessionMiddleware({ accessKey: true }), async (req, res)
338
248
  // Calculate recommended recharge cycle and amount
339
249
  const recommendedRecharge = calculateRecommendedRechargeAmount(relatedSubscriptions, paymentCurrency.id);
340
250
 
341
- return res.json({
251
+ return c.json({
342
252
  currency: {
343
253
  ...paymentCurrency.toJSON(),
344
254
  paymentMethod,
@@ -348,79 +258,142 @@ router.get('/recharge', sessionMiddleware({ accessKey: true }), async (req, res)
348
258
  });
349
259
  } catch (err) {
350
260
  logger.error('Error getting balance recharge info', err);
351
- return res.status(500).json({ error: err.message });
261
+ return c.json({ error: err.message }, 500);
352
262
  }
353
263
  });
354
264
 
355
265
  // get address token
356
- router.get('/payer-token', sessionMiddleware({ accessKey: true }), async (req, res) => {
357
- if (!req.user) {
358
- return res.status(403).json({ error: 'Unauthorized' });
266
+ // Static path registered before /:id so hono matches it first.
267
+ app.get('/payer-token', sessionMiddleware({ accessKey: true }), async (c) => {
268
+ if (!c.get('user')) {
269
+ return c.json({ error: 'Unauthorized' }, 403);
359
270
  }
360
- if (!req.query.currencyId) {
361
- return res.status(400).json({ error: 'Currency ID is required' });
271
+ if (!c.req.query('currencyId')) {
272
+ return c.json({ error: 'Currency ID is required' }, 400);
362
273
  }
363
274
  try {
364
- let paymentAddress = req.query.payerAddress as string;
275
+ let paymentAddress = c.req.query('payerAddress') as string;
365
276
  let customer: Customer | null = null;
366
277
  if (!paymentAddress) {
367
- customer = await Customer.findByPkOrDid(req.user.did as string);
278
+ customer = await Customer.findByPkOrDid(c.get('user').did as string);
368
279
  if (!customer) {
369
- return res.status(404).json({ error: 'Customer not found' });
280
+ return c.json({ error: 'Customer not found' }, 404);
370
281
  }
371
282
  paymentAddress = customer.did;
372
283
  }
373
284
 
374
- const paymentCurrency = await PaymentCurrency.findByPk(req.query.currencyId as string);
285
+ const paymentCurrency = await PaymentCurrency.findByPk(c.req.query('currencyId') as string);
375
286
  if (!paymentCurrency) {
376
- return res.status(404).json({ error: 'Currency not found' });
287
+ return c.json({ error: 'Currency not found' }, 404);
377
288
  }
378
289
 
379
290
  const paymentMethod = await PaymentMethod.findByPk(paymentCurrency.payment_method_id);
380
291
  if (!paymentMethod) {
381
- return res.status(404).json({ error: 'Payment method not found' });
292
+ return c.json({ error: 'Payment method not found' }, 404);
382
293
  }
383
294
 
384
295
  if (!['arcblock', 'ethereum', 'base'].includes(paymentMethod.type)) {
385
- return res.status(400).json({ error: `Payment method not supported: ${paymentMethod.type}` });
296
+ return c.json({ error: `Payment method not supported: ${paymentMethod.type}` }, 400);
386
297
  }
387
298
 
388
299
  if (!paymentAddress) {
389
- return res.status(400).json({ error: `Payment address not found for customer: ${customer?.id}` });
300
+ return c.json({ error: `Payment address not found for customer: ${customer?.id}` }, 400);
390
301
  }
391
302
 
392
303
  const token = await getTokenByAddress(paymentAddress, paymentMethod, paymentCurrency);
393
- return res.json({ token, paymentAddress });
304
+ return c.json({ token, paymentAddress });
394
305
  } catch (err) {
395
306
  logger.error('Error getting customer payer token', err);
396
- return res.status(500).json({ error: err.message });
307
+ return c.json({ error: err.message }, 500);
308
+ }
309
+ });
310
+
311
+ // get overdue invoices — /:id/overdue/invoices registered before plain /:id
312
+ app.get('/:id/overdue/invoices', authPortal, async (c) => {
313
+ try {
314
+ const doc = await Customer.findByPkOrDid(c.req.param('id') as string);
315
+ if (!doc) {
316
+ return c.json({ error: 'Customer not found' }, 404);
317
+ }
318
+ const where: WhereOptions<Invoice> = {
319
+ customer_id: doc.id,
320
+ status: ['uncollectible'],
321
+ amount_remaining: { [Op.gt]: '0' },
322
+ };
323
+ if (typeof c.get('livemode') === 'boolean') {
324
+ where.livemode = !!c.get('livemode');
325
+ }
326
+ const { rows: invoices, count } = await Invoice.findAndCountAll({
327
+ where,
328
+ include: [
329
+ { model: PaymentCurrency, as: 'paymentCurrency' },
330
+ { model: PaymentMethod, as: 'paymentMethod' },
331
+ ],
332
+ });
333
+ if (count === 0) {
334
+ return c.json({
335
+ summary: null,
336
+ invoices: [],
337
+ subscriptionCount: 0,
338
+ });
339
+ }
340
+ const summary: Record<string, { amount: string; currency: PaymentCurrency; method: PaymentMethod }> = {};
341
+ invoices.forEach((invoice) => {
342
+ const key = invoice.currency_id;
343
+ if (!summary[key]) {
344
+ summary[key] = {
345
+ amount: '0',
346
+ // @ts-ignore
347
+ currency: invoice.paymentCurrency,
348
+ // @ts-ignore
349
+ method: invoice.paymentMethod,
350
+ };
351
+ }
352
+ if (invoice && summary[key]) {
353
+ // @ts-ignore
354
+ summary[key].amount = new BN(summary[key]?.amount || '0')
355
+ .add(new BN(invoice.amount_remaining || '0'))
356
+ .toString();
357
+ }
358
+ });
359
+ const subscriptionCount = new Set(invoices.map((x) => x.subscription_id)).size;
360
+ return c.json({
361
+ summary,
362
+ invoices,
363
+ subscriptionCount,
364
+ customer: doc,
365
+ });
366
+ } catch (err) {
367
+ logger.error(err);
368
+ return c.json({ error: `Failed to get overdue invoices: ${err.message}` }, 500);
397
369
  }
398
370
  });
399
371
 
400
- router.get('/:id', auth, async (req, res) => {
401
- if (!req.params.id) {
402
- return res.status(400).json({ error: 'Customer ID is required' });
372
+ app.get('/:id', auth, async (c) => {
373
+ if (!c.req.param('id')) {
374
+ return c.json({ error: 'Customer ID is required' }, 400);
403
375
  }
404
376
  try {
405
- const doc = await Customer.findByPkOrDid(req.params.id as string);
377
+ const doc = await Customer.findByPkOrDid(c.req.param('id') as string);
406
378
  if (doc) {
407
- return res.json(doc);
379
+ return c.json(doc);
408
380
  }
409
- if (req.body.create) {
410
- if (!req.user) {
411
- return res.status(403).json({ error: 'Unauthorized' });
381
+ const body = c.get('sanitizedBody') ?? {};
382
+ if (body.create) {
383
+ if (!c.get('user')) {
384
+ return c.json({ error: 'Unauthorized' }, 403);
412
385
  }
413
- if (req.params.id.startsWith('cus_')) {
414
- return res.status(404).json({ error: 'Customer not found' });
386
+ if (c.req.param('id').startsWith('cus_')) {
387
+ return c.json({ error: 'Customer not found' }, 404);
415
388
  }
416
- const { user } = await blocklet.getUser(req.params.id);
389
+ const { user } = await blocklet.getUser(c.req.param('id'));
417
390
  if (!user) {
418
- return res.status(404).json({ error: 'User not found' });
391
+ return c.json({ error: 'User not found' }, 404);
419
392
  }
420
393
  const customer = await Customer.create({
421
394
  livemode: true,
422
- did: user?.did || req.params.id,
423
- name: user?.fullName ?? req.params.id,
395
+ did: user?.did || c.req.param('id'),
396
+ name: user?.fullName ?? c.req.param('id'),
424
397
  email: user?.email ?? '',
425
398
  phone: user?.phone ?? '',
426
399
  address: Customer.formatAddressFromUser(user),
@@ -435,21 +408,20 @@ router.get('/:id', auth, async (req, res) => {
435
408
  customerId: customer.id,
436
409
  did: customer.did,
437
410
  });
438
- return res.json(customer);
411
+ return c.json(customer);
439
412
  }
440
- return res.status(404).json(null);
413
+ return c.json(null, 404);
441
414
  } catch (err) {
442
415
  logger.error(err);
443
- return res.status(500).json({ error: `Failed to get customer: ${err.message}` });
416
+ return c.json({ error: `Failed to get customer: ${err.message}` }, 500);
444
417
  }
445
418
  });
446
419
 
447
- router.get('/:id/summary', auth, async (req, res) => {
420
+ app.get('/:id/summary', auth, async (c) => {
448
421
  try {
449
- const doc = await Customer.findByPkOrDid(req.params.id as string);
422
+ const doc = await Customer.findByPkOrDid(c.req.param('id') as string);
450
423
  if (!doc) {
451
- res.status(404).json({ error: 'Customer not found' });
452
- return;
424
+ return c.json({ error: 'Customer not found' }, 404);
453
425
  }
454
426
 
455
427
  const [summary, stake, token] = await Promise.all([
@@ -457,10 +429,10 @@ router.get('/:id/summary', auth, async (req, res) => {
457
429
  getStakeSummaryByDid(doc.did, doc.livemode),
458
430
  getTokenSummaryByDid(doc.did, doc.livemode),
459
431
  ]);
460
- res.json({ ...summary, stake, token });
432
+ return c.json({ ...summary, stake, token });
461
433
  } catch (err) {
462
434
  logger.error(err);
463
- res.json(null);
435
+ return c.json(null);
464
436
  }
465
437
  });
466
438
 
@@ -484,20 +456,22 @@ const updatePreferenceSchema = Joi.object({
484
456
  }).optional(),
485
457
  }).unknown(false);
486
458
 
487
- router.put('/preference', sessionMiddleware({ accessKey: true }), async (req, res) => {
459
+ // Static path registered before PUT /:id so hono matches it first.
460
+ app.put('/preference', sessionMiddleware({ accessKey: true }), async (c) => {
488
461
  try {
489
- if (!req.user) {
490
- return res.status(403).json({ error: 'Unauthorized' });
462
+ if (!c.get('user')) {
463
+ return c.json({ error: 'Unauthorized' }, 403);
491
464
  }
492
465
 
493
- const doc = await Customer.findByPkOrDid(req.user.did as string);
466
+ const doc = await Customer.findByPkOrDid(c.get('user').did as string);
494
467
  if (!doc) {
495
- return res.status(404).json({ error: 'Customer not found' });
468
+ return c.json({ error: 'Customer not found' }, 404);
496
469
  }
497
470
 
498
- const { error, value } = updatePreferenceSchema.validate(req.body);
471
+ const body = c.get('sanitizedBody') ?? {};
472
+ const { error, value } = updatePreferenceSchema.validate(body);
499
473
  if (error) {
500
- return res.status(400).json({ error: error.message });
474
+ return c.json({ error: error.message }, 400);
501
475
  }
502
476
 
503
477
  // Get old preference before update
@@ -514,10 +488,10 @@ router.put('/preference', sessionMiddleware({ accessKey: true }), async (req, re
514
488
  await handleNotificationPreferenceChange(doc.id, value.notification);
515
489
  }
516
490
 
517
- return res.json(doc);
491
+ return c.json(doc);
518
492
  } catch (err) {
519
493
  logger.error('Failed to update customer preference', err);
520
- return res.status(400).json({ error: `Failed to update preference: ${err.message}` });
494
+ return c.json({ error: `Failed to update preference: ${err.message}` }, 400);
521
495
  }
522
496
  });
523
497
 
@@ -542,29 +516,30 @@ const updateCustomerSchema = Joi.object({
542
516
  postal_code: Joi.string().max(20).empty(''),
543
517
  }).empty(''),
544
518
  }).unknown(true);
545
- // eslint-disable-next-line consistent-return
546
- router.put('/:id', authPortal, async (req, res) => {
519
+
520
+ app.put('/:id', authPortal, async (c) => {
547
521
  try {
548
- const doc = await Customer.findByPkOrDid(req.params.id as string);
522
+ const doc = await Customer.findByPkOrDid(c.req.param('id') as string);
549
523
  if (!doc) {
550
- return res.status(404).json({ error: 'Customer not found' });
524
+ return c.json({ error: 'Customer not found' }, 404);
551
525
  }
552
526
 
553
- const raw = pick(req.body, ['metadata', 'name', 'email', 'phone', 'address']);
527
+ const body = c.get('sanitizedBody') ?? {};
528
+ const raw = pick(body, ['metadata', 'name', 'email', 'phone', 'address']);
554
529
  const { error } = updateCustomerSchema.validate(raw);
555
530
  if (error) {
556
- return res.status(400).json({ error: error.message });
531
+ return c.json({ error: error.message }, 400);
557
532
  }
558
533
  if (raw.metadata) {
559
534
  raw.metadata = formatMetadata(raw.metadata);
560
535
  }
561
536
 
562
537
  await doc.update(raw);
563
- res.json(doc);
538
+ return c.json(doc);
564
539
  } catch (err) {
565
540
  logger.error(err);
566
- res.json(null);
541
+ return c.json(null);
567
542
  }
568
543
  });
569
544
 
570
- export default router;
545
+ export default app;