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,23 +1,34 @@
1
+ // Phase 3 (express→hono) — hono fork of routes/products.ts. Sub-app with
2
+ // routes relative to /api/products (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 { BN, fromTokenToUnit } from '@ocap/util';
2
- import { Router } from 'express';
6
+ import { Hono } from 'hono';
3
7
  import Joi from 'joi';
4
8
  import cloneDeep from 'lodash/cloneDeep';
5
9
  import pick from 'lodash/pick';
6
10
  import { Op } from 'sequelize';
7
-
8
- import { createListParamSchema, getOrder, getWhereFromKvQuery, getWhereFromQuery, MetadataSchema } from '../libs/api';
9
- import logger from '../libs/logger';
10
- import { authenticate } from '../libs/security';
11
- import { formatMetadata } from '../libs/util';
12
- import { PaymentCurrency } from '../store/models/payment-currency';
13
- import { Price } from '../store/models/price';
14
- import { Product } from '../store/models/product';
15
- import { ProductVendor } from '../store/models/product-vendor';
16
-
17
- import type { CustomUnitAmount } from '../store/models/types';
18
- import { checkCurrencySupportRecurring } from '../libs/product';
19
-
20
- const router = Router();
11
+ import { allowChangeLockedPrice } from '../../libs/env';
12
+
13
+ import {
14
+ createListParamSchema,
15
+ getOrder,
16
+ getWhereFromKvQuery,
17
+ getWhereFromQuery,
18
+ MetadataSchema,
19
+ } from '../../libs/api';
20
+ import logger from '../../libs/logger';
21
+ import { authenticate } from '../../middlewares/hono/security';
22
+ import { formatMetadata } from '../../libs/util';
23
+ import { PaymentCurrency } from '../../store/models/payment-currency';
24
+ import { Price } from '../../store/models/price';
25
+ import { Product } from '../../store/models/product';
26
+ import { ProductVendor } from '../../store/models/product-vendor';
27
+
28
+ import type { CustomUnitAmount } from '../../store/models/types';
29
+ import { checkCurrencySupportRecurring } from '../../libs/product';
30
+
31
+ const app = new Hono();
21
32
 
22
33
  const auth = authenticate<Product>({ component: true, roles: ['owner', 'admin'] });
23
34
 
@@ -40,7 +51,7 @@ const ProductAndPriceSchema = Joi.object({
40
51
  tax_code: Joi.string().max(30).allow(null).empty('').optional(),
41
52
  statement_descriptor: Joi.string()
42
53
  .max(22)
43
- .pattern(/^(?=.*[A-Za-z])[^\u4e00-\u9fa5<>"’\\]*$/)
54
+ .pattern(/^(?=.*[A-Za-z])[^\u4e00-\u9fa5<>"'’\\]*$/)
44
55
  .messages({
45
56
  'string.pattern.base':
46
57
  'statement_descriptor should be at least one letter and cannot include Chinese characters and special characters such as <, >、"、’ or \\',
@@ -283,32 +294,106 @@ export async function createProductAndPrices(payload: any) {
283
294
 
284
295
  // FIXME: @wangshijun use schema validation
285
296
  // create product and price
286
- // eslint-disable-next-line consistent-return
287
- router.post('/', auth, async (req, res) => {
297
+ // static POST route registered before /:id patterns to avoid shadowing
298
+ app.post('/batch-price-update', auth, async (c) => {
299
+ if (!allowChangeLockedPrice()) {
300
+ return c.json({ error: 'forbidden' }, 403);
301
+ }
302
+
303
+ const body = c.get('sanitizedBody') ?? {};
304
+ const dryRun = typeof body.dryRun === 'undefined' || body.dryRun !== '0';
305
+ const factor = Number(body.factor);
306
+ if (Number.isNaN(factor)) {
307
+ return c.json({ error: 'factor is required' }, 400);
308
+ }
309
+ if (factor <= 0) {
310
+ return c.json({ error: 'factor must be positive' }, 400);
311
+ }
312
+ if (!body.description) {
313
+ return c.json({ error: 'description is required' }, 400);
314
+ }
315
+ if (!body.before) {
316
+ return c.json({ error: 'before is required' }, 400);
317
+ }
318
+
288
319
  try {
289
- const { error } = ProductAndPriceSchema.validate(req.body);
320
+ const updated: any[] = [];
321
+ const products = await Product.findAll({
322
+ where: { active: false, livemode: !!c.get('livemode'), created_at: { [Op.lt]: body.before } },
323
+ });
324
+ await Promise.all(
325
+ products.map(async (product) => {
326
+ if (!product.metadata?.domain) {
327
+ return;
328
+ }
329
+
330
+ const prices = await Price.findAll({ where: { product_id: product.id } });
331
+ for (const price of prices) {
332
+ if (price.currency_id === c.get('baseCurrency').id) {
333
+ const unit = new BN(price.unit_amount).div(new BN(factor)).toString();
334
+ const options = cloneDeep(price.currency_options);
335
+ const option = options.find((x) => x.currency_id === price.currency_id);
336
+ if (option) {
337
+ option.unit_amount = unit;
338
+ }
339
+
340
+ const metadata = price.metadata || {};
341
+ metadata.update_history = [metadata.update_history, body.description].filter(Boolean).join(';');
342
+
343
+ if (dryRun) {
344
+ updated.push({ price: price.toJSON(), metadata, unit_amount: unit, currency_options: options });
345
+ } else {
346
+ // eslint-disable-next-line
347
+ await Price.update(
348
+ { metadata, unit_amount: unit, currency_options: options },
349
+ { where: { id: price.id } }
350
+ );
351
+ updated.push(product.id);
352
+ }
353
+ }
354
+ }
355
+ })
356
+ );
357
+
358
+ logger.info('Batch price update completed', {
359
+ updatedCount: updated.length,
360
+ dryRun,
361
+ factor,
362
+ requestedBy: c.get('user')?.did,
363
+ });
364
+ return c.json(updated);
365
+ } catch (err) {
366
+ logger.error('batch price update error', err);
367
+ return c.json({ error: (err as any).message }, 400);
368
+ }
369
+ });
370
+
371
+ app.post('/', auth, async (c) => {
372
+ try {
373
+ const body = c.get('sanitizedBody') ?? {};
374
+ const { error } = ProductAndPriceSchema.validate(body);
290
375
  if (error) {
291
- return res.status(400).json({ error: `Product create request invalid: ${error.message}` });
376
+ return c.json({ error: `Product create request invalid: ${error.message}` }, 400);
292
377
  }
293
378
  const result = await createProductAndPrices({
294
- ...req.body,
379
+ ...body,
295
380
  active: true,
296
- type: req.body.type || 'service',
297
- livemode: !!req.livemode,
298
- created_via: req.user?.via,
299
- currency_id: req.baseCurrency.id,
300
- metadata: formatMetadata(req.body.metadata),
381
+ type: body.type || 'service',
382
+ livemode: !!c.get('livemode'),
383
+ created_via: c.get('user')?.via,
384
+ currency_id: c.get('baseCurrency').id,
385
+ metadata: formatMetadata(body.metadata),
301
386
  });
302
387
  logger.info('Product and prices created', {
303
388
  productId: result.id,
304
389
  name: result.name,
305
390
  priceCount: result.prices.length,
306
- requestedBy: req.user?.did,
391
+ requestedBy: c.get('user')?.did,
307
392
  });
308
- res.json(result);
393
+ return c.json(result);
309
394
  } catch (err) {
310
395
  logger.error('create product error', err);
311
- return res.status(400).json({ error: err.message });
396
+ return c.json({ error: (err as any).message }, 400);
312
397
  }
313
398
  });
314
399
 
@@ -330,16 +415,17 @@ const paginationSchema = createListParamSchema<{
330
415
  meter_id: Joi.string().empty(''),
331
416
  type: Joi.string().empty(''),
332
417
  });
333
- router.get('/', auth, async (req, res) => {
334
- const { page, pageSize, active, livemode, name, description, ...query } = await paginationSchema.validateAsync(
335
- req.query,
336
- { stripUnknown: false, allowUnknown: true }
337
- );
338
- const where = getWhereFromKvQuery(query.q);
339
-
340
- if (query.status) {
418
+ app.get('/', auth, async (c) => {
419
+ const query = c.req.query();
420
+ const { page, pageSize, active, livemode, name, description, ...rest } = await paginationSchema.validateAsync(query, {
421
+ stripUnknown: false,
422
+ allowUnknown: true,
423
+ });
424
+ const where = getWhereFromKvQuery(rest.q);
425
+
426
+ if (rest.status) {
341
427
  // 兼容处理,支持 status
342
- where.active = query.status === 'active';
428
+ where.active = rest.status === 'active';
343
429
  }
344
430
 
345
431
  if (typeof active === 'boolean') {
@@ -354,36 +440,36 @@ router.get('/', auth, async (req, res) => {
354
440
  if (description) {
355
441
  where.description = description;
356
442
  }
357
- if (query.donation === 'hide') {
443
+ if (rest.donation === 'hide') {
358
444
  where.created_via = { [Op.not]: 'donation' };
359
445
  }
360
- Object.keys(query)
446
+ Object.keys(rest)
361
447
  .filter((x) => x.startsWith('metadata.'))
362
448
  .forEach((key: string) => {
363
449
  // @ts-ignore
364
- where[key] = query[key];
450
+ where[key] = rest[key];
365
451
  });
366
452
 
367
- if (query.type === 'credit') {
453
+ if (rest.type === 'credit') {
368
454
  where.type = 'credit';
369
455
  }
370
456
  const findOptions: any = {
371
457
  where,
372
- order: getOrder(req.query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
458
+ order: getOrder(query, [['created_at', rest.o === 'asc' ? 'ASC' : 'DESC']]),
373
459
  offset: (page - 1) * pageSize,
374
460
  limit: pageSize,
375
461
  include: [{ model: Price, as: 'prices' }],
376
462
  distinct: true,
377
463
  };
378
464
 
379
- if (query.type !== 'credit' && query.meter_id) {
465
+ if (rest.type !== 'credit' && rest.meter_id) {
380
466
  findOptions.include = [
381
467
  {
382
468
  model: Price,
383
469
  as: 'prices',
384
470
  where: {
385
471
  recurring: {
386
- meter_id: query.meter_id,
472
+ meter_id: rest.meter_id,
387
473
  },
388
474
  },
389
475
  required: true,
@@ -391,14 +477,14 @@ router.get('/', auth, async (req, res) => {
391
477
  ];
392
478
  }
393
479
 
394
- if (query.type === 'credit' && query.meter_id) {
480
+ if (rest.type === 'credit' && rest.meter_id) {
395
481
  findOptions.include = [
396
482
  {
397
483
  model: Price,
398
484
  as: 'prices',
399
485
  where: {
400
486
  metadata: {
401
- meter_id: query.meter_id,
487
+ meter_id: rest.meter_id,
402
488
  },
403
489
  },
404
490
  required: true,
@@ -408,17 +494,18 @@ router.get('/', auth, async (req, res) => {
408
494
 
409
495
  const { rows: list, count } = await Product.findAndCountAll(findOptions);
410
496
 
411
- res.json({ count, list, paging: { page, pageSize } });
497
+ return c.json({ count, list, paging: { page, pageSize } });
412
498
  });
413
499
 
414
500
  // search products
501
+ // registered before /:id to prevent shadowing
415
502
  const searchSchema = createListParamSchema<{
416
503
  query: string;
417
504
  }>({
418
505
  query: Joi.string(),
419
506
  });
420
- router.get('/search', auth, async (req, res) => {
421
- const { page, pageSize, query, livemode, q } = await searchSchema.validateAsync(req.query, {
507
+ app.get('/search', auth, async (c) => {
508
+ const { page, pageSize, query, livemode, q } = await searchSchema.validateAsync(c.req.query(), {
422
509
  stripUnknown: false,
423
510
  allowUnknown: true,
424
511
  });
@@ -430,37 +517,37 @@ router.get('/search', auth, async (req, res) => {
430
517
 
431
518
  const { rows: list, count } = await Product.findAndCountAll({
432
519
  where,
433
- order: getOrder(req.query, [['created_at', 'DESC']]),
520
+ order: getOrder(c.req.query(), [['created_at', 'DESC']]),
434
521
  offset: (page - 1) * pageSize,
435
522
  limit: pageSize,
436
523
  include: [{ model: Price, as: 'prices', separate: true }],
437
524
  });
438
525
 
439
- res.json({ count, list });
526
+ return c.json({ count, list });
440
527
  });
441
528
 
442
529
  // get product detail
443
- router.get('/:id', async (req, res) => {
444
- const doc = await Product.expand(req.params.id as string);
530
+ app.get('/:id', async (c) => {
531
+ const doc = await Product.expand(c.req.param('id') as string);
445
532
  if (doc) {
446
- res.json(doc);
447
- } else {
448
- res.status(404).json(null);
533
+ return c.json(doc);
449
534
  }
535
+ return c.json(null, 404);
450
536
  });
451
537
 
452
538
  // update product
453
- router.put('/:id', auth, async (req, res) => {
454
- const product = await Product.findByPk(req.params.id);
539
+ app.put('/:id', auth, async (c) => {
540
+ const product = await Product.findByPk(c.req.param('id'));
455
541
 
456
542
  if (!product) {
457
- return res.status(404).json({ error: 'product not found' });
543
+ return c.json({ error: 'product not found' }, 404);
458
544
  }
459
545
  if (product.locked) {
460
- return res.status(403).json({ error: 'product locked' });
546
+ return c.json({ error: 'product locked' }, 403);
461
547
  }
462
548
 
463
- const updates: Partial<Product> = pick(req.body, [
549
+ const body = c.get('sanitizedBody') ?? {};
550
+ const updates: Partial<Product> = pick(body, [
464
551
  'status',
465
552
  'name',
466
553
  'description',
@@ -476,19 +563,19 @@ router.put('/:id', auth, async (req, res) => {
476
563
  'tax_code',
477
564
  ]);
478
565
 
479
- if (Array.isArray(req.body.vendor_config)) {
480
- const { error: vendorConfigError } = VendorConfigSchema.validate(req.body.vendor_config);
566
+ if (Array.isArray(body.vendor_config)) {
567
+ const { error: vendorConfigError } = VendorConfigSchema.validate(body.vendor_config);
481
568
  if (vendorConfigError) {
482
- return res.status(400).json({ error: `vendor_config validation failed: ${vendorConfigError.message}` });
569
+ return c.json({ error: `vendor_config validation failed: ${vendorConfigError.message}` }, 400);
483
570
  }
484
571
 
485
572
  const vendorConfigs = await ProductVendor.findAll({
486
573
  where: {
487
- id: req.body.vendor_config.map((x: any) => x.vendor_id),
574
+ id: body.vendor_config.map((x: any) => x.vendor_id),
488
575
  },
489
576
  });
490
577
 
491
- updates.vendor_config = req.body.vendor_config.map((config: any) => {
578
+ updates.vendor_config = body.vendor_config.map((config: any) => {
492
579
  const vendorConfig = vendorConfigs.find((x) => x.id === config.vendor_id);
493
580
  if (!vendorConfig) {
494
581
  throw new Error(`vendor ${config.vendor_id} not found`);
@@ -507,7 +594,7 @@ router.put('/:id', auth, async (req, res) => {
507
594
  }
508
595
  const { error } = ProductAndPriceSchema.validate(updates);
509
596
  if (error) {
510
- return res.status(400).json({ error: `Product update request invalid: ${error.message}` });
597
+ return c.json({ error: `Product update request invalid: ${error.message}` }, 400);
511
598
  }
512
599
  if (updates.metadata) {
513
600
  updates.metadata = formatMetadata(updates.metadata);
@@ -516,48 +603,48 @@ router.put('/:id', auth, async (req, res) => {
516
603
  logger.info('Product updated', {
517
604
  productId: product.id,
518
605
  updatedFields: Object.keys(updates),
519
- requestedBy: req.user?.did,
606
+ requestedBy: c.get('user')?.did,
520
607
  });
521
- return res.json(await Product.expand(req.params.id as string));
608
+ return c.json(await Product.expand(c.req.param('id') as string));
522
609
  });
523
610
 
524
611
  // archive
525
- router.put('/:id/archive', auth, async (req, res) => {
526
- const product = await Product.findByPk(req.params.id);
612
+ app.put('/:id/archive', auth, async (c) => {
613
+ const product = await Product.findByPk(c.req.param('id'));
527
614
 
528
615
  if (!product) {
529
- return res.status(404).json({ error: 'product not found' });
616
+ return c.json({ error: 'product not found' }, 404);
530
617
  }
531
618
  if (product.locked) {
532
- return res.status(403).json({ error: 'product locked' });
619
+ return c.json({ error: 'product locked' }, 403);
533
620
  }
534
621
 
535
622
  await product.update({ active: !product.active });
536
623
 
537
624
  // FIXME: deactivate payment-links, pricing-tables
538
- return res.json(await Product.expand(req.params.id as string));
625
+ return c.json(await Product.expand(c.req.param('id') as string));
539
626
  });
540
627
 
541
628
  // delete product
542
- router.delete('/:id', auth, async (req, res) => {
629
+ app.delete('/:id', auth, async (c) => {
543
630
  try {
544
- const product = await Product.findByPk(req.params.id);
631
+ const product = await Product.findByPk(c.req.param('id'));
545
632
 
546
633
  if (!product) {
547
- return res.status(404).json({ error: 'product not found' });
634
+ return c.json({ error: 'product not found' }, 404);
548
635
  }
549
636
  if (product.locked) {
550
- return res.status(403).json({ error: 'product locked' });
637
+ return c.json({ error: 'product locked' }, 403);
551
638
  }
552
639
 
553
640
  const prices = await Price.findAll({ where: { product_id: product.id } });
554
641
  for (const price of prices) {
555
642
  if (price.locked) {
556
- return res.status(403).json({ error: 'product have prices that is locked' });
643
+ return c.json({ error: 'product have prices that is locked' }, 403);
557
644
  }
558
645
  // eslint-disable-next-line no-await-in-loop
559
646
  if (await price.isUsed(false)) {
560
- return res.status(403).json({ error: 'product have prices that is used by other resources' });
647
+ return c.json({ error: 'product have prices that is used by other resources' }, 403);
561
648
  }
562
649
  }
563
650
 
@@ -565,87 +652,13 @@ router.delete('/:id', auth, async (req, res) => {
565
652
  await Price.destroy({ where: { product_id: product.id } });
566
653
  logger.info('Product and associated prices deleted', {
567
654
  productId: product.id,
568
- requestedBy: req.user?.did,
655
+ requestedBy: c.get('user')?.did,
569
656
  });
570
- return res.json(product);
657
+ return c.json(product);
571
658
  } catch (err) {
572
659
  logger.error('delete product error', err);
573
- return res.status(400).json({ error: err.message });
574
- }
575
- });
576
-
577
- // This is a dangerous operation, only allow to run with a special env variable
578
- // Only meant to be used in DID Domain
579
- router.post('/batch-price-update', auth, async (req, res) => {
580
- if (process.env.PAYMENT_CHANGE_LOCKED_PRICE !== '1') {
581
- return res.status(403).json({ error: 'forbidden' });
582
- }
583
-
584
- const dryRun = typeof req.body.dryRun === 'undefined' || req.body.dryRun !== '0';
585
- const factor = Number(req.body.factor);
586
- if (Number.isNaN(factor)) {
587
- return res.status(400).json({ error: 'factor is required' });
588
- }
589
- if (factor <= 0) {
590
- return res.status(400).json({ error: 'factor must be positive' });
591
- }
592
- if (!req.body.description) {
593
- return res.status(400).json({ error: 'description is required' });
594
- }
595
- if (!req.body.before) {
596
- return res.status(400).json({ error: 'before is required' });
597
- }
598
-
599
- try {
600
- const updated: any[] = [];
601
- const products = await Product.findAll({
602
- where: { active: false, livemode: !!req.livemode, created_at: { [Op.lt]: req.body.before } },
603
- });
604
- await Promise.all(
605
- products.map(async (product) => {
606
- if (!product.metadata?.domain) {
607
- return;
608
- }
609
-
610
- const prices = await Price.findAll({ where: { product_id: product.id } });
611
- for (const price of prices) {
612
- if (price.currency_id === req.baseCurrency.id) {
613
- const unit = new BN(price.unit_amount).div(new BN(factor)).toString();
614
- const options = cloneDeep(price.currency_options);
615
- const option = options.find((x) => x.currency_id === price.currency_id);
616
- if (option) {
617
- option.unit_amount = unit;
618
- }
619
-
620
- const metadata = price.metadata || {};
621
- metadata.update_history = [metadata.update_history, req.body.description].filter(Boolean).join(';');
622
-
623
- if (dryRun) {
624
- updated.push({ price: price.toJSON(), metadata, unit_amount: unit, currency_options: options });
625
- } else {
626
- // eslint-disable-next-line
627
- await Price.update(
628
- { metadata, unit_amount: unit, currency_options: options },
629
- { where: { id: price.id } }
630
- );
631
- updated.push(product.id);
632
- }
633
- }
634
- }
635
- })
636
- );
637
-
638
- logger.info('Batch price update completed', {
639
- updatedCount: updated.length,
640
- dryRun,
641
- factor,
642
- requestedBy: req.user?.did,
643
- });
644
- return res.json(updated);
645
- } catch (err) {
646
- logger.error('batch price update error', err);
647
- return res.status(400).json({ error: err.message });
660
+ return c.json({ error: (err as any).message }, 400);
648
661
  }
649
662
  });
650
663
 
651
- export default router;
664
+ export default app;