payment-kit 1.29.1 → 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 (310) 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/migrations/0006_tenant_columns.sql +46 -0
  236. package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
  237. package/cloudflare/migrations/0008_schema_parity.sql +16 -0
  238. package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
  239. package/cloudflare/queue-runtime-mode.ts +13 -0
  240. package/cloudflare/run-build.js +10 -56
  241. package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
  242. package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
  243. package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
  244. package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
  245. package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
  246. package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
  247. package/cloudflare/shims/blocklet-sdk/util-csrf.ts +13 -0
  248. package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
  249. package/cloudflare/shims/cron.ts +38 -158
  250. package/cloudflare/shims/events.ts +124 -0
  251. package/cloudflare/shims/fastq.ts +15 -1
  252. package/cloudflare/shims/nedb-storage.ts +16 -8
  253. package/cloudflare/shims/xss.ts +8 -0
  254. package/cloudflare/tenant-middleware.ts +36 -0
  255. package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
  256. package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
  257. package/cloudflare/worker.ts +204 -433
  258. package/cloudflare/wrangler.local-e2e.jsonc +26 -0
  259. package/jest.config.js +3 -1
  260. package/package.json +33 -38
  261. package/scripts/core-env-whitelist.json +1 -0
  262. package/scripts/e2e-12b-runtime.ts +149 -0
  263. package/scripts/e2e-core-config.ts +125 -0
  264. package/scripts/e2e-d1-tenancy.ts +116 -0
  265. package/scripts/e2e-d2-cron-queue.ts +139 -0
  266. package/scripts/e2e-d3-embedded-multi.ts +171 -0
  267. package/scripts/e2e-hono-s2.ts +125 -0
  268. package/scripts/e2e-hono-s3e.ts +135 -0
  269. package/scripts/e2e-hono-s4.ts +114 -0
  270. package/scripts/e2e-migration-contract.ts +100 -0
  271. package/scripts/e2e-s0.ts +61 -0
  272. package/scripts/e2e-s1.ts +107 -0
  273. package/scripts/e2e-s2.ts +178 -0
  274. package/scripts/e2e-s3.ts +110 -0
  275. package/scripts/e2e-s4.ts +191 -0
  276. package/scripts/e2e-s5.ts +139 -0
  277. package/scripts/e2e-s6.ts +127 -0
  278. package/scripts/e2e-tenant-model.ts +119 -0
  279. package/scripts/e2e-tenant-worker.ts +199 -0
  280. package/scripts/gen-sql-migrations.js +46 -0
  281. package/scripts/phase8-codemod.js +219 -0
  282. package/scripts/phase9a-env-getters-codemod.js +82 -0
  283. package/scripts/scan-core-env.js +109 -0
  284. package/scripts/scan-tenant-queries.js +235 -0
  285. package/scripts/schema-drift-guard.ts +210 -0
  286. package/scripts/tenant-scan-whitelist.json +1 -0
  287. package/src/env.d.ts +13 -1
  288. package/tsconfig.json +1 -1
  289. package/api/src/libs/did-space.ts +0 -235
  290. package/api/src/libs/middleware.ts +0 -50
  291. package/api/src/libs/security.ts +0 -192
  292. package/api/src/queues/space.ts +0 -662
  293. package/api/src/routes/credit-tokens.ts +0 -38
  294. package/api/src/routes/exchange-rates.ts +0 -87
  295. package/api/src/routes/index.ts +0 -142
  296. package/api/src/routes/integrations/stripe.ts +0 -61
  297. package/api/src/routes/meters.ts +0 -274
  298. package/api/src/routes/passports.ts +0 -68
  299. package/api/src/routes/redirect.ts +0 -20
  300. package/api/src/routes/tool.ts +0 -65
  301. package/api/src/routes/webhook-endpoints.ts +0 -126
  302. package/api/tests/routes/credit-grants.spec.ts +0 -1261
  303. package/cloudflare/shims/did-space-js.ts +0 -17
  304. package/cloudflare/shims/did-space.ts +0 -11
  305. package/cloudflare/shims/express-compat/index.ts +0 -80
  306. package/cloudflare/shims/express-compat/types.ts +0 -41
  307. package/cloudflare/shims/lock.ts +0 -115
  308. package/cloudflare/shims/queue.ts +0 -611
  309. package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
  310. package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
@@ -1,18 +1,22 @@
1
- import { Router } from 'express';
1
+ // Phase 3 (express→hono) hono fork of routes/exchange-rate-providers.ts. Sub-app with
2
+ // routes relative to /api/exchange-rate-providers (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 } from 'sequelize';
4
8
 
5
- import { authenticate } from '../libs/security';
6
- import { ExchangeRateProvider } from '../store/models/exchange-rate-provider';
7
- import { TokenDataProvider } from '../libs/exchange-rate/token-data-provider';
8
- import { CoinGeckoProvider } from '../libs/exchange-rate/coingecko-provider';
9
- import { CoinMarketCapProvider } from '../libs/exchange-rate/coinmarketcap-provider';
9
+ import { authenticate } from '../../middlewares/hono/security';
10
+ import { ExchangeRateProvider } from '../../store/models/exchange-rate-provider';
11
+ import { TokenDataProvider } from '../../libs/exchange-rate/token-data-provider';
12
+ import { CoinGeckoProvider } from '../../libs/exchange-rate/coingecko-provider';
13
+ import { CoinMarketCapProvider } from '../../libs/exchange-rate/coinmarketcap-provider';
10
14
 
11
- const router = Router();
15
+ const app = new Hono();
12
16
  const auth = authenticate({ component: true, roles: ['owner', 'admin'], ensureLogin: true });
13
17
  const allowedStatuses = ['active', 'degraded', 'paused', 'inactive'];
14
18
 
15
- router.get('/', auth, async (_req, res) => {
19
+ app.get('/', auth, async (c) => {
16
20
  const providers = await ExchangeRateProvider.findAll({
17
21
  order: [
18
22
  ['priority', 'ASC'],
@@ -26,10 +30,10 @@ router.get('/', auth, async (_req, res) => {
26
30
  config: ExchangeRateProvider.maskConfig(p.config),
27
31
  }));
28
32
 
29
- res.json({ data: maskedProviders });
33
+ return c.json({ data: maskedProviders });
30
34
  });
31
35
 
32
- router.post('/', auth, async (req, res) => {
36
+ app.post('/', auth, async (c) => {
33
37
  const {
34
38
  name,
35
39
  type = 'token-data',
@@ -39,18 +43,15 @@ router.post('/', auth, async (req, res) => {
39
43
  config = {},
40
44
  // eslint-disable-next-line @typescript-eslint/naming-convention
41
45
  paused_reason = null,
42
- } = req.body;
46
+ } = c.get('sanitizedBody') ?? {};
43
47
  if (!name) {
44
- res.status(400).json({ error: 'name is required' });
45
- return;
48
+ return c.json({ error: 'name is required' }, 400);
46
49
  }
47
50
  if (Number.isNaN(Number(priority))) {
48
- res.status(400).json({ error: 'priority must be a number' });
49
- return;
51
+ return c.json({ error: 'priority must be a number' }, 400);
50
52
  }
51
53
  if (!allowedStatuses.includes(status)) {
52
- res.status(400).json({ error: 'invalid status' });
53
- return;
54
+ return c.json({ error: 'invalid status' }, 400);
54
55
  }
55
56
 
56
57
  const provider = await ExchangeRateProvider.create({
@@ -63,39 +64,7 @@ router.post('/', auth, async (req, res) => {
63
64
  config: ExchangeRateProvider.encryptConfig(config),
64
65
  });
65
66
 
66
- res.json({ data: provider });
67
- });
68
-
69
- router.put('/:id', auth, async (req, res) => {
70
- const provider = await ExchangeRateProvider.findByPk(req.params.id);
71
- if (!provider) {
72
- res.status(404).json({ error: 'Provider not found' });
73
- return;
74
- }
75
-
76
- // Note: 'type' is intentionally excluded - cannot be changed after creation
77
- const updates = pick(req.body, ['name', 'enabled', 'priority', 'status', 'paused_reason', 'config']);
78
- if (updates.priority !== undefined && Number.isNaN(Number(updates.priority))) {
79
- res.status(400).json({ error: 'priority must be a number' });
80
- return;
81
- }
82
- if (updates.status && !allowedStatuses.includes(updates.status)) {
83
- res.status(400).json({ error: 'invalid status' });
84
- return;
85
- }
86
-
87
- // Encrypt config if it's being updated
88
- if (updates.config) {
89
- const existingConfig = ExchangeRateProvider.decryptConfig(provider.config) || {};
90
- const mergedConfig = { ...existingConfig, ...updates.config };
91
- if (updates.config.api_key === '') {
92
- delete mergedConfig.api_key;
93
- }
94
- updates.config = ExchangeRateProvider.encryptConfig(mergedConfig);
95
- }
96
-
97
- await provider.update(updates);
98
- res.json({ data: provider });
67
+ return c.json({ data: provider });
99
68
  });
100
69
 
101
70
  // TECHNICAL DEBT: Exchange rate provider management bypasses AFS layer
@@ -106,75 +75,18 @@ router.put('/:id', auth, async (req, res) => {
106
75
  // Decision: Accepted as pragmatic completion of existing system (2026-01-12)
107
76
  // Reference: ai/intent/20260112-token-data-provider-management-alignment.md
108
77
 
109
- /**
110
- * DELETE endpoint - Remove an exchange rate provider
111
- * Safety constraints:
112
- * 1. Cannot delete provider that is currently in use
113
- * 2. Cannot delete the last enabled provider
114
- */
115
- router.delete('/:id', auth, async (req, res) => {
116
- const provider = await ExchangeRateProvider.findByPk(req.params.id);
117
- if (!provider) {
118
- res.status(404).json({ error: 'Provider not found' });
119
- return;
120
- }
121
-
122
- // Safety Check 1: Get active provider (same logic as frontend)
123
- const allProviders = await ExchangeRateProvider.findAll({
124
- order: [
125
- ['priority', 'ASC'],
126
- ['created_at', 'ASC'],
127
- ],
128
- });
129
-
130
- const activeProviders = allProviders
131
- .filter((p) => p.enabled && p.status !== 'paused')
132
- .sort((a, b) => a.priority - b.priority);
133
-
134
- const activeProviderId = activeProviders[0]?.id || null;
135
-
136
- if (provider.id === activeProviderId) {
137
- res.status(400).json({
138
- error: 'Cannot delete provider that is currently in use',
139
- });
140
- return;
141
- }
142
-
143
- // Safety Check 2: Ensure we're not deleting the last enabled provider
144
- const otherEnabledCount = await ExchangeRateProvider.count({
145
- where: {
146
- id: { [Op.ne]: req.params.id },
147
- enabled: true,
148
- status: { [Op.ne]: 'paused' },
149
- },
150
- });
151
-
152
- if (otherEnabledCount === 0 && provider.enabled && provider.status !== 'paused') {
153
- res.status(400).json({
154
- error:
155
- 'Cannot delete the last enabled provider. Please ensure at least one other provider is enabled before deleting this one.',
156
- });
157
- return;
158
- }
159
-
160
- // All checks passed, safe to delete
161
- await provider.destroy();
162
-
163
- res.json({ success: true });
164
- });
165
-
166
78
  /**
167
79
  * POST /test-connection - Test provider connection without saving
168
80
  * Validates provider configuration by attempting to fetch a test rate
81
+ * Registered before /:id routes so the static segment wins
169
82
  */
170
- router.post('/test-connection', auth, async (req, res) => {
171
- const { type, config = {}, provider_id: providerId } = req.body;
83
+ app.post('/test-connection', auth, async (c) => {
84
+ const { type, config = {}, provider_id: providerId } = c.get('sanitizedBody') ?? {};
172
85
 
173
86
  // Validate provider type
174
87
  const allowedTypes = ['token-data', 'coingecko', 'coinmarketcap'];
175
88
  if (!type || !allowedTypes.includes(type)) {
176
- res.status(400).json({ error: 'Invalid provider type' });
177
- return;
89
+ return c.json({ error: 'Invalid provider type' }, 400);
178
90
  }
179
91
 
180
92
  // Decrypt config if it contains encrypted api_key
@@ -209,15 +121,16 @@ router.post('/test-connection', auth, async (req, res) => {
209
121
  provider = new CoinMarketCapProvider(effectiveConfig || undefined);
210
122
  break;
211
123
  default:
212
- res.status(400).json({ error: 'Invalid provider type' });
213
- return;
124
+ return c.json({ error: 'Invalid provider type' }, 400);
214
125
  }
215
126
  } catch (error: any) {
216
- res.status(200).json({
217
- success: false,
218
- error: error.message || 'Failed to initialize provider',
219
- });
220
- return;
127
+ return c.json(
128
+ {
129
+ success: false,
130
+ error: error.message || 'Failed to initialize provider',
131
+ },
132
+ 200
133
+ );
221
134
  }
222
135
 
223
136
  const testSymbol = type === 'token-data' ? 'ABT' : 'ETH';
@@ -227,7 +140,7 @@ router.post('/test-connection', auth, async (req, res) => {
227
140
  const result = await provider.fetch(testSymbol);
228
141
  const responseTime = Date.now() - startTime;
229
142
 
230
- res.json({
143
+ return c.json({
231
144
  success: true,
232
145
  responseTime,
233
146
  rate: result.rate,
@@ -236,13 +149,112 @@ router.post('/test-connection', auth, async (req, res) => {
236
149
  });
237
150
  } catch (error: any) {
238
151
  const responseTime = Date.now() - startTime;
239
- res.status(200).json({
240
- success: false,
241
- responseTime,
242
- error: error.message || 'Connection test failed',
243
- symbol: testSymbol,
244
- });
152
+ return c.json(
153
+ {
154
+ success: false,
155
+ responseTime,
156
+ error: error.message || 'Connection test failed',
157
+ symbol: testSymbol,
158
+ },
159
+ 200
160
+ );
161
+ }
162
+ });
163
+
164
+ /**
165
+ * DELETE endpoint - Remove an exchange rate provider
166
+ * Safety constraints:
167
+ * 1. Cannot delete provider that is currently in use
168
+ * 2. Cannot delete the last enabled provider
169
+ */
170
+ app.put('/:id', auth, async (c) => {
171
+ const provider = await ExchangeRateProvider.findByPk(c.req.param('id'));
172
+ if (!provider) {
173
+ return c.json({ error: 'Provider not found' }, 404);
174
+ }
175
+
176
+ // Note: 'type' is intentionally excluded - cannot be changed after creation
177
+ const updates = pick(c.get('sanitizedBody') ?? {}, [
178
+ 'name',
179
+ 'enabled',
180
+ 'priority',
181
+ 'status',
182
+ 'paused_reason',
183
+ 'config',
184
+ ]);
185
+ if (updates.priority !== undefined && Number.isNaN(Number(updates.priority))) {
186
+ return c.json({ error: 'priority must be a number' }, 400);
245
187
  }
188
+ if (updates.status && !allowedStatuses.includes(updates.status)) {
189
+ return c.json({ error: 'invalid status' }, 400);
190
+ }
191
+
192
+ // Encrypt config if it's being updated
193
+ if (updates.config) {
194
+ const existingConfig = ExchangeRateProvider.decryptConfig(provider.config) || {};
195
+ const mergedConfig = { ...existingConfig, ...updates.config };
196
+ if (updates.config.api_key === '') {
197
+ delete mergedConfig.api_key;
198
+ }
199
+ updates.config = ExchangeRateProvider.encryptConfig(mergedConfig);
200
+ }
201
+
202
+ await provider.update(updates);
203
+ return c.json({ data: provider });
204
+ });
205
+
206
+ app.delete('/:id', auth, async (c) => {
207
+ const provider = await ExchangeRateProvider.findByPk(c.req.param('id'));
208
+ if (!provider) {
209
+ return c.json({ error: 'Provider not found' }, 404);
210
+ }
211
+
212
+ // Safety Check 1: Get active provider (same logic as frontend)
213
+ const allProviders = await ExchangeRateProvider.findAll({
214
+ order: [
215
+ ['priority', 'ASC'],
216
+ ['created_at', 'ASC'],
217
+ ],
218
+ });
219
+
220
+ const activeProviders = allProviders
221
+ .filter((p) => p.enabled && p.status !== 'paused')
222
+ .sort((a, b) => a.priority - b.priority);
223
+
224
+ const activeProviderId = activeProviders[0]?.id || null;
225
+
226
+ if (provider.id === activeProviderId) {
227
+ return c.json(
228
+ {
229
+ error: 'Cannot delete provider that is currently in use',
230
+ },
231
+ 400
232
+ );
233
+ }
234
+
235
+ // Safety Check 2: Ensure we're not deleting the last enabled provider
236
+ const otherEnabledCount = await ExchangeRateProvider.count({
237
+ where: {
238
+ id: { [Op.ne]: c.req.param('id') },
239
+ enabled: true,
240
+ status: { [Op.ne]: 'paused' },
241
+ },
242
+ });
243
+
244
+ if (otherEnabledCount === 0 && provider.enabled && provider.status !== 'paused') {
245
+ return c.json(
246
+ {
247
+ error:
248
+ 'Cannot delete the last enabled provider. Please ensure at least one other provider is enabled before deleting this one.',
249
+ },
250
+ 400
251
+ );
252
+ }
253
+
254
+ // All checks passed, safe to delete
255
+ await provider.destroy();
256
+
257
+ return c.json({ success: true });
246
258
  });
247
259
 
248
- export default router;
260
+ export default app;
@@ -0,0 +1,77 @@
1
+ // Phase 3a (express→hono) — hono fork of routes/exchange-rates.ts. Sub-app with
2
+ // routes relative to /api/exchange-rates (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';
6
+
7
+ import { getExchangeRateService } from '../../libs/exchange-rate/service';
8
+ import { getExchangeRateSymbol, hasTokenAddress } from '../../libs/exchange-rate/token-address-mapping';
9
+ import { authenticate } from '../../middlewares/hono/security';
10
+ import { ChainType, PaymentCurrency, PaymentMethod } from '../../store/models';
11
+
12
+ const app = new Hono();
13
+ const auth = authenticate({ component: true, roles: ['owner', 'admin'], ensureLogin: true });
14
+ const exchangeRateService = getExchangeRateService();
15
+
16
+ /**
17
+ * Validate if exchange rate can be fetched for a currency.
18
+ * Used during price creation/editing to validate the base currency.
19
+ */
20
+ app.post('/validate', auth, async (c) => {
21
+ try {
22
+ const { currency: currencyId } = (c.get('sanitizedBody') ?? {}) as any;
23
+
24
+ if (!currencyId) {
25
+ return c.json({ error: 'currency is required' }, 400);
26
+ }
27
+
28
+ const paymentCurrency = (await PaymentCurrency.findByPk(currencyId, {
29
+ include: [{ model: PaymentMethod, as: 'payment_method' }],
30
+ })) as PaymentCurrency & { payment_method: PaymentMethod };
31
+
32
+ if (!paymentCurrency) {
33
+ return c.json({ error: 'Currency not found' }, 400);
34
+ }
35
+
36
+ if (paymentCurrency.payment_method?.type === 'stripe') {
37
+ return c.json({ error: `Currency ${paymentCurrency.symbol} is not supported.`, supported: false }, 400);
38
+ }
39
+
40
+ if (!hasTokenAddress(paymentCurrency.symbol, paymentCurrency.payment_method?.type as ChainType)) {
41
+ return c.json({ error: `Currency ${paymentCurrency.symbol} is not supported.`, supported: false }, 400);
42
+ }
43
+
44
+ try {
45
+ const rateSymbol = getExchangeRateSymbol(
46
+ paymentCurrency.symbol,
47
+ paymentCurrency.payment_method?.type as ChainType
48
+ );
49
+ const rateResult = await exchangeRateService.getRate(rateSymbol);
50
+
51
+ return c.json({
52
+ supported: true,
53
+ currency: paymentCurrency.symbol,
54
+ base_currency: 'USD',
55
+ rate: rateResult.rate,
56
+ timestamp_ms: rateResult.timestamp_ms,
57
+ fetched_at: rateResult.fetched_at,
58
+ provider_id: rateResult.provider_id,
59
+ provider_name: rateResult.provider_name,
60
+ provider_display: rateResult.provider_display,
61
+ providers: rateResult.providers,
62
+ consensus_method: rateResult.consensus_method,
63
+ degraded: rateResult.degraded,
64
+ degraded_reason: rateResult.degraded_reason,
65
+ });
66
+ } catch (error: any) {
67
+ return c.json(
68
+ { error: `Failed to fetch exchange rate for ${paymentCurrency.symbol}: ${error.message}`, supported: false },
69
+ 400
70
+ );
71
+ }
72
+ } catch (error: any) {
73
+ return c.json({ error: error.message }, 400);
74
+ }
75
+ });
76
+
77
+ export default app;
@@ -0,0 +1,115 @@
1
+ // Phase 3 (express→hono) — registry of MIGRATED native resource domains. Each
2
+ // domain is mounted via mountResourceGroup (app-shell pipeline + livemode +
3
+ // optional baseCurrency, scoped to the prefix). Domains NOT listed here are still
4
+ // served by the legacy express app through the catch-all bridge; both coexist
5
+ // until Phase 4 deletes the express modules. Batches add domains here one PR at a
6
+ // time (3a → 3f).
7
+ import type { Hono, MiddlewareHandler } from 'hono';
8
+ import { mountResourceGroup } from '../../middlewares/hono/resource-mount';
9
+
10
+ import exchangeRates from './exchange-rates';
11
+ import paymentCurrencies from './payment-currencies';
12
+ import paymentStats from './payment-stats';
13
+ import pricingTable from './pricing-table';
14
+ import customers from './customers';
15
+ import paymentMethods from './payment-methods';
16
+ import products from './products';
17
+ import prices from './prices';
18
+ import checkoutSessions from './checkout-sessions';
19
+ import subscriptions from './subscriptions';
20
+ import invoices from './invoices';
21
+ import paymentIntents from './payment-intents';
22
+ import coupons from './coupons';
23
+ import promotionCodes from './promotion-codes';
24
+ import creditGrants from './credit-grants';
25
+ import creditTokens from './credit-tokens';
26
+ import creditTransactions from './credit-transactions';
27
+ import meterEvents from './meter-events';
28
+ import usageRecords from './usage-records';
29
+ import stripeIntegration from './integrations/stripe';
30
+ import appStoreIntegration from './integrations/app-store';
31
+ import googlePlayIntegration from './integrations/google-play';
32
+ import autoRechargeConfigs from './auto-recharge-configs';
33
+ import donations from './donations';
34
+ import entitlements from './entitlements';
35
+ import events from './events';
36
+ import exchangeRateProviders from './exchange-rate-providers';
37
+ import meters from './meters';
38
+ import passports from './passports';
39
+ import paymentLinks from './payment-links';
40
+ import payouts from './payouts';
41
+ import redirect from './redirect';
42
+ import refunds from './refunds';
43
+ import settings from './settings';
44
+ import subscriptionItems from './subscription-items';
45
+ import taxRates from './tax-rates';
46
+ import tool from './tool';
47
+ import vendor from './vendor';
48
+ import webhookAttempts from './webhook-attempts';
49
+ import webhookEndpoints from './webhook-endpoints';
50
+ import archive from './archive';
51
+
52
+ export function mountMigratedResources(native: Hono, opts: { appShell?: MiddlewareHandler[] } = {}): void {
53
+ // The node host passes its full app-shell pipeline (cors/xss/csrf/cdn/i18n/
54
+ // context). The CF worker passes nothing → mountResourceGroup defaults to the
55
+ // LITE app-shell (xss only; the worker owns cors + tenant and never ran csrf/cdn).
56
+ const shell = opts.appShell;
57
+ const g = (prefix: string, subApp: Hono, o: { baseCurrency?: boolean } = {}) =>
58
+ mountResourceGroup(native, prefix, subApp, { ...o, appShell: shell });
59
+
60
+ // ── Batch 3a (low-risk read-only / validation) ─────────────────────────────
61
+ g('/api/exchange-rates', exchangeRates);
62
+ g('/api/payment-currencies', paymentCurrencies);
63
+ g('/api/payment-stats', paymentStats);
64
+ g('/api/pricing-tables', pricingTable);
65
+
66
+ // ── Batch 3b (customers / payment-methods / products / prices) ──────────────
67
+ g('/api/customers', customers);
68
+ g('/api/payment-methods', paymentMethods);
69
+ g('/api/products', products, { baseCurrency: true });
70
+ g('/api/prices', prices, { baseCurrency: true });
71
+
72
+ // ── Batch 3c (checkout-sessions / subscriptions / invoices / payment-intents) ─
73
+ g('/api/checkout-sessions', checkoutSessions, { baseCurrency: true });
74
+ g('/api/subscriptions', subscriptions);
75
+ g('/api/invoices', invoices);
76
+ g('/api/payment-intents', paymentIntents);
77
+
78
+ // ── Batch 3d (coupons / promotion-codes / credit-* / meter-events / usage-records)
79
+ g('/api/coupons', coupons);
80
+ g('/api/promotion-codes', promotionCodes);
81
+ g('/api/credit-grants', creditGrants);
82
+ g('/api/credit-tokens', creditTokens);
83
+ g('/api/credit-transactions', creditTransactions);
84
+ g('/api/meter-events', meterEvents);
85
+ g('/api/usage-records', usageRecords);
86
+
87
+ // ── Batch 3e (integrations) — Stripe webhook is RAW-BODY (xss skips that path,
88
+ // route reads c.req.arrayBuffer); app-store/google-play are normal JSON. ──────
89
+ g('/api/integrations/stripe', stripeIntegration);
90
+ g('/api/integrations/app-store', appStoreIntegration);
91
+ g('/api/integrations/google-play', googlePlayIntegration);
92
+
93
+ // ── Batch 3f (remaining domains) ────────────────────────────────────────────
94
+ g('/api/auto-recharge-configs', autoRechargeConfigs);
95
+ g('/api/donations', donations, { baseCurrency: true });
96
+ g('/api/entitlements', entitlements);
97
+ g('/api/events', events);
98
+ g('/api/exchange-rate-providers', exchangeRateProviders);
99
+ g('/api/meters', meters);
100
+ g('/api/passports', passports);
101
+ g('/api/payment-links', paymentLinks, { baseCurrency: true });
102
+ g('/api/payouts', payouts);
103
+ g('/api/redirect', redirect);
104
+ g('/api/refunds', refunds);
105
+ g('/api/settings', settings);
106
+ g('/api/subscription-items', subscriptionItems);
107
+ g('/api/tax-rates', taxRates);
108
+ g('/api/tool', tool);
109
+ g('/api/vendors', vendor);
110
+ g('/api/webhook-attempts', webhookAttempts);
111
+ g('/api/webhook-endpoints', webhookEndpoints);
112
+ g('/api/archive', archive);
113
+ }
114
+
115
+ export default mountMigratedResources;