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,70 +1,79 @@
1
+ // Phase 3 (express→hono) — hono fork of routes/payment-currencies.ts. Sub-app with
2
+ // routes relative to /api/payment-currencies (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 { fromTokenToUnit } from '@ocap/util';
2
- import { Router } from 'express';
6
+ import { Hono } from 'hono';
3
7
  import { InferAttributes, Op, WhereOptions } from 'sequelize';
4
8
 
5
9
  import Joi from 'joi';
6
10
  import pick from 'lodash/pick';
7
- import { sessionMiddleware } from '@blocklet/sdk/lib/middlewares/session';
8
- import { fetchErc20Meta } from '../integrations/ethereum/token';
9
- import logger from '../libs/logger';
10
- import { authenticate } from '../libs/security';
11
- import { PaymentCurrency, TPaymentCurrency } from '../store/models/payment-currency';
12
- import { PaymentMethod } from '../store/models/payment-method';
13
- import { Price, Product } from '../store/models';
14
- import { EVM_CHAIN_TYPES } from '../libs/constants';
15
- import { ethWallet, getVaultAddress, wallet } from '../libs/auth';
16
- import { resolveAddressChainTypes } from '../libs/util';
17
- import { depositVaultQueue } from '../queues/payment';
18
- import { checkDepositVaultAmount } from '../libs/payment';
19
- import { getTokenSummaryByDid } from '../integrations/arcblock/stake';
20
- import { MetadataSchema } from '../libs/api';
21
- import { getRechargePaymentUrl } from '../libs/currency';
22
- import { checkCurrencySupportRecurring } from '../libs/product';
23
-
24
- const router = Router();
11
+ import { fetchErc20Meta } from '../../integrations/ethereum/token';
12
+ import logger from '../../libs/logger';
13
+ import { authenticate } from '../../middlewares/hono/security';
14
+ import { sessionMiddleware } from '../../middlewares/hono/session';
15
+ import { PaymentCurrency, TPaymentCurrency } from '../../store/models/payment-currency';
16
+ import { PaymentMethod } from '../../store/models/payment-method';
17
+ import { Price, Product } from '../../store/models';
18
+ import { EVM_CHAIN_TYPES } from '../../libs/constants';
19
+ import { ethWallet, getVaultAddress, wallet } from '../../libs/auth';
20
+ import { resolveAddressChainTypes } from '../../libs/util';
21
+ import { depositVaultQueue } from '../../queues/payment';
22
+ import { checkDepositVaultAmount } from '../../libs/payment';
23
+ import { getTokenSummaryByDid } from '../../integrations/arcblock/stake';
24
+ import { MetadataSchema } from '../../libs/api';
25
+ import { getRechargePaymentUrl } from '../../libs/currency';
26
+ import { checkCurrencySupportRecurring } from '../../libs/product';
27
+
28
+ const app = new Hono();
25
29
 
26
- const user = sessionMiddleware({ accessKey: true });
27
30
  const auth = authenticate<PaymentCurrency>({ component: true, roles: ['owner', 'admin'] });
28
31
  const authOwner = authenticate<PaymentCurrency>({ component: true, roles: ['owner'] });
32
+ // faithful fork of the express `sessionMiddleware({ accessKey: true })` — populates
33
+ // c.get('user') from a login token OR access key, and does NOT gate (next() either way).
34
+ const user = sessionMiddleware({ accessKey: true });
35
+
29
36
  const paymentCurrencyCreateSchema = Joi.object({
30
37
  name: Joi.string().max(32).required(),
31
38
  description: Joi.string().max(255).required(),
32
39
  }).unknown(true);
33
- router.post('/', auth, async (req, res) => {
34
- const raw: Partial<TPaymentCurrency> = req.body;
35
40
 
36
- const { error } = paymentCurrencyCreateSchema.validate(pick(req.body, ['name', 'description']));
41
+ app.post('/', auth, async (c) => {
42
+ const body = c.get('sanitizedBody') ?? {};
43
+ const raw: Partial<TPaymentCurrency> = body;
44
+
45
+ const { error } = paymentCurrencyCreateSchema.validate(pick(body, ['name', 'description']));
37
46
  if (error) {
38
- return res.status(400).json({ error: error.message });
47
+ return c.json({ error: error.message }, 400);
39
48
  }
40
49
 
41
50
  if (!raw.payment_method_id) {
42
- return res.status(400).json({ error: 'payment_method_id is required' });
51
+ return c.json({ error: 'payment_method_id is required' }, 400);
43
52
  }
44
53
  if (!raw.name) {
45
- return res.status(400).json({ error: 'payment currency name is required' });
54
+ return c.json({ error: 'payment currency name is required' }, 400);
46
55
  }
47
56
  if (!raw.description) {
48
- return res.status(400).json({ error: 'payment currency description is required' });
57
+ return c.json({ error: 'payment currency description is required' }, 400);
49
58
  }
50
59
  const method = await PaymentMethod.findByPk(raw.payment_method_id);
51
60
  if (!method) {
52
- return res.status(400).json({ error: 'payment method not found' });
61
+ return c.json({ error: 'payment method not found' }, 400);
53
62
  }
54
63
  raw.logo = raw.logo || method.logo;
55
64
  if (!raw.logo) {
56
- return res.status(400).json({ error: 'payment currency logo is required' });
65
+ return c.json({ error: 'payment currency logo is required' }, 400);
57
66
  }
58
67
  if (!raw.contract) {
59
- return res.status(400).json({ error: 'payment currency contract is required' });
68
+ return c.json({ error: 'payment currency contract is required' }, 400);
60
69
  }
61
70
  const exist = await PaymentCurrency.findOne({ where: { contract: raw.contract } });
62
71
  if (exist) {
63
- return res.status(400).json({ error: 'payment currency with same contract already exist' });
72
+ return c.json({ error: 'payment currency with same contract already exist' }, 400);
64
73
  }
65
74
 
66
75
  if (method.type === 'stripe') {
67
- return res.status(400).json({ error: 'Adding method for stripe not supported' });
76
+ return c.json({ error: 'Adding method for stripe not supported' }, 400);
68
77
  }
69
78
 
70
79
  if (EVM_CHAIN_TYPES.includes(method.type)) {
@@ -73,7 +82,7 @@ router.post('/', auth, async (req, res) => {
73
82
  const info = await fetchErc20Meta(client, raw.contract);
74
83
  logger.info(`${method.type} erc20 info fetched`, { raw, info });
75
84
  if (!info.symbol || !info.decimal) {
76
- return res.status(400).json({ error: `${method.type} token not found` });
85
+ return c.json({ error: `${method.type} token not found` }, 400);
77
86
  }
78
87
 
79
88
  const currency = await PaymentCurrency.create({
@@ -98,10 +107,10 @@ router.post('/', auth, async (req, res) => {
98
107
 
99
108
  metadata: {},
100
109
  });
101
- return res.json(currency);
110
+ return c.json(currency);
102
111
  } catch (err) {
103
112
  logger.error(err);
104
- return res.status(400).json({ error: `${method.type} currency contract verify failed` });
113
+ return c.json({ error: `${method.type} currency contract verify failed` }, 400);
105
114
  }
106
115
  }
107
116
 
@@ -111,7 +120,7 @@ router.post('/', auth, async (req, res) => {
111
120
  const { state } = await client.getTokenState({ address: raw.contract });
112
121
  logger.info('ocap token info fetched', { raw, state });
113
122
  if (!state) {
114
- return res.status(400).json({ error: 'ocap token not found' });
123
+ return c.json({ error: 'ocap token not found' }, 400);
115
124
  }
116
125
 
117
126
  const currency = await PaymentCurrency.create({
@@ -136,18 +145,45 @@ router.post('/', auth, async (req, res) => {
136
145
 
137
146
  metadata: {},
138
147
  });
139
- return res.json(currency);
148
+ return c.json(currency);
140
149
  } catch (err) {
141
150
  logger.error(err);
142
- return res.status(400).json({ error: `${method.type} currency contract verify failed` });
151
+ return c.json({ error: `${method.type} currency contract verify failed` }, 400);
143
152
  }
144
153
  }
145
154
 
146
- return res.status(400).json({ error: `add currency for payment method ${method.type} is not supported` });
155
+ return c.json({ error: `add currency for payment method ${method.type} is not supported` }, 400);
156
+ });
157
+
158
+ // check if currencies support recurring subscriptions
159
+ // registered before /:id routes so the static segment wins
160
+ app.post('/check-recurring-support', async (c) => {
161
+ try {
162
+ const body = c.get('sanitizedBody') ?? {};
163
+ const { currency_ids: currencyIds } = body as any;
164
+ if (!Array.isArray(currencyIds)) {
165
+ return c.json({ error: 'currency_ids must be an array' }, 400);
166
+ }
167
+ if (currencyIds.length === 0) {
168
+ return c.json({ error: 'currency_ids cannot be empty' }, 400);
169
+ }
170
+ const { notSupportCurrencies, validate } = await checkCurrencySupportRecurring(currencyIds, true);
171
+ return c.json({
172
+ supported: validate,
173
+ unsupported_currencies: notSupportCurrencies.map((cur) => ({
174
+ id: cur.id,
175
+ name: cur.name,
176
+ symbol: cur.symbol,
177
+ })),
178
+ });
179
+ } catch (err) {
180
+ logger.error('check recurring support error', err);
181
+ return c.json({ error: (err as any).message }, 400);
182
+ }
147
183
  });
148
184
 
149
- router.get('/', auth, async (req, res) => {
150
- const { query } = req;
185
+ app.get('/', auth, async (c) => {
186
+ const query = c.req.query();
151
187
  const where: WhereOptions<InferAttributes<PaymentCurrency>> = {};
152
188
 
153
189
  if (typeof query.active === 'string') {
@@ -168,13 +204,13 @@ router.get('/', auth, async (req, res) => {
168
204
  include: [],
169
205
  });
170
206
 
171
- res.json(list);
207
+ return c.json(list);
172
208
  });
173
209
 
174
- router.get('/vault-config', auth, async (req, res) => {
210
+ app.get('/vault-config', auth, async (c) => {
175
211
  const vaultAddress = await getVaultAddress();
176
212
  if (!vaultAddress) {
177
- return res.json({
213
+ return c.json({
178
214
  list: [],
179
215
  balances: {},
180
216
  });
@@ -196,16 +232,16 @@ router.get('/vault-config', auth, async (req, res) => {
196
232
  payment_method_id: {
197
233
  [Op.in]: paymentMethodIds,
198
234
  },
199
- livemode: !!req.livemode,
235
+ livemode: !!c.get('livemode'),
200
236
  },
201
237
  include: [{ model: PaymentMethod, as: 'payment_method' }],
202
238
  });
203
239
  try {
204
240
  const [arcblock, ethereum] = await Promise.all([
205
- getTokenSummaryByDid(wallet.address, !!req.livemode, 'arcblock'),
206
- getTokenSummaryByDid(ethWallet.address, !!req.livemode, EVM_CHAIN_TYPES),
241
+ getTokenSummaryByDid(wallet.address, !!c.get('livemode'), 'arcblock'),
242
+ getTokenSummaryByDid(ethWallet.address, !!c.get('livemode'), EVM_CHAIN_TYPES),
207
243
  ]);
208
- return res.json({
244
+ return c.json({
209
245
  list,
210
246
  balances: {
211
247
  ...arcblock,
@@ -214,54 +250,87 @@ router.get('/vault-config', auth, async (req, res) => {
214
250
  });
215
251
  } catch (err) {
216
252
  logger.error('get token summary failed', err);
217
- return res.status(400).json({ error: err.message, list, balances: {} });
253
+ return c.json({ error: (err as any).message, list, balances: {} }, 400);
218
254
  }
219
255
  } catch (err) {
220
256
  logger.error('get payment currency vault config failed', err);
221
- return res.status(400).json({ error: err.message, list: [], balances: {} });
257
+ return c.json({ error: (err as any).message, list: [], balances: {} }, 400);
222
258
  }
223
259
  });
224
260
 
225
- router.get('/:id/deposit-vault', auth, async (req, res) => {
226
- const { id } = req.params;
261
+ app.get('/:id/deposit-vault', auth, async (c) => {
262
+ const id = c.req.param('id');
227
263
  if (!id) {
228
- return res.status(400).json({ error: 'Missing payment currency id' });
264
+ return c.json({ error: 'Missing payment currency id' }, 400);
229
265
  }
230
266
  try {
231
267
  const result = await checkDepositVaultAmount(id);
232
- return res.json(result);
268
+ return c.json(result);
233
269
  } catch (error) {
234
270
  logger.error('Error checking deposit vault amount', { error, id });
235
- return res.status(400).json({ error: 'Failed to check deposit vault amount', message: error.message });
271
+ return c.json({ error: 'Failed to check deposit vault amount', message: (error as any).message }, 400);
236
272
  }
237
273
  });
238
274
 
239
- router.put('/:id/deposit-vault', auth, async (req, res) => {
240
- const paymentCurrency = await PaymentCurrency.findByPk(req.params.id);
275
+ app.put('/:id/deposit-vault', auth, async (c) => {
276
+ const paymentCurrency = await PaymentCurrency.findByPk(c.req.param('id'));
241
277
  if (!paymentCurrency) {
242
- return res.status(404).json({ error: 'Payment currency not found' });
278
+ return c.json({ error: 'Payment currency not found' }, 404);
243
279
  }
244
280
  const vaultAddress = await getVaultAddress();
245
281
  if (!vaultAddress) {
246
- return res.status(400).json({ error: 'Vault address not found' });
282
+ return c.json({ error: 'Vault address not found' }, 400);
247
283
  }
248
284
  depositVaultQueue.push({
249
285
  id: `deposit-vault-${paymentCurrency.id}`,
250
286
  job: { currencyId: paymentCurrency.id },
251
287
  });
252
288
  logger.info('Deposit vault job pushed', { currencyId: paymentCurrency.id });
253
- return res.json({ message: 'Deposit vault job pushed' });
289
+ return c.json({ message: 'Deposit vault job pushed' });
254
290
  });
255
291
 
256
- router.get('/:id', auth, async (req, res) => {
257
- const doc = await PaymentCurrency.findOne({
258
- where: { [Op.or]: [{ id: req.params.id }, { symbol: req.params.id }] },
259
- });
292
+ app.get('/:id/recharge-config', user, async (c) => {
293
+ try {
294
+ const id = c.req.param('id');
260
295
 
261
- if (doc) {
262
- res.json(doc);
263
- } else {
264
- res.status(404).json(null);
296
+ const currency = await PaymentCurrency.scope('withRechargeConfig').findByPk(id);
297
+
298
+ if (!currency) {
299
+ return c.json({ error: 'Credit currency not found' }, 404);
300
+ }
301
+
302
+ if (!currency.recharge_config) {
303
+ return c.json({
304
+ currency_id: id,
305
+ recharge_config: null,
306
+ message: 'No recharge config available for this currency',
307
+ });
308
+ }
309
+
310
+ const paymentUrl = await getRechargePaymentUrl(currency);
311
+
312
+ let basePrice: (Price & { product: Product }) | null = null;
313
+ if (currency.recharge_config.base_price_id) {
314
+ basePrice = (await Price.findByPk(currency.recharge_config.base_price_id, {
315
+ include: [{ model: Product, as: 'product' }],
316
+ })) as Price & { product: Product };
317
+ }
318
+
319
+ return c.json({
320
+ currency_id: id,
321
+ currency_info: pick(currency, ['id', 'name', 'symbol', 'decimal', 'type']),
322
+ recharge_config: {
323
+ ...currency.recharge_config,
324
+ basePrice,
325
+ payment_url: paymentUrl,
326
+ },
327
+ });
328
+ } catch (error: any) {
329
+ logger.error('Failed to get currency recharge config', {
330
+ currencyId: c.req.param('id'),
331
+ error: error.message,
332
+ });
333
+ return c.json({ error: 'Internal server error' }, 500);
265
334
  }
266
335
  });
267
336
 
@@ -271,23 +340,25 @@ const UpdateVaultConfigSchema = Joi.object({
271
340
  withdraw_threshold: Joi.number().min(0).required(),
272
341
  buffer_threshold: Joi.number().min(0).required(),
273
342
  });
274
- router.put('/:id/vault-config', authOwner, async (req, res) => {
343
+
344
+ app.put('/:id/vault-config', authOwner, async (c) => {
275
345
  try {
276
- const { id } = req.params;
346
+ const id = c.req.param('id');
347
+ const body = c.get('sanitizedBody') ?? {};
277
348
 
278
- const { error, value: vaultConfig } = UpdateVaultConfigSchema.validate(req.body);
349
+ const { error, value: vaultConfig } = UpdateVaultConfigSchema.validate(body);
279
350
  if (error) {
280
- return res.status(400).json({ error: error.message });
351
+ return c.json({ error: error.message }, 400);
281
352
  }
282
353
 
283
354
  const paymentCurrency = await PaymentCurrency.findByPk(id);
284
355
  if (!paymentCurrency) {
285
- return res.status(404).json({ error: 'payment currency not found' });
356
+ return c.json({ error: 'payment currency not found' }, 404);
286
357
  }
287
358
 
288
359
  const vaultAddress = await getVaultAddress();
289
360
  if (!vaultAddress) {
290
- return res.status(400).json({ error: 'Vault address not found' });
361
+ return c.json({ error: 'Vault address not found' }, 400);
291
362
  }
292
363
 
293
364
  const updateData: Partial<TPaymentCurrency> = {
@@ -301,10 +372,10 @@ router.put('/:id/vault-config', authOwner, async (req, res) => {
301
372
 
302
373
  await paymentCurrency.update(updateData);
303
374
 
304
- return res.json(paymentCurrency.toJSON());
375
+ return c.json(paymentCurrency.toJSON());
305
376
  } catch (err) {
306
377
  logger.error('update payment currency vault config failed', err);
307
- return res.status(400).json({ error: err.message });
378
+ return c.json({ error: (err as any).message }, 400);
308
379
  }
309
380
  });
310
381
 
@@ -315,26 +386,28 @@ const updateCurrencySchema = Joi.object({
315
386
  metadata: MetadataSchema,
316
387
  symbol: Joi.string().empty('').optional(),
317
388
  }).unknown(true);
318
- router.put('/:id', auth, async (req, res) => {
319
- const { id } = req.params;
320
- const raw: Partial<TPaymentCurrency> = req.body;
389
+
390
+ app.put('/:id', auth, async (c) => {
391
+ const id = c.req.param('id');
392
+ const body = c.get('sanitizedBody') ?? {};
393
+ const raw: Partial<TPaymentCurrency> = body;
321
394
 
322
395
  const { error } = updateCurrencySchema.validate(raw);
323
396
  if (error) {
324
- return res.status(400).json({ error: error.message });
397
+ return c.json({ error: error.message }, 400);
325
398
  }
326
399
 
327
400
  const currency = await PaymentCurrency.findByPk(id);
328
401
  if (!currency) {
329
- return res.status(404).json({ error: 'Payment currency not found' });
402
+ return c.json({ error: 'Payment currency not found' }, 404);
330
403
  }
331
404
  if (raw.contract && raw.contract !== currency.contract) {
332
- return res.status(400).json({ error: 'contract cannot be updated' });
405
+ return c.json({ error: 'contract cannot be updated' }, 400);
333
406
  }
334
407
 
335
408
  const method = await PaymentMethod.findByPk(currency.payment_method_id);
336
409
  if (!method) {
337
- return res.status(400).json({ error: 'Payment method not found' });
410
+ return c.json({ error: 'Payment method not found' }, 400);
338
411
  }
339
412
 
340
413
  const updates: Partial<TPaymentCurrency> = {
@@ -349,33 +422,34 @@ router.put('/:id', auth, async (req, res) => {
349
422
  }
350
423
 
351
424
  const updatedCurrency = await currency.update(updates);
352
- return res.json(updatedCurrency);
425
+ return c.json(updatedCurrency);
353
426
  });
354
427
 
355
428
  const tokenConfigSchema = Joi.object({
356
429
  token_factory_address: Joi.string().required(),
357
430
  });
358
431
 
359
- router.put('/:id/token-config', auth, async (req, res) => {
432
+ app.put('/:id/token-config', auth, async (c) => {
360
433
  try {
361
- const { id } = req.params;
434
+ const id = c.req.param('id');
435
+ const body = c.get('sanitizedBody') ?? {};
362
436
 
363
- const { error, value } = tokenConfigSchema.validate(req.body);
437
+ const { error, value } = tokenConfigSchema.validate(body);
364
438
  if (error) {
365
- return res.status(400).json({ error: error.message });
439
+ return c.json({ error: error.message }, 400);
366
440
  }
367
441
 
368
442
  const currency = await PaymentCurrency.findByPk(id);
369
443
  if (!currency) {
370
- return res.status(404).json({ error: 'Payment currency not found' });
444
+ return c.json({ error: 'Payment currency not found' }, 404);
371
445
  }
372
446
 
373
447
  if (currency.type !== 'credit') {
374
- return res.status(400).json({ error: 'Only credit currencies can have token_config' });
448
+ return c.json({ error: 'Only credit currencies can have token_config' }, 400);
375
449
  }
376
450
 
377
451
  if (currency.token_config) {
378
- return res.status(400).json({ error: 'Token config already exists. Cannot be updated once set.' });
452
+ return c.json({ error: 'Token config already exists. Cannot be updated once set.' }, 400);
379
453
  }
380
454
 
381
455
  const paymentMethod = await PaymentMethod.findOne({
@@ -386,7 +460,7 @@ router.put('/:id/token-config', auth, async (req, res) => {
386
460
  });
387
461
 
388
462
  if (!paymentMethod) {
389
- return res.status(400).json({ error: 'ArcBlock payment method not found' });
463
+ return c.json({ error: 'ArcBlock payment method not found' }, 400);
390
464
  }
391
465
 
392
466
  const client = paymentMethod.getOcapClient();
@@ -395,7 +469,7 @@ router.put('/:id/token-config', auth, async (req, res) => {
395
469
  });
396
470
 
397
471
  if (!tokenFactoryState) {
398
- return res.status(400).json({ error: 'Token factory not found on chain' });
472
+ return c.json({ error: 'Token factory not found on chain' }, 400);
399
473
  }
400
474
 
401
475
  const tokenConfig = {
@@ -416,28 +490,31 @@ router.put('/:id/token-config', auth, async (req, res) => {
416
490
  tokenConfig,
417
491
  });
418
492
 
419
- return res.json(currency.toJSON());
493
+ return c.json(currency.toJSON());
420
494
  } catch (err) {
421
- logger.error('update payment currency token_config failed', { error: err?.message, id: req.params.id });
422
- return res.status(400).json({ error: err?.message });
495
+ logger.error('update payment currency token_config failed', {
496
+ error: (err as any)?.message,
497
+ id: c.req.param('id'),
498
+ });
499
+ return c.json({ error: (err as any)?.message }, 400);
423
500
  }
424
501
  });
425
502
 
426
- router.delete('/:id/token-config', auth, async (req, res) => {
503
+ app.delete('/:id/token-config', auth, async (c) => {
427
504
  try {
428
- const { id } = req.params;
505
+ const id = c.req.param('id');
429
506
 
430
507
  const currency = await PaymentCurrency.findByPk(id);
431
508
  if (!currency) {
432
- return res.status(404).json({ error: 'Payment currency not found' });
509
+ return c.json({ error: 'Payment currency not found' }, 404);
433
510
  }
434
511
 
435
512
  if (currency.type !== 'credit') {
436
- return res.status(400).json({ error: 'Only credit currencies can have token_config' });
513
+ return c.json({ error: 'Only credit currencies can have token_config' }, 400);
437
514
  }
438
515
 
439
516
  if (!currency.token_config) {
440
- return res.status(400).json({ error: 'Token config does not exist' });
517
+ return c.json({ error: 'Token config does not exist' }, 400);
441
518
  }
442
519
 
443
520
  await currency.update({
@@ -448,80 +525,25 @@ router.delete('/:id/token-config', auth, async (req, res) => {
448
525
  currencyId: id,
449
526
  });
450
527
 
451
- return res.json(currency.toJSON());
452
- } catch (err) {
453
- logger.error('delete payment currency token_config failed', { error: err?.message, id: req.params.id });
454
- return res.status(400).json({ error: err?.message });
455
- }
456
- });
457
-
458
- router.delete('/:id', auth, async (req, res) => {
459
- const { id } = req.params;
460
-
461
- const currency = await PaymentCurrency.findByPk(id);
462
- if (!currency) {
463
- return res.status(404).json({ error: 'Payment currency not found' });
464
- }
465
- const isLocked = await currency.isLocked();
466
- if (isLocked) {
467
- return res.status(400).json({ error: 'Can not delete locked payment currency' });
468
- }
469
- const isUsed = await currency.isUsed();
470
- if (isUsed) {
471
- return res.status(400).json({ error: 'Can not delete payment currency used by other resources' });
472
- }
473
- try {
474
- await currency.destroy();
475
- return res.status(200).end();
528
+ return c.json(currency.toJSON());
476
529
  } catch (err) {
477
- logger.error('delete payment currency error', err);
478
- return res.status(400).json({ error: 'Delete payment currency failed' });
530
+ logger.error('delete payment currency token_config failed', {
531
+ error: (err as any)?.message,
532
+ id: c.req.param('id'),
533
+ });
534
+ return c.json({ error: (err as any)?.message }, 400);
479
535
  }
480
536
  });
481
537
 
482
- router.get('/:id/recharge-config', user, async (req, res) => {
483
- try {
484
- const { id } = req.params;
485
-
486
- const currency = await PaymentCurrency.scope('withRechargeConfig').findByPk(id);
487
-
488
- if (!currency) {
489
- return res.status(404).json({ error: 'Credit currency not found' });
490
- }
491
-
492
- if (!currency.recharge_config) {
493
- return res.json({
494
- currency_id: id,
495
- recharge_config: null,
496
- message: 'No recharge config available for this currency',
497
- });
498
- }
499
-
500
- const paymentUrl = await getRechargePaymentUrl(currency);
501
-
502
- let basePrice: (Price & { product: Product }) | null = null;
503
- if (currency.recharge_config.base_price_id) {
504
- basePrice = (await Price.findByPk(currency.recharge_config.base_price_id, {
505
- include: [{ model: Product, as: 'product' }],
506
- })) as Price & { product: Product };
507
- }
538
+ app.get('/:id', auth, async (c) => {
539
+ const doc = await PaymentCurrency.findOne({
540
+ where: { [Op.or]: [{ id: c.req.param('id') }, { symbol: c.req.param('id') }] },
541
+ });
508
542
 
509
- return res.json({
510
- currency_id: id,
511
- currency_info: pick(currency, ['id', 'name', 'symbol', 'decimal', 'type']),
512
- recharge_config: {
513
- ...currency.recharge_config,
514
- basePrice,
515
- payment_url: paymentUrl,
516
- },
517
- });
518
- } catch (error: any) {
519
- logger.error('Failed to get currency recharge config', {
520
- currencyId: req.params.id,
521
- error: error.message,
522
- });
523
- return res.status(500).json({ error: 'Internal server error' });
543
+ if (doc) {
544
+ return c.json(doc);
524
545
  }
546
+ return c.json(null, 404);
525
547
  });
526
548
 
527
549
  const rechargeConfigSchema = Joi.object({
@@ -534,19 +556,20 @@ const rechargeConfigSchema = Joi.object({
534
556
  }).optional(),
535
557
  }).unknown(true);
536
558
 
537
- router.put('/:id/recharge-config', auth, async (req, res) => {
538
- const { id } = req.params;
559
+ app.put('/:id/recharge-config', auth, async (c) => {
560
+ const id = c.req.param('id');
561
+ const body = c.get('sanitizedBody') ?? {};
539
562
  const { error, value: rechargeConfig } = rechargeConfigSchema.validate(
540
- pick(req.body, ['base_price_id', 'payment_link_id', 'checkout_url', 'settings'])
563
+ pick(body, ['base_price_id', 'payment_link_id', 'checkout_url', 'settings'])
541
564
  );
542
565
  if (error) {
543
- return res.status(400).json({ error: error.message });
566
+ return c.json({ error: error.message }, 400);
544
567
  }
545
568
 
546
569
  const currency = await PaymentCurrency.findByPk(id);
547
570
 
548
571
  if (!currency) {
549
- return res.status(404).json({ error: 'Credit currency not found' });
572
+ return c.json({ error: 'Credit currency not found' }, 404);
550
573
  }
551
574
 
552
575
  if (rechargeConfig.base_price_id) {
@@ -559,7 +582,7 @@ router.put('/:id/recharge-config', auth, async (req, res) => {
559
582
  })) as Price & { product: Product };
560
583
 
561
584
  if (!basePrice) {
562
- return res.status(404).json({ error: 'Base price not found or inactive' });
585
+ return c.json({ error: 'Base price not found or inactive' }, 404);
563
586
  }
564
587
  }
565
588
 
@@ -571,36 +594,35 @@ router.put('/:id/recharge-config', auth, async (req, res) => {
571
594
  tiersCount: rechargeConfig.price_tiers?.length || 0,
572
595
  });
573
596
 
574
- return res.json({
597
+ return c.json({
575
598
  currency_id: id,
576
599
  recharge_config: rechargeConfig,
577
600
  message: 'Recharge config updated successfully',
578
601
  });
579
602
  });
580
603
 
581
- // check if currencies support recurring subscriptions
582
- router.post('/check-recurring-support', async (req, res) => {
604
+ app.delete('/:id', auth, async (c) => {
605
+ const id = c.req.param('id');
606
+
607
+ const currency = await PaymentCurrency.findByPk(id);
608
+ if (!currency) {
609
+ return c.json({ error: 'Payment currency not found' }, 404);
610
+ }
611
+ const isLocked = await currency.isLocked();
612
+ if (isLocked) {
613
+ return c.json({ error: 'Can not delete locked payment currency' }, 400);
614
+ }
615
+ const isUsed = await currency.isUsed();
616
+ if (isUsed) {
617
+ return c.json({ error: 'Can not delete payment currency used by other resources' }, 400);
618
+ }
583
619
  try {
584
- const { currency_ids: currencyIds } = req.body;
585
- if (!Array.isArray(currencyIds)) {
586
- return res.status(400).json({ error: 'currency_ids must be an array' });
587
- }
588
- if (currencyIds.length === 0) {
589
- return res.status(400).json({ error: 'currency_ids cannot be empty' });
590
- }
591
- const { notSupportCurrencies, validate } = await checkCurrencySupportRecurring(currencyIds, true);
592
- return res.json({
593
- supported: validate,
594
- unsupported_currencies: notSupportCurrencies.map((c) => ({
595
- id: c.id,
596
- name: c.name,
597
- symbol: c.symbol,
598
- })),
599
- });
620
+ await currency.destroy();
621
+ return c.body(null, 200);
600
622
  } catch (err) {
601
- logger.error('check recurring support error', err);
602
- return res.status(400).json({ error: err.message });
623
+ logger.error('delete payment currency error', err);
624
+ return c.json({ error: 'Delete payment currency failed' }, 400);
603
625
  }
604
626
  });
605
627
 
606
- export default router;
628
+ export default app;