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,12 +1,17 @@
1
- import { Router } from 'express';
1
+ // Phase 3 (express→hono) hono fork of routes/credit-grants.ts. Sub-app with
2
+ // routes relative to /api/credit-grants (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
+ // req.(c.get('customer_id') ?? query.customer_id) → (c.get('customer_id') ?? c.req.query('customer_id')) [SECURITY]
6
+ import { Hono } from 'hono';
2
7
  import Joi from 'joi';
3
8
  import { BN, fromTokenToUnit } from '@ocap/util';
4
9
 
5
10
  import { literal, OrderItem, fn, col, Op } from 'sequelize';
6
11
  import pick from 'lodash/pick';
7
- import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
8
- import logger from '../libs/logger';
9
- import { authenticate } from '../libs/security';
12
+ import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../../libs/api';
13
+ import logger from '../../libs/logger';
14
+ import { authenticate } from '../../middlewares/hono/security';
10
15
  import {
11
16
  AutoRechargeConfig,
12
17
  ChainType,
@@ -19,22 +24,22 @@ import {
19
24
  Price,
20
25
  Product,
21
26
  Subscription,
22
- } from '../store/models';
23
- import { createCreditGrant, getCreditGrantStats } from '../libs/credit-grant';
24
- import { expireGrant } from '../queues/credit-grant';
25
- import { getMeterPriceIdsFromSubscription } from '../libs/subscription';
26
- import { blocklet } from '../libs/auth';
27
- import { formatMetadata } from '../libs/util';
28
- import { getPriceUintAmountByCurrency } from '../libs/price';
29
- import { checkTokenBalance } from '../libs/payment';
30
- import { measurePhase } from '../libs/timing';
31
- import { getExchangeRateService } from '../libs/exchange-rate/service';
32
- import { getExchangeRateSymbol, hasTokenAddress } from '../libs/exchange-rate/token-address-mapping';
33
- import { isRateBelowMinAcceptableRate } from '../libs/slippage';
34
- import { trimDecimals } from '../libs/math-utils';
35
- import { systemMaxPendingAmount } from '../libs/env';
36
-
37
- const router = Router();
27
+ } from '../../store/models';
28
+ import { createCreditGrant, getCreditGrantStats } from '../../libs/credit-grant';
29
+ import { expireGrant } from '../../queues/credit-grant';
30
+ import { getMeterPriceIdsFromSubscription } from '../../libs/subscription';
31
+ import { blocklet } from '../../libs/auth';
32
+ import { formatMetadata } from '../../libs/util';
33
+ import { getPriceUintAmountByCurrency } from '../../libs/price';
34
+ import { checkTokenBalance } from '../../libs/payment';
35
+ import { measurePhase } from '../../libs/timing';
36
+ import { getExchangeRateService } from '../../libs/exchange-rate/service';
37
+ import { getExchangeRateSymbol, hasTokenAddress } from '../../libs/exchange-rate/token-address-mapping';
38
+ import { isRateBelowMinAcceptableRate } from '../../libs/slippage';
39
+ import { trimDecimals } from '../../libs/math-utils';
40
+ import { systemMaxPendingAmount } from '../../libs/env';
41
+
42
+ const app = new Hono();
38
43
  const auth = authenticate<CreditGrant>({ component: true, roles: ['owner', 'admin'] });
39
44
  const authMine = authenticate<CreditGrant>({ component: true, roles: ['owner', 'admin'], mine: true });
40
45
  const authPortal = authenticate<CreditGrant>({
@@ -92,14 +97,14 @@ async function expandScopePrices(creditGrant: CreditGrant) {
92
97
  return [];
93
98
  }
94
99
 
95
- router.get('/', authMine, async (req, res) => {
100
+ app.get('/', authMine, async (c) => {
96
101
  try {
97
- const { page, pageSize, ...query } = await listSchema.validateAsync(req.query, { stripUnknown: true });
102
+ const { page, pageSize, ...query } = await listSchema.validateAsync(c.req.query(), { stripUnknown: true });
98
103
  const where = getWhereFromKvQuery(query.q);
99
- if (query.customer_id) {
100
- const customer = await Customer.findByPkOrDid(query.customer_id);
104
+ if (c.get('customer_id') ?? query.customer_id) {
105
+ const customer = await Customer.findByPkOrDid(c.get('customer_id') ?? query.customer_id);
101
106
  if (!customer) {
102
- throw new Error(`Customer ${query.customer_id} not found`);
107
+ throw new Error(`Customer ${c.get('customer_id') ?? query.customer_id} not found`);
103
108
  }
104
109
  where.customer_id = customer.id;
105
110
  }
@@ -128,7 +133,7 @@ router.get('/', authMine, async (req, res) => {
128
133
  where.livemode = query.livemode;
129
134
  }
130
135
 
131
- const order: OrderItem[] = getOrder(req.query);
136
+ const order: OrderItem[] = getOrder(c.req.query());
132
137
  // 默认granted 、pending、depleted 排序
133
138
  order.unshift([literal("CASE status WHEN 'granted' THEN 1 WHEN 'pending' THEN 2 ELSE 3 END"), 'ASC']);
134
139
 
@@ -143,35 +148,36 @@ router.get('/', authMine, async (req, res) => {
143
148
  ],
144
149
  });
145
150
 
146
- res.json({ count, list, paging: { page, pageSize } });
151
+ return c.json({ count, list, paging: { page, pageSize } });
147
152
  } catch (err) {
148
153
  logger.error('Error listing credit grants', err);
149
- res.status(400).json({ error: err.message });
154
+ return c.json({ error: err.message }, 400);
150
155
  }
151
156
  });
152
157
 
153
- router.get('/summary', authMine, async (req, res) => {
158
+ // Static path registered before /:id so hono matches it first.
159
+ app.get('/summary', authMine, async (c) => {
154
160
  try {
155
- const customerId = req.query.customer_id;
161
+ const customerId = (c.get('customer_id') ?? c.req.query('customer_id')) as string | undefined;
156
162
  if (!customerId) {
157
- return res.status(400).json({ error: 'customer_id is required' });
163
+ return c.json({ error: 'customer_id is required' }, 400);
158
164
  }
159
165
 
160
166
  const customer = await Customer.findByPkOrDid(customerId as string);
161
167
  if (!customer) {
162
- return res.status(404).json({ error: `Customer ${customerId} not found` });
168
+ return c.json({ error: `Customer ${customerId} not found` }, 404);
163
169
  }
164
170
 
165
- const { subscription_id: subscriptionId } = req.query;
171
+ const subscriptionId = c.req.query('subscription_id');
166
172
  if (subscriptionId && typeof subscriptionId !== 'string') {
167
- return res.status(400).json({ error: 'subscription_id must be a string' });
173
+ return c.json({ error: 'subscription_id must be a string' }, 400);
168
174
  }
169
175
 
170
176
  let priceIds: string[] = [];
171
177
  if (subscriptionId) {
172
178
  const subscription = await Subscription.findByPk(subscriptionId);
173
179
  if (!subscription) {
174
- return res.status(404).json({ error: 'Subscription not found' });
180
+ return c.json({ error: 'Subscription not found' }, 404);
175
181
  }
176
182
  priceIds = await getMeterPriceIdsFromSubscription(subscription);
177
183
  }
@@ -181,10 +187,10 @@ router.get('/summary', authMine, async (req, res) => {
181
187
  priceIds,
182
188
  });
183
189
 
184
- return res.json(result);
190
+ return c.json(result);
185
191
  } catch (err: any) {
186
- logger.error('get credit balance failed', { error: err.message, customerId: req.params.customer_id });
187
- return res.status(400).json({ error: err.message });
192
+ logger.error('get credit balance failed', { error: err.message, customerId: c.req.param('customer_id') });
193
+ return c.json({ error: err.message }, 400);
188
194
  }
189
195
  });
190
196
 
@@ -196,22 +202,23 @@ const holdersSchema = Joi.object({
196
202
  });
197
203
 
198
204
  // Get all holders (customers with balance) for a specific credit currency
199
- router.get('/holders', auth, async (req, res) => {
205
+ // Static path registered before /:id so hono matches it first.
206
+ app.get('/holders', auth, async (c) => {
200
207
  try {
201
- const { error, value } = holdersSchema.validate(req.query, { stripUnknown: true });
208
+ const { error, value } = holdersSchema.validate(c.req.query(), { stripUnknown: true });
202
209
  if (error) {
203
- return res.status(400).json({ error: error.message });
210
+ return c.json({ error: error.message }, 400);
204
211
  }
205
212
 
206
213
  const { currency_id: currencyId, page, pageSize, livemode } = value;
207
214
 
208
215
  const currency = await PaymentCurrency.findByPk(currencyId);
209
216
  if (!currency) {
210
- return res.status(404).json({ error: `PaymentCurrency ${currencyId} not found` });
217
+ return c.json({ error: `PaymentCurrency ${currencyId} not found` }, 404);
211
218
  }
212
219
 
213
220
  if (currency.type !== 'credit') {
214
- return res.status(400).json({ error: 'Currency must be of type credit' });
221
+ return c.json({ error: 'Currency must be of type credit' }, 400);
215
222
  }
216
223
 
217
224
  // Build where clause for credit grants
@@ -250,7 +257,7 @@ router.get('/holders', auth, async (req, res) => {
250
257
  grantCount: parseInt(item.grantCount || '0', 10),
251
258
  }));
252
259
 
253
- return res.json({
260
+ return c.json({
254
261
  holders,
255
262
  currency: {
256
263
  id: currency.id,
@@ -267,7 +274,7 @@ router.get('/holders', auth, async (req, res) => {
267
274
  });
268
275
  } catch (err: any) {
269
276
  logger.error('Error getting credit holders', { error: err.message });
270
- return res.status(400).json({ error: err.message });
277
+ return c.json({ error: err.message }, 400);
271
278
  }
272
279
  });
273
280
 
@@ -281,11 +288,12 @@ const checkAutoRechargeSchema = Joi.object({
281
288
  .optional(),
282
289
  });
283
290
 
284
- router.get('/verify-availability', authMine, async (req, res) => {
291
+ // Static path registered before /:id so hono matches it first.
292
+ app.get('/verify-availability', authMine, async (c) => {
285
293
  try {
286
- const { error, value } = checkAutoRechargeSchema.validate(req.query, { stripUnknown: true });
294
+ const { error, value } = checkAutoRechargeSchema.validate(c.req.query(), { stripUnknown: true });
287
295
  if (error) {
288
- return res.status(400).json({ error: error.message });
296
+ return c.json({ error: error.message }, 400);
289
297
  }
290
298
 
291
299
  const { customer_id: customerId, currency_id: currencyId } = value;
@@ -297,10 +305,10 @@ router.get('/verify-availability', authMine, async (req, res) => {
297
305
  PaymentCurrency.findByPk(currencyId),
298
306
  ]);
299
307
  if (!customer) {
300
- return res.status(404).json({ error: `Customer ${customerId} not found` });
308
+ return c.json({ error: `Customer ${customerId} not found` }, 404);
301
309
  }
302
310
  if (!currency) {
303
- return res.status(404).json({ error: `PaymentCurrency ${currencyId} not found` });
311
+ return c.json({ error: `PaymentCurrency ${currencyId} not found` }, 404);
304
312
  }
305
313
 
306
314
  // AutoRechargeConfig needs customer.id which we now have
@@ -324,7 +332,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
324
332
  | null;
325
333
 
326
334
  if (!config) {
327
- return res.json({
335
+ return c.json({
328
336
  can_continue: false,
329
337
  has_auto_recharge: false,
330
338
  reason: 'auto_recharge_config_not_found',
@@ -333,21 +341,21 @@ router.get('/verify-availability', authMine, async (req, res) => {
333
341
 
334
342
  // 1. Check config completeness
335
343
  if (!config.rechargeCurrency) {
336
- return res.json({
344
+ return c.json({
337
345
  can_continue: false,
338
346
  reason: 'recharge_currency_not_found',
339
347
  });
340
348
  }
341
349
 
342
350
  if (!config.price) {
343
- return res.json({
351
+ return c.json({
344
352
  can_continue: false,
345
353
  reason: 'price_not_found',
346
354
  });
347
355
  }
348
356
 
349
357
  if (!config.paymentMethod) {
350
- return res.json({
358
+ return c.json({
351
359
  can_continue: false,
352
360
  reason: 'payment_method_not_found',
353
361
  });
@@ -355,7 +363,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
355
363
 
356
364
  // 2. Check if stripe (balance check not supported)
357
365
  if (config.paymentMethod.type === 'stripe') {
358
- return res.json({
366
+ return c.json({
359
367
  can_continue: false,
360
368
  reason: 'balance_check_not_supported',
361
369
  });
@@ -364,7 +372,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
364
372
  // 3. Check price amount
365
373
  const priceAmount = await getPriceUintAmountByCurrency(config.price, config.rechargeCurrency.id);
366
374
  if (!priceAmount) {
367
- return res.json({
375
+ return c.json({
368
376
  can_continue: false,
369
377
  reason: 'invalid_price_amount',
370
378
  });
@@ -378,7 +386,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
378
386
  const [pendingSummary] = await MeterEvent.getPendingAmounts({
379
387
  customerId: customer.id,
380
388
  currencyId,
381
- livemode: req.livemode,
389
+ livemode: c.get('livemode'),
382
390
  });
383
391
  pendingAmount = pendingSummary?.[currencyId] || '0';
384
392
  }
@@ -386,14 +394,14 @@ router.get('/verify-availability', authMine, async (req, res) => {
386
394
  const pendingAmountBN = new BN(pendingAmount);
387
395
 
388
396
  // 5. Check system-level maximum pending amount limit (highest priority, cannot be bypassed)
389
- // systemMaxPendingAmount is configured via PAYMENT_KIT_MAX_PENDING_AMOUNT (in token format)
390
- if (systemMaxPendingAmount > 0 && pendingAmountBN.gt(new BN(0))) {
397
+ // systemMaxPendingAmount() is configured via PAYMENT_KIT_MAX_PENDING_AMOUNT (in token format)
398
+ if (systemMaxPendingAmount() > 0 && pendingAmountBN.gt(new BN(0))) {
391
399
  const systemMaxPendingAmountBN = fromTokenToUnit(
392
- trimDecimals(String(systemMaxPendingAmount), currency.decimal),
400
+ trimDecimals(String(systemMaxPendingAmount()), currency.decimal),
393
401
  currency.decimal
394
402
  );
395
403
  if (pendingAmountBN.gt(systemMaxPendingAmountBN)) {
396
- return res.json({
404
+ return c.json({
397
405
  can_continue: false,
398
406
  reason: 'system_pending_limit_exceeded',
399
407
  pending_amount: pendingAmount,
@@ -411,7 +419,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
411
419
  currency.decimal
412
420
  );
413
421
  if (pendingAmountBN.gt(maxPendingAmountBN)) {
414
- return res.json({
422
+ return c.json({
415
423
  can_continue: false,
416
424
  reason: 'pending_amount_exceeds_limit',
417
425
  pending_amount: pendingAmount,
@@ -428,7 +436,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
428
436
  ]?.payer || customer.did;
429
437
 
430
438
  if (!payer) {
431
- return res.json({
439
+ return c.json({
432
440
  can_continue: false,
433
441
  reason: 'payer_not_found',
434
442
  });
@@ -440,7 +448,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
440
448
  // Get credit amount per recharge from price metadata
441
449
  const creditConfig = config.price.metadata?.credit_config;
442
450
  if (!creditConfig || !creditConfig.credit_amount) {
443
- return res.json({
451
+ return c.json({
444
452
  can_continue: false,
445
453
  reason: 'credit_config_not_found',
446
454
  });
@@ -461,7 +469,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
461
469
  // Check if required recharge times exceeds limit
462
470
  const maxRechargeTimes = value.max_recharge_times;
463
471
  if (requiredRechargeTimesBN.gt(new BN(maxRechargeTimes))) {
464
- return res.json({
472
+ return c.json({
465
473
  can_continue: false,
466
474
  reason: 'too_many_recharges_required',
467
475
  pending_amount: pendingAmount,
@@ -489,7 +497,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
489
497
  const requiredRechargeTimes = requiredRechargeTimesBN.toNumber();
490
498
  const remainingAttempts = maxAttempts - Number(attemptCount);
491
499
  if (requiredRechargeTimes > remainingAttempts) {
492
- return res.json({
500
+ return c.json({
493
501
  can_continue: false,
494
502
  reason: 'daily_limit_reached',
495
503
  detail: 'attempt_limit_exceeded',
@@ -502,7 +510,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
502
510
  if (maxAmount.gt(new BN(0))) {
503
511
  const remainingAmount = maxAmount.sub(new BN(totalAmountStats || '0'));
504
512
  if (requiredPaymentAmount.gt(remainingAmount)) {
505
- return res.json({
513
+ return c.json({
506
514
  can_continue: false,
507
515
  reason: 'daily_limit_reached',
508
516
  detail: 'amount_limit_exceeded',
@@ -526,7 +534,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
526
534
  );
527
535
 
528
536
  if (!balanceResult.sufficient) {
529
- return res.json({
537
+ return c.json({
530
538
  can_continue: false,
531
539
  reason: 'insufficient_balance',
532
540
  payment_account_balance: balanceResult.token?.balance || '0',
@@ -548,7 +556,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
548
556
  );
549
557
 
550
558
  if (!balanceResult.sufficient) {
551
- return res.json({
559
+ return c.json({
552
560
  can_continue: false,
553
561
  reason: 'insufficient_balance',
554
562
  payment_account_balance: balanceResult.token?.balance || '0',
@@ -568,7 +576,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
568
576
  // 7a. Check if exchange rate is available
569
577
  const methodType = config.paymentMethod.type as ChainType;
570
578
  if (!hasTokenAddress(config.rechargeCurrency.symbol, methodType)) {
571
- return res.json({
579
+ return c.json({
572
580
  can_continue: false,
573
581
  reason: 'exchange_rate_not_supported',
574
582
  is_dynamic_pricing: true,
@@ -590,7 +598,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
590
598
  error: err.message,
591
599
  symbol: config.rechargeCurrency.symbol,
592
600
  });
593
- return res.json({
601
+ return c.json({
594
602
  can_continue: false,
595
603
  reason: 'exchange_rate_fetch_failed',
596
604
  is_dynamic_pricing: true,
@@ -610,7 +618,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
610
618
  };
611
619
 
612
620
  if (rateBelowMin) {
613
- return res.json({
621
+ return c.json({
614
622
  can_continue: false,
615
623
  reason: 'slippage_exceeded',
616
624
  is_dynamic_pricing: true,
@@ -636,7 +644,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
636
644
  canCoverPending = creditAmount.gte(pendingAmountBN);
637
645
 
638
646
  if (!canCoverPending) {
639
- return res.json({
647
+ return c.json({
640
648
  can_continue: false,
641
649
  reason: 'cannot_cover_pending_single_recharge',
642
650
  is_dynamic_pricing: true,
@@ -649,7 +657,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
649
657
  }
650
658
  }
651
659
 
652
- return res.json({
660
+ return c.json({
653
661
  can_continue: true,
654
662
  payment_account_sufficient: true,
655
663
  payment_account_balance: balanceResult?.token?.balance || '0',
@@ -662,10 +670,10 @@ router.get('/verify-availability', authMine, async (req, res) => {
662
670
  } catch (err: any) {
663
671
  logger.error('check auto recharge failed', {
664
672
  error: err.message,
665
- customerId: req.query.customer_id,
666
- currencyId: req.query.currency_id,
673
+ customerId: c.get('customer_id') ?? c.req.query('customer_id'),
674
+ currencyId: c.req.query('currency_id'),
667
675
  });
668
- return res.status(400).json({ error: err.message });
676
+ return c.json({ error: err.message }, 400);
669
677
  }
670
678
  });
671
679
 
@@ -686,11 +694,12 @@ const statsSchema = Joi.object({
686
694
  });
687
695
 
688
696
  // Get credit grant statistics with flexible filtering
689
- router.get('/stats', auth, async (req, res) => {
697
+ // Static path registered before /:id so hono matches it first.
698
+ app.get('/stats', auth, async (c) => {
690
699
  try {
691
- const { error, value } = statsSchema.validate(req.query, { stripUnknown: true });
700
+ const { error, value } = statsSchema.validate(c.req.query(), { stripUnknown: true });
692
701
  if (error) {
693
- return res.status(400).json({ error: error.message });
702
+ return c.json({ error: error.message }, 400);
694
703
  }
695
704
 
696
705
  const {
@@ -703,7 +712,7 @@ router.get('/stats', auth, async (req, res) => {
703
712
  } = value;
704
713
 
705
714
  if (startDate > endDate) {
706
- return res.status(400).json({ error: 'start_date must be less than or equal to end_date' });
715
+ return c.json({ error: 'start_date must be less than or equal to end_date' }, 400);
707
716
  }
708
717
 
709
718
  const result = await getCreditGrantStats({
@@ -715,22 +724,22 @@ router.get('/stats', auth, async (req, res) => {
715
724
  timezoneOffset,
716
725
  });
717
726
 
718
- return res.json(result);
727
+ return c.json(result);
719
728
  } catch (err: any) {
720
- logger.error('Error getting credit grant stats', { error: err.message, query: req.query });
721
- return res.status(400).json({ error: err.message });
729
+ logger.error('Error getting credit grant stats', { error: err.message, query: c.req.query() });
730
+ return c.json({ error: err.message }, 400);
722
731
  }
723
732
  });
724
733
 
725
- router.get('/:id', authPortal, async (req, res) => {
726
- const creditGrant = (await CreditGrant.findByPk(req.params.id, {
734
+ app.get('/:id', authPortal, async (c) => {
735
+ const creditGrant = (await CreditGrant.findByPk(c.req.param('id'), {
727
736
  include: [
728
737
  { model: Customer, as: 'customer' },
729
738
  { model: PaymentCurrency, as: 'paymentCurrency' },
730
739
  ],
731
740
  })) as CreditGrant & { paymentCurrency?: PaymentCurrency };
732
741
  if (!creditGrant) {
733
- return res.status(404).json({ error: `Credit grant ${req.params.id} not found` });
742
+ return c.json({ error: `Credit grant ${c.req.param('id')} not found` }, 404);
734
743
  }
735
744
 
736
745
  let paymentMethod = null;
@@ -738,40 +747,41 @@ router.get('/:id', authPortal, async (req, res) => {
738
747
  paymentMethod = await PaymentMethod.findByPk(creditGrant.paymentCurrency.payment_method_id);
739
748
  }
740
749
  const expandedPrices = await expandScopePrices(creditGrant);
741
- return res.json({
750
+ return c.json({
742
751
  ...creditGrant.toJSON(),
743
752
  items: expandedPrices,
744
753
  paymentMethod,
745
754
  });
746
755
  });
747
756
 
748
- router.post('/', auth, async (req, res) => {
757
+ app.post('/', auth, async (c) => {
749
758
  try {
750
- const { error } = creditGrantSchema.validate(req.body);
759
+ const body = c.get('sanitizedBody') ?? {};
760
+ const { error } = creditGrantSchema.validate(body);
751
761
  if (error) {
752
- return res.status(400).json({ error: `Credit grant create request invalid: ${error.message}` });
762
+ return c.json({ error: `Credit grant create request invalid: ${error.message}` }, 400);
753
763
  }
754
764
 
755
765
  // 获取币种信息用于金额转换
756
- const currencyId = req.body.currency_id;
766
+ const currencyId = body.currency_id;
757
767
  if (!currencyId) {
758
- return res.status(400).json({ error: 'currency_id is required' });
768
+ return c.json({ error: 'currency_id is required' }, 400);
759
769
  }
760
770
 
761
771
  const paymentCurrency = await PaymentCurrency.findByPk(currencyId);
762
772
  if (!paymentCurrency) {
763
- return res.status(404).json({ error: `PaymentCurrency ${currencyId} not found` });
773
+ return c.json({ error: `PaymentCurrency ${currencyId} not found` }, 404);
764
774
  }
765
775
 
766
- let customer = await Customer.findByPkOrDid(req.body.customer_id);
776
+ let customer = await Customer.findByPkOrDid(body.customer_id);
767
777
  if (!customer) {
768
- if (req.body.customer_id.startsWith('cus_')) {
769
- return res.status(404).json({ error: `Customer ${req.body.customer_id} not found` });
778
+ if (body.customer_id.startsWith('cus_')) {
779
+ return c.json({ error: `Customer ${body.customer_id} not found` }, 404);
770
780
  }
771
- const did = req.body.customer_id;
781
+ const did = body.customer_id;
772
782
  const { user: userInfo } = await blocklet.getUser(did);
773
783
  if (!userInfo) {
774
- return res.status(404).json({ error: `User ${did} not found` });
784
+ return c.json({ error: `User ${did} not found` }, 404);
775
785
  }
776
786
  customer = await Customer.create({
777
787
  livemode: true,
@@ -793,9 +803,9 @@ router.post('/', auth, async (req, res) => {
793
803
  });
794
804
  }
795
805
 
796
- const unitAmount = fromTokenToUnit(req.body.amount, paymentCurrency.decimal).toString();
797
- let applicabilityConfig = req.body.applicability_config;
798
- if (!req.body.applicability_config || !req.body.applicability_config.scope?.prices) {
806
+ const unitAmount = fromTokenToUnit(body.amount, paymentCurrency.decimal).toString();
807
+ let applicabilityConfig = body.applicability_config;
808
+ if (!body.applicability_config || !body.applicability_config.scope?.prices) {
799
809
  applicabilityConfig = {
800
810
  scope: {
801
811
  price_type: 'metered',
@@ -807,26 +817,26 @@ router.post('/', auth, async (req, res) => {
807
817
  amount: unitAmount,
808
818
  currency_id: currencyId,
809
819
  customer_id: customer.id,
810
- name: req.body.name,
811
- category: req.body.category,
812
- priority: req.body.priority,
813
- effective_at: req.body.effective_at,
814
- expires_at: req.body.expires_at,
820
+ name: body.name,
821
+ category: body.category,
822
+ priority: body.priority,
823
+ effective_at: body.effective_at,
824
+ expires_at: body.expires_at,
815
825
  applicability_config: applicabilityConfig,
816
- metadata: req.body.metadata,
817
- livemode: req.livemode,
818
- created_via: req.user?.via || 'api',
819
- created_by: req.user?.did,
826
+ metadata: body.metadata,
827
+ livemode: c.get('livemode'),
828
+ created_via: c.get('user')?.via || 'api',
829
+ created_by: c.get('user')?.did,
820
830
  });
821
831
 
822
- return res.json({
832
+ return c.json({
823
833
  ...creditGrant.toJSON(),
824
834
  customer,
825
835
  paymentCurrency,
826
836
  });
827
837
  } catch (err: any) {
828
- logger.error('create credit grant failed', { error: err, request: req.body });
829
- return res.status(400).json({ error: err.message });
838
+ logger.error('create credit grant failed', { error: err, request: c.get('sanitizedBody') ?? {} });
839
+ return c.json({ error: err.message }, 400);
830
840
  }
831
841
  });
832
842
 
@@ -835,15 +845,16 @@ const updateSchema = Joi.object({
835
845
  expired: Joi.boolean().optional(),
836
846
  });
837
847
 
838
- router.put('/:id', auth, async (req, res) => {
839
- const creditGrant = await CreditGrant.findByPk(req.params.id);
848
+ app.put('/:id', auth, async (c) => {
849
+ const body = c.get('sanitizedBody') ?? {};
850
+ const creditGrant = await CreditGrant.findByPk(c.req.param('id'));
840
851
  if (!creditGrant) {
841
- return res.status(404).json({ error: `Credit grant ${req.params.id} not found` });
852
+ return c.json({ error: `Credit grant ${c.req.param('id')} not found` }, 404);
842
853
  }
843
854
 
844
- const { error, value } = updateSchema.validate(pick(req.body, ['metadata', 'expired']), { stripUnknown: true });
855
+ const { error, value } = updateSchema.validate(pick(body, ['metadata', 'expired']), { stripUnknown: true });
845
856
  if (error) {
846
- return res.status(400).json({ error: `Credit grant update request invalid: ${error.message}` });
857
+ return c.json({ error: `Credit grant update request invalid: ${error.message}` }, 400);
847
858
  }
848
859
 
849
860
  // Handle metadata update
@@ -855,21 +866,24 @@ router.put('/:id', auth, async (req, res) => {
855
866
  if (value.expired === true) {
856
867
  // Only pending or granted grants can be expired
857
868
  if (!['pending', 'granted'].includes(creditGrant.status)) {
858
- return res.status(400).json({
859
- error: `Cannot expire credit grant with status '${creditGrant.status}'. Only 'pending' or 'granted' grants can be expired.`,
860
- });
869
+ return c.json(
870
+ {
871
+ error: `Cannot expire credit grant with status '${creditGrant.status}'. Only 'pending' or 'granted' grants can be expired.`,
872
+ },
873
+ 400
874
+ );
861
875
  }
862
876
 
863
877
  await expireGrant(creditGrant);
864
878
 
865
879
  logger.info('Credit grant manually expired', {
866
- creditGrantId: req.params.id,
880
+ creditGrantId: c.req.param('id'),
867
881
  previousStatus: creditGrant.status,
868
- requestedBy: req.user?.did,
882
+ requestedBy: c.get('user')?.did,
869
883
  });
870
884
  }
871
885
 
872
- return res.json({ success: true });
886
+ return c.json({ success: true });
873
887
  });
874
888
 
875
- export default router;
889
+ export default app;