payment-kit 1.29.0 → 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 (312) 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/docs/2026-06-10-bundle-size-analysis.md +288 -0
  236. package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
  237. package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
  238. package/cloudflare/migrations/0008_schema_parity.sql +16 -0
  239. package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
  240. package/cloudflare/queue-runtime-mode.ts +13 -0
  241. package/cloudflare/run-build.js +31 -56
  242. package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
  243. package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
  244. package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
  245. package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
  246. package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
  247. package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
  248. package/cloudflare/shims/blocklet-sdk/util-csrf.ts +13 -0
  249. package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
  250. package/cloudflare/shims/cron.ts +38 -158
  251. package/cloudflare/shims/events.ts +124 -0
  252. package/cloudflare/shims/fastq.ts +15 -1
  253. package/cloudflare/shims/nedb-storage.ts +16 -8
  254. package/cloudflare/shims/node-fetch.ts +35 -0
  255. package/cloudflare/shims/xss.ts +8 -0
  256. package/cloudflare/tenant-middleware.ts +36 -0
  257. package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
  258. package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
  259. package/cloudflare/worker.ts +204 -433
  260. package/cloudflare/wrangler.local-e2e.jsonc +26 -0
  261. package/jest.config.js +3 -1
  262. package/package.json +33 -38
  263. package/scripts/core-env-whitelist.json +1 -0
  264. package/scripts/e2e-12b-runtime.ts +149 -0
  265. package/scripts/e2e-core-config.ts +125 -0
  266. package/scripts/e2e-d1-tenancy.ts +116 -0
  267. package/scripts/e2e-d2-cron-queue.ts +139 -0
  268. package/scripts/e2e-d3-embedded-multi.ts +171 -0
  269. package/scripts/e2e-hono-s2.ts +125 -0
  270. package/scripts/e2e-hono-s3e.ts +135 -0
  271. package/scripts/e2e-hono-s4.ts +114 -0
  272. package/scripts/e2e-migration-contract.ts +100 -0
  273. package/scripts/e2e-s0.ts +61 -0
  274. package/scripts/e2e-s1.ts +107 -0
  275. package/scripts/e2e-s2.ts +178 -0
  276. package/scripts/e2e-s3.ts +110 -0
  277. package/scripts/e2e-s4.ts +191 -0
  278. package/scripts/e2e-s5.ts +139 -0
  279. package/scripts/e2e-s6.ts +127 -0
  280. package/scripts/e2e-tenant-model.ts +119 -0
  281. package/scripts/e2e-tenant-worker.ts +199 -0
  282. package/scripts/gen-sql-migrations.js +46 -0
  283. package/scripts/phase8-codemod.js +219 -0
  284. package/scripts/phase9a-env-getters-codemod.js +82 -0
  285. package/scripts/scan-core-env.js +109 -0
  286. package/scripts/scan-tenant-queries.js +235 -0
  287. package/scripts/schema-drift-guard.ts +210 -0
  288. package/scripts/tenant-scan-whitelist.json +1 -0
  289. package/src/env.d.ts +13 -1
  290. package/tsconfig.json +1 -1
  291. package/api/src/libs/did-space.ts +0 -235
  292. package/api/src/libs/middleware.ts +0 -50
  293. package/api/src/libs/security.ts +0 -192
  294. package/api/src/queues/space.ts +0 -662
  295. package/api/src/routes/credit-tokens.ts +0 -38
  296. package/api/src/routes/exchange-rates.ts +0 -87
  297. package/api/src/routes/index.ts +0 -142
  298. package/api/src/routes/integrations/stripe.ts +0 -61
  299. package/api/src/routes/meters.ts +0 -274
  300. package/api/src/routes/passports.ts +0 -68
  301. package/api/src/routes/redirect.ts +0 -20
  302. package/api/src/routes/tool.ts +0 -65
  303. package/api/src/routes/webhook-endpoints.ts +0 -126
  304. package/api/tests/routes/credit-grants.spec.ts +0 -1261
  305. package/cloudflare/shims/did-space-js.ts +0 -17
  306. package/cloudflare/shims/did-space.ts +0 -11
  307. package/cloudflare/shims/express-compat/index.ts +0 -80
  308. package/cloudflare/shims/express-compat/types.ts +0 -41
  309. package/cloudflare/shims/lock.ts +0 -115
  310. package/cloudflare/shims/queue.ts +0 -611
  311. package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
  312. package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
@@ -1,14 +1,18 @@
1
- import { Router } from 'express';
1
+ // Phase 3 (express→hono) hono fork of routes/tax-rates.ts. Sub-app with
2
+ // routes relative to /api/tax-rates (mounted via mountResourceGroup). The
3
+ // business logic is unchanged; only the express plumbing becomes hono:
4
+ // req.body → c.get('sanitizedBody'); res.status(n).json(x) → c.json(x, n).
5
+ import { Hono } from 'hono';
2
6
  import Joi from 'joi';
3
7
 
4
8
  import { CustomError } from '@blocklet/error';
5
9
  import { literal } from 'sequelize';
6
- import { authenticate } from '../libs/security';
7
- import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
8
- import { formatMetadata } from '../libs/util';
9
- import { InvoiceItem, TaxRate } from '../store/models';
10
+ import { authenticate } from '../../middlewares/hono/security';
11
+ import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../../libs/api';
12
+ import { formatMetadata } from '../../libs/util';
13
+ import { InvoiceItem, TaxRate } from '../../store/models';
10
14
 
11
- const router = Router();
15
+ const app = new Hono();
12
16
  const auth = authenticate({ component: true, roles: ['owner', 'admin'] });
13
17
 
14
18
  const createTaxRateSchema = Joi.object({
@@ -42,19 +46,18 @@ const listSchema = createListParamSchema<{
42
46
  });
43
47
 
44
48
  // List tax rates
45
- router.get('/', auth, async (req, res) => {
49
+ app.get('/', auth, async (c) => {
46
50
  try {
47
- const { error, value: validated } = listSchema.validate(req.query, { stripUnknown: true });
51
+ const { error, value: validated } = listSchema.validate(c.req.query(), { stripUnknown: true });
48
52
  if (error) {
49
- res.status(400).json({ error: `Validation error: ${error.message}` });
50
- return;
53
+ return c.json({ error: `Validation error: ${error.message}` }, 400);
51
54
  }
52
55
 
53
56
  const { page, pageSize, ...query } = validated;
54
57
  const where = getWhereFromKvQuery(query.q);
55
58
 
56
- if (typeof req.livemode === 'boolean') {
57
- where.livemode = req.livemode;
59
+ if (typeof c.get('livemode') === 'boolean') {
60
+ where.livemode = c.get('livemode');
58
61
  }
59
62
  if (query.country) {
60
63
  where.country = query.country;
@@ -68,7 +71,7 @@ router.get('/', auth, async (req, res) => {
68
71
 
69
72
  const { rows: list, count } = await TaxRate.findAndCountAll({
70
73
  where,
71
- order: getOrder(req.query, [['created_at', 'DESC']]),
74
+ order: getOrder(c.req.query(), [['created_at', 'DESC']]),
72
75
  offset: (page - 1) * pageSize,
73
76
  limit: pageSize,
74
77
  attributes: {
@@ -85,39 +88,48 @@ router.get('/', auth, async (req, res) => {
85
88
  },
86
89
  });
87
90
 
88
- res.json({ count, list });
91
+ return c.json({ count, list });
89
92
  } catch (error) {
90
- res.status(400).json({ error: error?.message });
93
+ return c.json({ error: (error as any)?.message }, 400);
91
94
  }
92
95
  });
93
96
 
94
- // Get a specific tax rate
95
- router.get('/:id', auth, async (req, res) => {
96
- const taxRate = await TaxRate.findOne({
97
- where: {
98
- id: req.params.id,
99
- livemode: req.livemode,
100
- },
97
+ // Find matching tax rate for a given location and tax code
98
+ // static path registered before /:id so it wins over the param route
99
+ app.post('/match', auth, async (c) => {
100
+ const schema = Joi.object({
101
+ country: Joi.string().length(2).uppercase().required(),
102
+ state: Joi.string().max(50).empty('').optional(),
103
+ postal_code: Joi.string().max(20).empty('').optional(),
104
+ tax_code: Joi.string().max(20).empty('').optional(),
101
105
  });
102
106
 
103
- if (!taxRate) {
104
- throw new CustomError(404, 'Tax rate not found');
107
+ const { error, value: data } = schema.validate(c.get('sanitizedBody') ?? {}, { stripUnknown: true });
108
+ if (error) {
109
+ return c.json({ error: error.message }, 400);
105
110
  }
106
111
 
107
- res.json(taxRate);
112
+ const matchedRate = await TaxRate.findMatchingRate({
113
+ country: data.country,
114
+ state: data.state,
115
+ postalCode: data.postal_code,
116
+ taxCode: data.tax_code,
117
+ livemode: c.get('livemode'),
118
+ });
119
+
120
+ return c.json(matchedRate);
108
121
  });
109
122
 
110
123
  // Create a new tax rate
111
- router.post('/', auth, async (req, res) => {
112
- const { error, value: data } = createTaxRateSchema.validate(req.body, { stripUnknown: true });
124
+ app.post('/', auth, async (c) => {
125
+ const { error, value: data } = createTaxRateSchema.validate(c.get('sanitizedBody') ?? {}, { stripUnknown: true });
113
126
  if (error) {
114
- res.status(400).json({ error: `Validation failed: ${error.message}` });
115
- return;
127
+ return c.json({ error: `Validation failed: ${error.message}` }, 400);
116
128
  }
117
129
 
118
130
  const existingRate = await TaxRate.findOne({
119
131
  where: {
120
- livemode: req.livemode,
132
+ livemode: c.get('livemode'),
121
133
  country: data.country,
122
134
  state: data.state || null,
123
135
  postal_code: data.postal_code || null,
@@ -134,25 +146,40 @@ router.post('/', auth, async (req, res) => {
134
146
 
135
147
  const taxRate = await TaxRate.create({
136
148
  ...data,
137
- livemode: req.livemode,
149
+ livemode: c.get('livemode'),
138
150
  metadata: formatMetadata(data.metadata),
139
151
  });
140
152
 
141
- res.json(taxRate);
153
+ return c.json(taxRate);
154
+ });
155
+
156
+ // Get a specific tax rate
157
+ app.get('/:id', auth, async (c) => {
158
+ const taxRate = await TaxRate.findOne({
159
+ where: {
160
+ id: c.req.param('id'),
161
+ livemode: c.get('livemode'),
162
+ },
163
+ });
164
+
165
+ if (!taxRate) {
166
+ throw new CustomError(404, 'Tax rate not found');
167
+ }
168
+
169
+ return c.json(taxRate);
142
170
  });
143
171
 
144
172
  // Update a tax rate
145
- router.put('/:id', auth, async (req, res) => {
146
- const { error, value: data } = updateTaxRateSchema.validate(req.body, { stripUnknown: true });
173
+ app.put('/:id', auth, async (c) => {
174
+ const { error, value: data } = updateTaxRateSchema.validate(c.get('sanitizedBody') ?? {}, { stripUnknown: true });
147
175
  if (error) {
148
- res.status(400).json({ error: `Validation failed: ${error.message}` });
149
- return;
176
+ return c.json({ error: `Validation failed: ${error.message}` }, 400);
150
177
  }
151
178
 
152
179
  const taxRate = await TaxRate.findOne({
153
180
  where: {
154
- id: req.params.id,
155
- livemode: req.livemode,
181
+ id: c.req.param('id'),
182
+ livemode: c.get('livemode'),
156
183
  },
157
184
  });
158
185
 
@@ -165,15 +192,15 @@ router.put('/:id', auth, async (req, res) => {
165
192
  metadata: data.metadata ? formatMetadata(data.metadata) : taxRate.metadata,
166
193
  });
167
194
 
168
- res.json(taxRate);
195
+ return c.json(taxRate);
169
196
  });
170
197
 
171
198
  // Delete a tax rate
172
- router.delete('/:id', auth, async (req, res) => {
199
+ app.delete('/:id', auth, async (c) => {
173
200
  const taxRate = await TaxRate.findOne({
174
201
  where: {
175
- id: req.params.id,
176
- livemode: req.livemode,
202
+ id: c.req.param('id'),
203
+ livemode: c.get('livemode'),
177
204
  },
178
205
  });
179
206
 
@@ -188,33 +215,7 @@ router.delete('/:id', auth, async (req, res) => {
188
215
 
189
216
  await taxRate.destroy();
190
217
 
191
- res.json({ deleted: true, id: req.params.id });
192
- });
193
-
194
- // Find matching tax rate for a given location and tax code
195
- router.post('/match', auth, async (req, res) => {
196
- const schema = Joi.object({
197
- country: Joi.string().length(2).uppercase().required(),
198
- state: Joi.string().max(50).empty('').optional(),
199
- postal_code: Joi.string().max(20).empty('').optional(),
200
- tax_code: Joi.string().max(20).empty('').optional(),
201
- });
202
-
203
- const { error, value: data } = schema.validate(req.body, { stripUnknown: true });
204
- if (error) {
205
- res.status(400).json({ error: error.message });
206
- return;
207
- }
208
-
209
- const matchedRate = await TaxRate.findMatchingRate({
210
- country: data.country,
211
- state: data.state,
212
- postalCode: data.postal_code,
213
- taxCode: data.tax_code,
214
- livemode: req.livemode,
215
- });
216
-
217
- res.json(matchedRate);
218
+ return c.json({ deleted: true, id: c.req.param('id') });
218
219
  });
219
220
 
220
- export default router;
221
+ export default app;
@@ -0,0 +1,69 @@
1
+ // Phase 3 (express→hono) — hono fork of routes/tool.ts. Sub-app with
2
+ // routes relative to /api/tool (mounted via mountResourceGroup). The
3
+ // business logic is unchanged; only the express plumbing becomes hono:
4
+ // req.body → c.get('sanitizedBody'); res.status(n).json(x) → c.json(x, n).
5
+ import dayjs from 'dayjs';
6
+ import { Hono } from 'hono';
7
+ import Joi from 'joi';
8
+
9
+ import { getUrl } from '@blocklet/sdk/lib/component';
10
+ import { joinURL } from 'ufo';
11
+ import { authenticate } from '../../middlewares/hono/security';
12
+ import { formatToShortUrl } from '../../libs/url';
13
+ import { CheckoutSession } from '../../store/models';
14
+ import logger from '../../libs/logger';
15
+
16
+ const loginAuth = authenticate<CheckoutSession>({ component: false, ensureLogin: true, mine: true });
17
+
18
+ const app = new Hono();
19
+
20
+ const shortUrlSchema = Joi.object({
21
+ url: Joi.string().uri().required(),
22
+ timeoutMin: Joi.number().optional(),
23
+ maxVisits: Joi.number().optional(),
24
+ }).unknown(true);
25
+
26
+ app.get('/short-url', loginAuth, async (c) => {
27
+ const query = c.req.query();
28
+ const { error } = shortUrlSchema.validate(query);
29
+ if (error) {
30
+ return c.json({ error: 'Invalid parameters', message: error.message }, 400);
31
+ }
32
+ const { url, timeoutMin = 60, maxVisits = 5 } = query;
33
+ const timeoutMinNumber = Number(timeoutMin);
34
+ const validUntil = dayjs()
35
+ .add(timeoutMinNumber > 60 ? 60 : timeoutMinNumber, 'minutes')
36
+ .format('YYYY-MM-DDTHH:mm:ss+00:00');
37
+
38
+ const shortUrl = await formatToShortUrl({ url: url as string, validUntil, maxVisits: Number(maxVisits) });
39
+
40
+ return c.json({ url: shortUrl });
41
+ });
42
+
43
+ app.get('/short-connect-url', async (c) => {
44
+ const query = c.req.query();
45
+ const { error } = shortUrlSchema.validate(query);
46
+ if (error) {
47
+ return c.json({ error: 'Invalid parameters', message: error.message }, 400);
48
+ }
49
+
50
+ const { url, timeoutMin = 60, maxVisits = 5 } = query;
51
+ const serverUrl = getUrl();
52
+
53
+ const allowedEndpoint = joinURL(new URL(serverUrl).origin, '.well-known/service/gen-simple-access-key');
54
+
55
+ if (!url?.toString().startsWith(allowedEndpoint)) {
56
+ logger.error('Client try to generate short connect url from invalid service', { url });
57
+ return c.json({ error: 'Just support generate short connect url from gen-simple-access-key service' }, 400);
58
+ }
59
+
60
+ const timeoutMinNumber = Number(timeoutMin);
61
+ const validUntil = dayjs()
62
+ .add(timeoutMinNumber > 60 ? 60 : timeoutMinNumber, 'minutes')
63
+ .format('YYYY-MM-DDTHH:mm:ss+00:00');
64
+
65
+ const shortUrl = await formatToShortUrl({ url: url as string, validUntil, maxVisits: Number(maxVisits) });
66
+ return c.json({ url: shortUrl });
67
+ });
68
+
69
+ export default app;
@@ -1,19 +1,22 @@
1
- /* eslint-disable consistent-return */
2
- import { Request, Response, Router } from 'express';
1
+ // Phase 3 (express→hono) — hono fork of routes/usage-records.ts. Sub-app with
2
+ // routes relative to /api/usage-records (mounted via mountResourceGroup). The
3
+ // business logic is unchanged; only the express plumbing becomes hono:
4
+ // req.body → c.get('sanitizedBody') ?? {}; res.status(n).json(x) → c.json(x, n).
5
+ import { Context, Hono } from 'hono';
3
6
  import Joi from 'joi';
4
7
  import pick from 'lodash/pick';
5
8
  import { Op } from 'sequelize';
6
9
 
7
- import { forwardUsageRecordToStripe } from '../integrations/stripe/resource';
8
- import { createListParamSchema, getOrder } from '../libs/api';
9
- import dayjs from '../libs/dayjs';
10
- import { authenticate } from '../libs/security';
11
- import { usageRecordQueue } from '../queues/usage-record';
12
- import { Invoice, Price, Subscription, SubscriptionItem, UsageRecord } from '../store/models';
13
- import logger from '../libs/logger';
14
- import { getLock } from '../libs/lock';
10
+ import { forwardUsageRecordToStripe } from '../../integrations/stripe/resource';
11
+ import { createListParamSchema, getOrder } from '../../libs/api';
12
+ import dayjs from '../../libs/dayjs';
13
+ import { getLock } from '../../libs/lock';
14
+ import logger from '../../libs/logger';
15
+ import { authenticate } from '../../middlewares/hono/security';
16
+ import { usageRecordQueue } from '../../queues/usage-record';
17
+ import { Invoice, Price, Subscription, SubscriptionItem, UsageRecord } from '../../store/models';
15
18
 
16
- const router = Router();
19
+ const app = new Hono();
17
20
  const auth = authenticate<UsageRecord>({ component: true, roles: ['owner', 'admin'] });
18
21
 
19
22
  const UsageReportScheme = Joi.object({
@@ -25,24 +28,25 @@ const UsageReportScheme = Joi.object({
25
28
  }).unknown(true);
26
29
 
27
30
  // @link https://stripe.com/docs/api/usage_records/create
28
- router.post('/', auth, async (req, res) => {
29
- const { error } = UsageReportScheme.validate(req.body);
31
+ app.post('/', auth, async (c) => {
32
+ const body = c.get('sanitizedBody') ?? {};
33
+ const { error } = UsageReportScheme.validate(body);
30
34
  if (error) {
31
- return res.status(400).json({ error: error.message });
35
+ return c.json({ error: error.message }, 400);
32
36
  }
33
- const raw: Partial<UsageRecord> = pick(req.body, ['timestamp', 'quantity', 'subscription_item_id']);
37
+ const raw: Partial<UsageRecord> = pick(body, ['timestamp', 'quantity', 'subscription_item_id']);
34
38
  const item = await SubscriptionItem.findByPk(raw.subscription_item_id);
35
39
  if (!item) {
36
- return res.status(400).json({ error: `SubscriptionItem not found: ${raw.subscription_item_id}` });
40
+ return c.json({ error: `SubscriptionItem not found: ${raw.subscription_item_id}` }, 400);
37
41
  }
38
42
 
39
43
  const subscription = await Subscription.findByPk(item.subscription_id);
40
44
  if (!subscription) {
41
- return res.status(400).json({ error: `Subscription not found: ${item.subscription_id}` });
45
+ return c.json({ error: `Subscription not found: ${item.subscription_id}` }, 400);
42
46
  }
43
47
  if (raw.timestamp) {
44
48
  if (raw.timestamp < subscription.current_period_start || raw.timestamp > subscription.current_period_end) {
45
- return res.status(400).json({ error: '`timestamp` must be within the current period' });
49
+ return c.json({ error: '`timestamp` must be within the current period' }, 400);
46
50
  }
47
51
  }
48
52
 
@@ -57,14 +61,14 @@ router.post('/', auth, async (req, res) => {
57
61
  logger.info('Usage reporting', {
58
62
  subscriptionId: item.subscription_id,
59
63
  subscriptionItemId: item.id,
60
- body: req.body,
64
+ body,
61
65
  });
62
66
 
63
67
  let doc = await UsageRecord.findOne({
64
68
  where: { timestamp: raw.timestamp, subscription_item_id: raw.subscription_item_id },
65
69
  });
66
70
 
67
- const action = req.body.action || 'increment';
71
+ const action = body.action || 'increment';
68
72
  if (doc) {
69
73
  if (doc.billed) {
70
74
  logger.info('UsageRecord updated', {
@@ -72,7 +76,7 @@ router.post('/', auth, async (req, res) => {
72
76
  timestamp: raw.timestamp,
73
77
  newQuantity: raw.quantity,
74
78
  });
75
- return res.status(400).json({ error: 'UsageRecord is immutable because already billed' });
79
+ return c.json({ error: 'UsageRecord is immutable because already billed' }, 400);
76
80
  }
77
81
  if (action === 'increment') {
78
82
  await doc.increment('quantity', { by: raw.quantity });
@@ -88,9 +92,10 @@ router.post('/', auth, async (req, res) => {
88
92
  subscriptionId: subscription.id,
89
93
  action,
90
94
  });
91
- return res
92
- .status(400)
93
- .json({ error: 'UsageRecord action must be `increment` for subscriptions with billing_thresholds' });
95
+ return c.json(
96
+ { error: 'UsageRecord action must be `increment` for subscriptions with billing_thresholds' },
97
+ 400
98
+ );
94
99
  }
95
100
  await doc.update({ quantity: raw.quantity });
96
101
  logger.info('UsageRecord updated', {
@@ -101,7 +106,7 @@ router.post('/', auth, async (req, res) => {
101
106
  });
102
107
  }
103
108
  } else {
104
- raw.livemode = req.livemode;
109
+ raw.livemode = c.get('livemode');
105
110
  doc = await UsageRecord.create(raw as UsageRecord);
106
111
  }
107
112
 
@@ -127,10 +132,10 @@ router.post('/', auth, async (req, res) => {
127
132
  action,
128
133
  });
129
134
  await doc.reload();
130
- return res.json(doc);
135
+ return c.json(doc);
131
136
  } catch (err) {
132
137
  logger.error('Error in usage-records', { error: err });
133
- return res.status(400).json({ error: err.message || 'UsageRecord creation failed' });
138
+ return c.json({ error: (err as any).message || 'UsageRecord creation failed' }, 400);
134
139
  } finally {
135
140
  lock.release();
136
141
  }
@@ -149,21 +154,21 @@ const schema = createListParamSchema<{
149
154
  },
150
155
  100
151
156
  );
152
- router.get('/summary', auth, async (req, res) => {
153
- const { page, pageSize, ...query } = await schema.validateAsync(req.query, { stripUnknown: true });
157
+ app.get('/summary', auth, async (c) => {
158
+ const { page, pageSize, ...query } = await schema.validateAsync(c.req.query(), { stripUnknown: true });
154
159
 
155
160
  try {
156
161
  const item = await SubscriptionItem.findByPk(query.subscription_item_id, {
157
162
  include: [{ model: Price, as: 'price' }],
158
163
  });
159
164
  if (!item) {
160
- return res.status(400).json({ error: `SubscriptionItem not found: ${query.subscription_item_id}` });
165
+ return c.json({ error: `SubscriptionItem not found: ${query.subscription_item_id}` }, 400);
161
166
  }
162
167
 
163
168
  const { rows, count } = await Invoice.findAndCountAll({
164
169
  where: { subscription_id: item.subscription_id },
165
170
  attributes: ['id', 'period_end', 'period_start'],
166
- order: getOrder(req.query, [['created_at', 'DESC']]),
171
+ order: getOrder(c.req.query(), [['created_at', 'DESC']]),
167
172
  offset: (page - 1) * pageSize,
168
173
  limit: pageSize,
169
174
  });
@@ -190,10 +195,10 @@ router.get('/summary', auth, async (req, res) => {
190
195
  })
191
196
  );
192
197
 
193
- res.json({ count, list, paging: { page, pageSize } });
198
+ return c.json({ count, list, paging: { page, pageSize } });
194
199
  } catch (err) {
195
200
  logger.error(err);
196
- res.json({ count: 0, list: [], paging: { page, pageSize } });
201
+ return c.json({ count: 0, list: [], paging: { page, pageSize } });
197
202
  }
198
203
  });
199
204
 
@@ -207,19 +212,19 @@ const UsageRecordScheme = Joi.object({
207
212
  }).unknown(true);
208
213
 
209
214
  export function createUsageRecordQueryFn(doc?: Subscription) {
210
- return async (req: Request, res: Response) => {
211
- const { error, value: query } = UsageRecordScheme.validate(req.query, { stripUnknown: true });
215
+ return async (c: Context) => {
216
+ const { error, value: query } = UsageRecordScheme.validate(c.req.query(), { stripUnknown: true });
212
217
  if (error) {
213
- return res.status(400).json({ error: `usage record request query invalid: ${error.message}` });
218
+ return c.json({ error: `usage record request query invalid: ${error.message}` }, 400);
214
219
  }
215
220
  try {
216
221
  const item = await SubscriptionItem.findByPk(query.subscription_item_id);
217
222
  if (!item) {
218
- return res.status(400).json({ error: `SubscriptionItem not found: ${query.subscription_item_id}` });
223
+ return c.json({ error: `SubscriptionItem not found: ${query.subscription_item_id}` }, 400);
219
224
  }
220
225
  const subscription = doc || (await Subscription.findByPk(item.subscription_id));
221
226
  if (!subscription) {
222
- return res.status(400).json({ error: `Subscription not found: ${item.subscription_id}` });
227
+ return c.json({ error: `Subscription not found: ${item.subscription_id}` }, 400);
223
228
  }
224
229
 
225
230
  const { rows: list, count } = await UsageRecord.findAndCountAll({
@@ -233,14 +238,14 @@ export function createUsageRecordQueryFn(doc?: Subscription) {
233
238
  order: [['created_at', 'ASC']],
234
239
  });
235
240
 
236
- res.json({ count, list });
241
+ return c.json({ count, list });
237
242
  } catch (err) {
238
243
  logger.error(err);
239
- res.json({ count: 0, list: [] });
244
+ return c.json({ count: 0, list: [] });
240
245
  }
241
246
  };
242
247
  }
243
248
 
244
- router.get('/', auth, createUsageRecordQueryFn());
249
+ app.get('/', auth, createUsageRecordQueryFn());
245
250
 
246
- export default router;
251
+ export default app;