payment-kit 1.29.1 → 1.29.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/api/dev.ts +41 -2
  2. package/api/hono.d.ts +42 -0
  3. package/api/node-sqlite.d.ts +12 -0
  4. package/api/src/bootstrap.ts +47 -0
  5. package/api/src/crons/base.ts +3 -3
  6. package/api/src/crons/currency.ts +1 -1
  7. package/api/src/crons/index.ts +41 -37
  8. package/api/src/crons/metering-subscription-detection.ts +1 -1
  9. package/api/src/crons/overdue-detection.ts +2 -2
  10. package/api/src/crons/retry-pending-events.ts +6 -0
  11. package/api/src/crons/tenant-fanout.ts +82 -0
  12. package/api/src/host-node/did-connect-runtime-node.ts +33 -0
  13. package/api/src/host-node/serve-static-arc.ts +68 -0
  14. package/api/src/host-node/serve-static.ts +41 -0
  15. package/api/src/index.ts +22 -161
  16. package/api/src/integrations/app-store/client.ts +3 -4
  17. package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
  18. package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
  19. package/api/src/integrations/arcblock/token.ts +21 -7
  20. package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
  21. package/api/src/integrations/google-play/handlers/voided.ts +2 -2
  22. package/api/src/integrations/google-play/verify.ts +3 -2
  23. package/api/src/integrations/iap-reconcile.ts +3 -5
  24. package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
  25. package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
  26. package/api/src/libs/archive/query.ts +19 -0
  27. package/api/src/libs/audit.ts +61 -4
  28. package/api/src/libs/auth.ts +247 -47
  29. package/api/src/libs/context.ts +89 -1
  30. package/api/src/libs/currency.ts +2 -2
  31. package/api/src/libs/dayjs.ts +8 -2
  32. package/api/src/libs/did-connect/runtime-did-connect-js.ts +88 -0
  33. package/api/src/libs/did-connect/tenant-identity.ts +221 -0
  34. package/api/src/libs/drivers/auth-storage.ts +118 -0
  35. package/api/src/libs/drivers/cron.ts +264 -0
  36. package/api/src/libs/drivers/db.ts +170 -0
  37. package/api/src/libs/drivers/identity.ts +142 -0
  38. package/api/src/libs/drivers/index.ts +40 -0
  39. package/api/src/libs/drivers/locks.ts +226 -0
  40. package/api/src/libs/drivers/migrate-runner.ts +70 -0
  41. package/api/src/libs/drivers/queue.ts +104 -0
  42. package/api/src/libs/drivers/secrets.ts +194 -0
  43. package/api/src/libs/env.ts +170 -54
  44. package/api/src/libs/exchange-rate/service.ts +7 -6
  45. package/api/src/libs/http-fetch-adapter.ts +60 -0
  46. package/api/src/libs/invoice.ts +1 -1
  47. package/api/src/libs/lock.ts +51 -47
  48. package/api/src/libs/logger.ts +48 -8
  49. package/api/src/libs/notification/index.ts +1 -1
  50. package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
  51. package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
  52. package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
  53. package/api/src/libs/overdraft-protection.ts +1 -1
  54. package/api/src/libs/payout.ts +1 -1
  55. package/api/src/libs/queue/index.ts +271 -52
  56. package/api/src/libs/queue/runtime.ts +175 -0
  57. package/api/src/libs/resource.ts +3 -3
  58. package/api/src/libs/secrets.ts +38 -0
  59. package/api/src/libs/session.ts +3 -2
  60. package/api/src/libs/subscription.ts +5 -5
  61. package/api/src/libs/tenant.ts +92 -0
  62. package/api/src/libs/url.ts +3 -3
  63. package/api/src/libs/util.ts +21 -13
  64. package/api/src/middlewares/hono/cdn.ts +63 -0
  65. package/api/src/middlewares/hono/context.ts +80 -0
  66. package/api/src/middlewares/hono/csrf.ts +83 -0
  67. package/api/src/middlewares/hono/fallback.ts +194 -0
  68. package/api/src/middlewares/hono/pipeline.ts +73 -0
  69. package/api/src/middlewares/hono/resource-mount.ts +42 -0
  70. package/api/src/middlewares/hono/resource.ts +63 -0
  71. package/api/src/middlewares/hono/security.ts +209 -0
  72. package/api/src/middlewares/hono/session.ts +114 -0
  73. package/api/src/middlewares/hono/xss.ts +61 -0
  74. package/api/src/queues/auto-recharge.ts +12 -10
  75. package/api/src/queues/checkout-session.ts +38 -21
  76. package/api/src/queues/credit-consume.ts +40 -36
  77. package/api/src/queues/credit-grant.ts +25 -18
  78. package/api/src/queues/credit-reconciliation.ts +7 -5
  79. package/api/src/queues/discount-status.ts +9 -6
  80. package/api/src/queues/event.ts +41 -11
  81. package/api/src/queues/exchange-rate-health.ts +49 -30
  82. package/api/src/queues/invoice.ts +18 -15
  83. package/api/src/queues/notification.ts +14 -7
  84. package/api/src/queues/payment.ts +64 -37
  85. package/api/src/queues/payout.ts +37 -21
  86. package/api/src/queues/refund.ts +36 -18
  87. package/api/src/queues/subscription.ts +83 -53
  88. package/api/src/queues/token-transfer.ts +15 -10
  89. package/api/src/queues/usage-record.ts +8 -5
  90. package/api/src/queues/vendors/commission.ts +7 -5
  91. package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
  92. package/api/src/queues/vendors/fulfillment.ts +4 -2
  93. package/api/src/queues/vendors/return-processor.ts +5 -3
  94. package/api/src/queues/vendors/return-scanner.ts +5 -4
  95. package/api/src/queues/vendors/status-check.ts +10 -7
  96. package/api/src/queues/webhook.ts +60 -32
  97. package/api/src/routes/connect/shared.ts +1 -2
  98. package/api/src/routes/connect/subscribe.ts +3 -3
  99. package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
  100. package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
  101. package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
  102. package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
  103. package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
  104. package/api/src/routes/hono/credit-tokens.ts +43 -0
  105. package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
  106. package/api/src/routes/{customers.ts → hono/customers.ts} +199 -224
  107. package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
  108. package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
  109. package/api/src/routes/{events.ts → hono/events.ts} +107 -71
  110. package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
  111. package/api/src/routes/hono/exchange-rates.ts +77 -0
  112. package/api/src/routes/hono/index.ts +115 -0
  113. package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
  114. package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
  115. package/api/src/routes/hono/integrations/stripe.ts +74 -0
  116. package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
  117. package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
  118. package/api/src/routes/hono/meters.ts +288 -0
  119. package/api/src/routes/hono/passports.ts +73 -0
  120. package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
  121. package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
  122. package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
  123. package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
  124. package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
  125. package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
  126. package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
  127. package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
  128. package/api/src/routes/{products.ts → hono/products.ts} +172 -159
  129. package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
  130. package/api/src/routes/hono/redirect.ts +24 -0
  131. package/api/src/routes/{refunds.ts → hono/refunds.ts} +98 -83
  132. package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
  133. package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
  134. package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
  135. package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
  136. package/api/src/routes/hono/tool.ts +69 -0
  137. package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
  138. package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
  139. package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
  140. package/api/src/routes/hono/webhook-endpoints.ts +126 -0
  141. package/api/src/service.ts +814 -0
  142. package/api/src/store/migrations/20230911-seeding.ts +2 -1
  143. package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
  144. package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
  145. package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
  146. package/api/src/store/models/auto-recharge-config.ts +22 -10
  147. package/api/src/store/models/checkout-session.ts +15 -14
  148. package/api/src/store/models/coupon.ts +29 -20
  149. package/api/src/store/models/credit-grant.ts +38 -29
  150. package/api/src/store/models/credit-transaction.ts +32 -21
  151. package/api/src/store/models/customer.ts +19 -17
  152. package/api/src/store/models/discount.ts +11 -2
  153. package/api/src/store/models/entitlement-grant.ts +21 -9
  154. package/api/src/store/models/entitlement-product.ts +21 -9
  155. package/api/src/store/models/entitlement.ts +19 -10
  156. package/api/src/store/models/event.ts +18 -9
  157. package/api/src/store/models/exchange-rate-provider.ts +17 -4
  158. package/api/src/store/models/invoice-item.ts +18 -9
  159. package/api/src/store/models/invoice.ts +16 -8
  160. package/api/src/store/models/meter-event.ts +27 -9
  161. package/api/src/store/models/meter.ts +31 -22
  162. package/api/src/store/models/payment-currency.ts +25 -8
  163. package/api/src/store/models/payment-intent.ts +15 -6
  164. package/api/src/store/models/payment-link.ts +15 -6
  165. package/api/src/store/models/payment-method.ts +38 -22
  166. package/api/src/store/models/payment-stat.ts +18 -9
  167. package/api/src/store/models/payout.ts +15 -6
  168. package/api/src/store/models/price-quote.ts +17 -8
  169. package/api/src/store/models/price.ts +24 -12
  170. package/api/src/store/models/pricing-table.ts +29 -20
  171. package/api/src/store/models/product-vendor.ts +20 -10
  172. package/api/src/store/models/product.ts +15 -6
  173. package/api/src/store/models/promotion-code.ts +14 -6
  174. package/api/src/store/models/refund.ts +15 -6
  175. package/api/src/store/models/revenue-snapshot.ts +21 -9
  176. package/api/src/store/models/setting.ts +18 -9
  177. package/api/src/store/models/setup-intent.ts +36 -27
  178. package/api/src/store/models/subscription-item.ts +21 -9
  179. package/api/src/store/models/subscription-schedule.ts +21 -9
  180. package/api/src/store/models/subscription.ts +21 -10
  181. package/api/src/store/models/tax-rate.ts +29 -21
  182. package/api/src/store/models/usage-record.ts +11 -2
  183. package/api/src/store/models/webhook-attempt.ts +18 -9
  184. package/api/src/store/models/webhook-endpoint.ts +18 -9
  185. package/api/src/store/scoped-core.ts +55 -0
  186. package/api/src/store/scoped.ts +247 -0
  187. package/api/src/store/sequelize.ts +82 -23
  188. package/api/src/store/sql-migrations.ts +20 -0
  189. package/api/src/store/tenant-backfill.ts +260 -0
  190. package/api/src/store/tenant-model.ts +124 -0
  191. package/api/src/store/tenant-tables.ts +50 -0
  192. package/api/tests/bootstrap/bootstrap.spec.ts +162 -0
  193. package/api/tests/crons/tenant-fanout.spec.ts +158 -0
  194. package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
  195. package/api/tests/fixtures/bare-query-violation.ts +13 -0
  196. package/api/tests/fixtures/core-env-violation.ts +10 -0
  197. package/api/tests/fixtures/host-read-violation.ts +19 -0
  198. package/api/tests/fixtures/tenants.ts +4 -0
  199. package/api/tests/integrations/iap-tenant.spec.ts +284 -0
  200. package/api/tests/libs/archive-query.spec.ts +26 -0
  201. package/api/tests/libs/audit-tenant.spec.ts +153 -0
  202. package/api/tests/libs/context.spec.ts +204 -0
  203. package/api/tests/libs/core-config.spec.ts +115 -0
  204. package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
  205. package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
  206. package/api/tests/libs/did-connect-runtime-js.spec.ts +98 -0
  207. package/api/tests/libs/did-connect-tenant-identity.spec.ts +159 -0
  208. package/api/tests/libs/lock-tenant.spec.ts +66 -0
  209. package/api/tests/libs/scoped.spec.ts +222 -0
  210. package/api/tests/libs/secrets-facade.spec.ts +52 -0
  211. package/api/tests/libs/service-host.spec.ts +37 -0
  212. package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
  213. package/api/tests/libs/tenant-middleware.spec.ts +42 -0
  214. package/api/tests/libs/tenant-scanner.spec.ts +120 -0
  215. package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
  216. package/api/tests/middlewares/hono/context.spec.ts +113 -0
  217. package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
  218. package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
  219. package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
  220. package/api/tests/middlewares/hono/security.spec.ts +181 -0
  221. package/api/tests/middlewares/hono/session.spec.ts +42 -0
  222. package/api/tests/middlewares/hono/xss.spec.ts +81 -0
  223. package/api/tests/models/tenant-backfill.spec.ts +287 -0
  224. package/api/tests/models/tenant-columns-model.spec.ts +46 -0
  225. package/api/tests/models/tenant-columns.spec.ts +161 -0
  226. package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
  227. package/api/tests/queues/credit-consume.spec.ts +8 -1
  228. package/api/tests/queues/event-tenant.spec.ts +292 -0
  229. package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
  230. package/api/tests/queues/queue-parity.spec.ts +249 -0
  231. package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
  232. package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
  233. package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
  234. package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
  235. package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
  236. package/api/tests/service/collapse.spec.ts +96 -0
  237. package/api/tests/service/didconnect-storage-slot.spec.ts +60 -0
  238. package/api/tests/service/fail-closed-http.spec.ts +79 -0
  239. package/api/tests/service/static-arc-handler.spec.ts +101 -0
  240. package/api/tests/service/static-externalized.spec.ts +48 -0
  241. package/api/tests/store/tenant-crosscut.spec.ts +202 -0
  242. package/api/tests/store/tenant-model-spike.spec.ts +177 -0
  243. package/api/tests/store/tenant-model.spec.ts +162 -0
  244. package/api/tests/store/tenant-residual.spec.ts +196 -0
  245. package/api/third.d.ts +4 -0
  246. package/blocklet.yml +1 -1
  247. package/cloudflare/MIGRATION-RUNBOOK.md +3 -8
  248. package/cloudflare/README.md +34 -27
  249. package/cloudflare/STAGING-MIGRATION-GUIDE.md +3 -15
  250. package/cloudflare/build.ts +33 -13
  251. package/cloudflare/cf-adapter.ts +419 -0
  252. package/cloudflare/did-connect-runtime.ts +96 -0
  253. package/cloudflare/did-connect-token-storage.ts +151 -0
  254. package/cloudflare/esbuild-cf-config.cjs +407 -0
  255. package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
  256. package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
  257. package/cloudflare/migrations/0008_schema_parity.sql +16 -0
  258. package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
  259. package/cloudflare/queue-runtime-mode.ts +13 -0
  260. package/cloudflare/run-build.js +33 -403
  261. package/cloudflare/scripts/cf-package-import-probe.mjs +90 -0
  262. package/cloudflare/scripts/didconnect-mock-smoke.mjs +140 -0
  263. package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
  264. package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
  265. package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
  266. package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
  267. package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
  268. package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
  269. package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +16 -1
  270. package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +18 -3
  271. package/cloudflare/shims/cron.ts +38 -158
  272. package/cloudflare/shims/events.ts +124 -0
  273. package/cloudflare/shims/fastq.ts +15 -1
  274. package/cloudflare/shims/nedb-storage.ts +16 -8
  275. package/cloudflare/shims/xss.ts +8 -0
  276. package/cloudflare/tenant-middleware.ts +36 -0
  277. package/cloudflare/tests/cf-adapter.spec.ts +244 -0
  278. package/cloudflare/tests/did-connect-token-storage.spec.ts +105 -0
  279. package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
  280. package/cloudflare/tests/worker-handler-gate.spec.ts +69 -0
  281. package/cloudflare/vite.config.ts +53 -45
  282. package/cloudflare/worker.ts +261 -448
  283. package/cloudflare/wrangler.json +0 -6
  284. package/cloudflare/wrangler.jsonc +0 -6
  285. package/cloudflare/wrangler.local-e2e.jsonc +25 -0
  286. package/cloudflare/wrangler.staging.json +0 -6
  287. package/jest.config.js +3 -1
  288. package/package.json +33 -38
  289. package/scripts/bootstrap-inject.ts +166 -0
  290. package/scripts/core-env-whitelist.json +1 -0
  291. package/scripts/e2e-12b-runtime.ts +149 -0
  292. package/scripts/e2e-core-config.ts +125 -0
  293. package/scripts/e2e-d1-tenancy.ts +116 -0
  294. package/scripts/e2e-d2-cron-queue.ts +139 -0
  295. package/scripts/e2e-d3-embedded-multi.ts +171 -0
  296. package/scripts/e2e-hono-s2.ts +125 -0
  297. package/scripts/e2e-hono-s3e.ts +135 -0
  298. package/scripts/e2e-hono-s4.ts +114 -0
  299. package/scripts/e2e-migration-contract.ts +100 -0
  300. package/scripts/e2e-s0.ts +61 -0
  301. package/scripts/e2e-s1.ts +107 -0
  302. package/scripts/e2e-s2.ts +178 -0
  303. package/scripts/e2e-s3.ts +110 -0
  304. package/scripts/e2e-s4.ts +191 -0
  305. package/scripts/e2e-s5.ts +139 -0
  306. package/scripts/e2e-s6.ts +127 -0
  307. package/scripts/e2e-tenant-model.ts +119 -0
  308. package/scripts/e2e-tenant-worker.ts +199 -0
  309. package/scripts/gen-sql-migrations.js +46 -0
  310. package/scripts/phase8-codemod.js +219 -0
  311. package/scripts/phase9a-env-getters-codemod.js +82 -0
  312. package/scripts/scan-core-env.js +109 -0
  313. package/scripts/scan-tenant-queries.js +235 -0
  314. package/scripts/schema-drift-guard.ts +210 -0
  315. package/scripts/tenant-scan-whitelist.json +1 -0
  316. package/src/app.tsx +2 -1
  317. package/src/env.d.ts +13 -1
  318. package/src/libs/service-host.ts +13 -0
  319. package/tsconfig.json +1 -1
  320. package/vite.arc.config.ts +159 -0
  321. package/api/src/libs/did-space.ts +0 -235
  322. package/api/src/libs/middleware.ts +0 -50
  323. package/api/src/libs/security.ts +0 -192
  324. package/api/src/queues/space.ts +0 -662
  325. package/api/src/routes/credit-tokens.ts +0 -38
  326. package/api/src/routes/exchange-rates.ts +0 -87
  327. package/api/src/routes/index.ts +0 -142
  328. package/api/src/routes/integrations/stripe.ts +0 -61
  329. package/api/src/routes/meters.ts +0 -274
  330. package/api/src/routes/passports.ts +0 -68
  331. package/api/src/routes/redirect.ts +0 -20
  332. package/api/src/routes/tool.ts +0 -65
  333. package/api/src/routes/webhook-endpoints.ts +0 -126
  334. package/api/tests/routes/credit-grants.spec.ts +0 -1261
  335. package/cloudflare/did-connect-auth.ts +0 -527
  336. package/cloudflare/shims/did-space-js.ts +0 -17
  337. package/cloudflare/shims/did-space.ts +0 -11
  338. package/cloudflare/shims/express-compat/index.ts +0 -80
  339. package/cloudflare/shims/express-compat/types.ts +0 -41
  340. package/cloudflare/shims/lock.ts +0 -115
  341. package/cloudflare/shims/queue.ts +0 -611
  342. package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
  343. package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
@@ -1,11 +1,19 @@
1
1
  import { BN, fromUnitToToken } from '@ocap/util';
2
2
  import { Op } from 'sequelize';
3
3
  import pAll from 'p-all';
4
+ import {
5
+ isCfWorker,
6
+ creditLowBalanceThresholdPercentage,
7
+ creditBatchSize,
8
+ creditBatchWindowMs,
9
+ creditQueueConcurrency,
10
+ } from '../libs/env';
11
+ import { systemFindAll, systemFindByPk, systemFindOne } from '../store/scoped';
4
12
 
5
13
  import { getLock } from '../libs/lock';
6
14
  import logger from '../libs/logger';
7
- import createQueue from '../libs/queue';
8
- import { createEvent } from '../libs/audit';
15
+ import createQueue, { assertJobObjectTenant } from '../libs/queue';
16
+ import { createEvent, reportAuditFailure } from '../libs/audit';
9
17
  import { MeterEvent, CreditGrant, CreditTransaction, Customer, Subscription, TMeterExpanded } from '../store/models';
10
18
  import { getCachedMeterExpanded } from '../libs/reference-cache';
11
19
 
@@ -20,7 +28,7 @@ import { addTokenTransferJob } from './token-transfer';
20
28
  // In Blocklet Server (no Queue ops limit), use the full MAX_RETRY_COUNT.
21
29
  // After max retries, mark as requires_action — retryFailedEventsForCustomer()
22
30
  // picks them up when credit is granted.
23
- const CREDIT_MAX_RETRY = (globalThis as any).__CF_ENV__ ? 5 : MAX_RETRY_COUNT;
31
+ const CREDIT_MAX_RETRY = isCfWorker() ? 5 : MAX_RETRY_COUNT;
24
32
 
25
33
  type CreditConsumptionJob = {
26
34
  meterEventId: string;
@@ -82,7 +90,7 @@ async function checkLowBalance(
82
90
  if (totalCreditAmountBn.lte(new BN(0))) return;
83
91
  const remainingAmountBn = new BN(remainingBalance);
84
92
  // Get threshold percentage from env var, default to 10%
85
- const thresholdPercentage = parseInt(process.env.CREDIT_LOW_BALANCE_THRESHOLD_PERCENTAGE || '10', 10);
93
+ const thresholdPercentage = creditLowBalanceThresholdPercentage();
86
94
  const threshold = totalCreditAmountBn.mul(new BN(thresholdPercentage)).div(new BN(100));
87
95
  if (remainingAmountBn.gt(new BN(0)) && remainingAmountBn.lte(threshold)) {
88
96
  const percentage = remainingAmountBn.mul(new BN(100)).div(totalCreditAmountBn).toString();
@@ -94,7 +102,7 @@ async function checkLowBalance(
94
102
  percentage,
95
103
  subscription_id: context.subscription?.id,
96
104
  },
97
- }).catch(console.error);
105
+ }).catch(reportAuditFailure);
98
106
  }
99
107
  } catch (error: any) {
100
108
  logger.error('Failed to check low balance', {
@@ -118,7 +126,7 @@ async function loadReferenceData(
118
126
 
119
127
  const [meter, customer] = await Promise.all([
120
128
  getCachedMeterExpanded(meterEvent.event_name) as Promise<TMeterExpanded | null>,
121
- Customer.findByPk(customerId),
129
+ systemFindByPk(Customer, customerId),
122
130
  ]);
123
131
 
124
132
  if (!meter) {
@@ -163,7 +171,7 @@ async function consumeAvailableCredits(
163
171
  const currencyId = context.meter.currency_id!;
164
172
  const meterEventId = context.meterEvent.id;
165
173
 
166
- const existingTransactions = await CreditTransaction.findAll({
174
+ const existingTransactions = await systemFindAll(CreditTransaction, {
167
175
  where: {
168
176
  source: meterEventId,
169
177
  },
@@ -300,7 +308,7 @@ async function handlePostConsumptionEvents(
300
308
  currency_id: currencyId,
301
309
  subscription_id: context.subscription?.id,
302
310
  },
303
- }).catch(console.error);
311
+ }).catch(reportAuditFailure);
304
312
  }
305
313
 
306
314
  // 如果有关联订阅且订阅活跃,将其标记为逾期
@@ -338,7 +346,7 @@ async function handlePostConsumptionEvents(
338
346
  currency_id: currencyId,
339
347
  subscription_id: context.subscription?.id,
340
348
  },
341
- }).catch(console.error);
349
+ }).catch(reportAuditFailure);
342
350
  }
343
351
 
344
352
  if (!insufficientTriggered) {
@@ -479,7 +487,7 @@ async function createCreditTransaction(
479
487
  });
480
488
 
481
489
  // 重新查询已存在的 transaction
482
- const duplicateTransaction = await CreditTransaction.findOne({
490
+ const duplicateTransaction = await systemFindOne(CreditTransaction, {
483
491
  where: {
484
492
  source: meterEventId,
485
493
  credit_grant_id: creditGrantId,
@@ -508,11 +516,12 @@ export async function handleCreditConsumption(job: CreditConsumptionJob) {
508
516
  logger.info('Starting credit consumption job', { meterEventId });
509
517
 
510
518
  // Pre-check before acquiring lock
511
- const preCheckEvent = await MeterEvent.findByPk(meterEventId);
519
+ const preCheckEvent = await systemFindByPk(MeterEvent, meterEventId);
512
520
  if (!preCheckEvent) {
513
521
  logger.warn('Skipping credit consumption job: MeterEvent not found', { meterEventId });
514
522
  return;
515
523
  }
524
+ assertJobObjectTenant(preCheckEvent);
516
525
  if (preCheckEvent.status === 'completed' || preCheckEvent.status === 'canceled') {
517
526
  logger.info('Skipping credit consumption job: MeterEvent already processed', {
518
527
  meterEventId,
@@ -554,8 +563,8 @@ export async function handleCreditConsumption(job: CreditConsumptionJob) {
554
563
 
555
564
  // Fresh loads inside lock for consistency
556
565
  const [freshEvent, freshSubscription] = await Promise.all([
557
- MeterEvent.findByPk(meterEventId),
558
- context._subscriptionId ? Subscription.findByPk(context._subscriptionId) : Promise.resolve(null),
566
+ systemFindByPk(MeterEvent, meterEventId),
567
+ context._subscriptionId ? systemFindByPk(Subscription, context._subscriptionId) : Promise.resolve(null),
559
568
  ]);
560
569
  if (!freshEvent) {
561
570
  logger.warn('MeterEvent disappeared after lock acquired', { meterEventId });
@@ -691,7 +700,7 @@ export async function handleCreditConsumption(job: CreditConsumptionJob) {
691
700
 
692
701
  // Handle retry logic with more robust error handling
693
702
  try {
694
- const meterEvent = await MeterEvent.findByPk(meterEventId);
703
+ const meterEvent = await systemFindByPk(MeterEvent, meterEventId);
695
704
  if (meterEvent && !['completed', 'canceled', 'requires_action'].includes(meterEvent.status)) {
696
705
  const attemptCount = meterEvent.attempt_count + 1;
697
706
  const nonRetryable = isNonRetryableCreditError(error);
@@ -762,9 +771,6 @@ export async function handleCreditConsumption(job: CreditConsumptionJob) {
762
771
  // ============================================================================
763
772
  // Batch credit consumption
764
773
  // ============================================================================
765
- const CREDIT_BATCH_SIZE = Math.max(1, parseInt(process.env.CREDIT_BATCH_SIZE || '50', 10));
766
- const CREDIT_BATCH_WINDOW_MS = Math.max(10, parseInt(process.env.CREDIT_BATCH_WINDOW_MS || '3000', 10));
767
-
768
774
  type PendingBatch = { eventIds: string[]; timer: NodeJS.Timeout | null };
769
775
  const pendingBatches = new Map<string, PendingBatch>();
770
776
  // Track in-flight batch jobs per key to avoid head-of-line blocking.
@@ -785,13 +791,13 @@ function addToBatch(customerId: string, eventName: string, meterEventId: string,
785
791
 
786
792
  batch.eventIds.push(meterEventId);
787
793
 
788
- if (batch.eventIds.length >= CREDIT_BATCH_SIZE) {
794
+ if (batch.eventIds.length >= creditBatchSize()) {
789
795
  flushBatch(key);
790
796
  return;
791
797
  }
792
798
 
793
799
  if (!batch.timer) {
794
- batch.timer = setTimeout(() => flushBatch(key), CREDIT_BATCH_WINDOW_MS);
800
+ batch.timer = setTimeout(() => flushBatch(key), creditBatchWindowMs());
795
801
  }
796
802
  }
797
803
 
@@ -864,7 +870,7 @@ async function handleBatchCreditConsumptionInner(meterEventIds: string[], batchS
864
870
  // ==========================================
865
871
  // Pre-check: filter already-processed events
866
872
  // ==========================================
867
- const preCheckEvents = await MeterEvent.findAll({
873
+ const preCheckEvents = await systemFindAll(MeterEvent, {
868
874
  where: { id: { [Op.in]: meterEventIds } },
869
875
  });
870
876
 
@@ -894,7 +900,7 @@ async function handleBatchCreditConsumptionInner(meterEventIds: string[], batchS
894
900
  // ==========================================
895
901
  const [meter, customer] = await Promise.all([
896
902
  getCachedMeterExpanded(eventName) as Promise<TMeterExpanded | null>,
897
- Customer.findByPk(customerId),
903
+ systemFindByPk(Customer, customerId),
898
904
  ]);
899
905
 
900
906
  if (!meter || !meter.currency_id || !customer) {
@@ -925,7 +931,7 @@ async function handleBatchCreditConsumptionInner(meterEventIds: string[], batchS
925
931
  // Fresh read inside lock
926
932
  // ==========================================
927
933
  const processableIds = processableEvents.map((e) => e.id);
928
- const freshEvents = await MeterEvent.findAll({
934
+ const freshEvents = await systemFindAll(MeterEvent, {
929
935
  where: { id: { [Op.in]: processableIds } },
930
936
  });
931
937
 
@@ -940,7 +946,7 @@ async function handleBatchCreditConsumptionInner(meterEventIds: string[], batchS
940
946
  let priceIds: string[] | undefined;
941
947
 
942
948
  if (subscriptionId) {
943
- subscription = await Subscription.findByPk(subscriptionId);
949
+ subscription = await systemFindByPk(Subscription, subscriptionId);
944
950
  if (!subscription) {
945
951
  logger.warn('Batch: Subscription not found inside lock, skipping', {
946
952
  customerId,
@@ -965,7 +971,7 @@ async function handleBatchCreditConsumptionInner(meterEventIds: string[], batchS
965
971
  // Batch idempotency check
966
972
  // ==========================================
967
973
  const allEventIds = freshProcessable.map((e) => e.id);
968
- const existingTransactions = await CreditTransaction.findAll({
974
+ const existingTransactions = await systemFindAll(CreditTransaction, {
969
975
  where: { source: { [Op.in]: allEventIds } },
970
976
  });
971
977
  const txBySource = new Map<string, CreditTransaction[]>();
@@ -1249,11 +1255,6 @@ async function handleBatchCreditConsumptionInner(meterEventIds: string[], batchS
1249
1255
  // Queue setup
1250
1256
  // ============================================================================
1251
1257
 
1252
- const creditQueueConcurrency = Math.max(
1253
- 1,
1254
- Math.min(20, parseInt(process.env.CREDIT_QUEUE_CONCURRENCY || '5', 10) || 5)
1255
- );
1256
-
1257
1258
  export const creditQueue = createQueue<CreditConsumptionJob | BatchCreditConsumptionJob>({
1258
1259
  name: 'credit-consumption',
1259
1260
  onJob: (job) => {
@@ -1263,7 +1264,7 @@ export const creditQueue = createQueue<CreditConsumptionJob | BatchCreditConsump
1263
1264
  return handleCreditConsumption(job as CreditConsumptionJob);
1264
1265
  },
1265
1266
  options: {
1266
- concurrency: creditQueueConcurrency,
1267
+ concurrency: creditQueueConcurrency(),
1267
1268
  maxRetries: 0,
1268
1269
  enableScheduledJob: true,
1269
1270
  },
@@ -1308,11 +1309,12 @@ const addCreditConsumptionJob = async (
1308
1309
  }
1309
1310
 
1310
1311
  if (!options.skipStatusCheck) {
1311
- const meterEvent = await MeterEvent.findByPk(meterEventId);
1312
+ const meterEvent = await systemFindByPk(MeterEvent, meterEventId);
1312
1313
  if (!meterEvent) {
1313
1314
  logger.warn('Cannot add credit consumption job: MeterEvent not found', { meterEventId });
1314
1315
  return;
1315
1316
  }
1317
+ assertJobObjectTenant(meterEvent);
1316
1318
 
1317
1319
  if (meterEvent.status === 'completed' || meterEvent.status === 'canceled') {
1318
1320
  logger.debug('Skipping credit consumption job: MeterEvent already processed', {
@@ -1342,7 +1344,7 @@ creditQueue.on('retry', ({ id, job }) => {
1342
1344
  });
1343
1345
 
1344
1346
  export async function startCreditConsumeQueue(): Promise<void> {
1345
- const lock = getLock('startCreditConsumeQueue');
1347
+ const lock = getLock('startCreditConsumeQueue', { scope: 'global' });
1346
1348
  if (lock.locked) {
1347
1349
  return;
1348
1350
  }
@@ -1359,7 +1361,7 @@ export async function startCreditConsumeQueue(): Promise<void> {
1359
1361
 
1360
1362
  do {
1361
1363
  // eslint-disable-next-line no-await-in-loop
1362
- batchEvents = await MeterEvent.findAll({
1364
+ batchEvents = await systemFindAll(MeterEvent, {
1363
1365
  where: {
1364
1366
  status: ['pending', 'requires_capture', 'processing'],
1365
1367
  },
@@ -1491,13 +1493,14 @@ events.on('customer.credit_grant.granted', async (creditGrant: CreditGrant) => {
1491
1493
  });
1492
1494
 
1493
1495
  async function retryFailedEventsForCustomer(creditGrant: CreditGrant): Promise<void> {
1494
- const grant = await CreditGrant.findByPk(creditGrant.id);
1496
+ const grant = await systemFindByPk(CreditGrant, creditGrant.id);
1495
1497
  if (!grant) {
1496
1498
  logger.error('Credit grant not found', {
1497
1499
  creditGrantId: creditGrant.id,
1498
1500
  });
1499
1501
  return;
1500
1502
  }
1503
+ assertJobObjectTenant(grant);
1501
1504
 
1502
1505
  const customerId = grant.customer_id;
1503
1506
  const currencyId = grant.currency_id;
@@ -1583,8 +1586,9 @@ async function retryFailedEventsForCustomer(creditGrant: CreditGrant): Promise<v
1583
1586
 
1584
1587
  let chunkIndex = 0;
1585
1588
  for (const [, subEventIds] of bySubscription) {
1586
- for (let i = 0; i < subEventIds.length; i += CREDIT_BATCH_SIZE) {
1587
- const chunk = subEventIds.slice(i, i + CREDIT_BATCH_SIZE);
1589
+ const batchSize = creditBatchSize();
1590
+ for (let i = 0; i < subEventIds.length; i += batchSize) {
1591
+ const chunk = subEventIds.slice(i, i + batchSize);
1588
1592
  creditQueue.push({
1589
1593
  id: `retry-batch-${customerId}-${Date.now()}-${chunkIndex++}`,
1590
1594
  job: { meterEventIds: chunk } as any,
@@ -4,7 +4,8 @@ import { Op } from 'sequelize';
4
4
  import { BN, fromTokenToUnit, fromUnitToToken } from '@ocap/util';
5
5
  import pAll from 'p-all';
6
6
  import dayjs from '../libs/dayjs';
7
- import createQueue from '../libs/queue';
7
+ import createQueue, { assertJobObjectTenant } from '../libs/queue';
8
+ import { systemFindAll, systemFindByPk } from '../store/scoped';
8
9
  import {
9
10
  AutoRechargeConfig,
10
11
  CreditGrant,
@@ -48,7 +49,7 @@ type CreditGrantJob =
48
49
  * Non on-chain credits finish immediately; on-chain credits wait for mint success
49
50
  */
50
51
  async function activateGrant(creditGrant: CreditGrant) {
51
- const paymentCurrency = await PaymentCurrency.findByPk(creditGrant.currency_id);
52
+ const paymentCurrency = await systemFindByPk(PaymentCurrency, creditGrant.currency_id);
52
53
  const isOnChainCredit = paymentCurrency && PaymentCurrency.isOnChainCredit(paymentCurrency);
53
54
 
54
55
  logger.info('Activating credit grant', {
@@ -76,10 +77,11 @@ async function activateGrant(creditGrant: CreditGrant) {
76
77
  }
77
78
 
78
79
  // Verify customer
79
- const customer = await Customer.findByPk(creditGrant.customer_id);
80
+ const customer = await systemFindByPk(Customer, creditGrant.customer_id);
80
81
  if (!customer) {
81
82
  throw new Error(`Customer DID not found for credit grant ${creditGrant.id}`);
82
83
  }
84
+ assertJobObjectTenant(customer);
83
85
 
84
86
  const customerState = await getAccountState(paymentCurrency, customer.did);
85
87
 
@@ -161,7 +163,7 @@ export async function expireGrant(creditGrant: CreditGrant) {
161
163
  return;
162
164
  }
163
165
 
164
- const paymentCurrency = await PaymentCurrency.findByPk(creditGrant.currency_id);
166
+ const paymentCurrency = await systemFindByPk(PaymentCurrency, creditGrant.currency_id);
165
167
  if (!paymentCurrency) {
166
168
  logger.error('Payment currency not found for credit grant transfer', {
167
169
  creditGrantId: creditGrant.id,
@@ -171,9 +173,10 @@ export async function expireGrant(creditGrant: CreditGrant) {
171
173
  await creditGrant.update({ status: 'expired' });
172
174
  return;
173
175
  }
176
+ assertJobObjectTenant(paymentCurrency);
174
177
 
175
178
  // Get customer DID
176
- const customer = await Customer.findByPk(creditGrant.customer_id);
179
+ const customer = await systemFindByPk(Customer, creditGrant.customer_id);
177
180
  if (!customer?.did) {
178
181
  logger.error('Customer DID not found for credit grant transfer', {
179
182
  creditGrantId: creditGrant.id,
@@ -280,11 +283,12 @@ const handleCreditGrantJob = async (job: CreditGrantJob) => {
280
283
 
281
284
  const { creditGrantId, action } = job as { creditGrantId: string; action: 'activate' | 'expire' };
282
285
 
283
- const creditGrant = await CreditGrant.findByPk(creditGrantId);
286
+ const creditGrant = await systemFindByPk(CreditGrant, creditGrantId);
284
287
  if (!creditGrant) {
285
288
  logger.error('Credit grant not found', { creditGrantId });
286
289
  return;
287
290
  }
291
+ assertJobObjectTenant(creditGrant);
288
292
 
289
293
  const now = dayjs().unix();
290
294
 
@@ -411,7 +415,7 @@ export async function scheduleCreditGrantJobs(creditGrant: CreditGrant) {
411
415
  export const startCreditGrantQueue = async () => {
412
416
  logger.info('Starting credit grant queue...');
413
417
 
414
- const grantsToSchedule = await CreditGrant.findAll({
418
+ const grantsToSchedule = await systemFindAll(CreditGrant, {
415
419
  where: {
416
420
  [Op.or]: [
417
421
  { status: 'pending' },
@@ -424,7 +428,7 @@ export const startCreditGrantQueue = async () => {
424
428
  order: [['created_at', 'ASC']],
425
429
  });
426
430
 
427
- const invoicesToSchedule = await Invoice.findAll({
431
+ const invoicesToSchedule = await systemFindAll(Invoice, {
428
432
  where: {
429
433
  status: 'paid',
430
434
  metadata: {
@@ -482,7 +486,7 @@ async function recoverCreditScheduleJobs(): Promise<void> {
482
486
  try {
483
487
  // Find subscriptions with active credit schedules
484
488
  // We filter for non-null credit_schedule_state in application code
485
- const allActiveSubscriptions = await Subscription.findAll({
489
+ const allActiveSubscriptions = await systemFindAll(Subscription, {
486
490
  where: {
487
491
  status: ['active', 'trialing'],
488
492
  },
@@ -589,7 +593,7 @@ creditGrantQueue.on('finished', ({ id }) => {
589
593
 
590
594
  async function handleInvoiceCredit(invoiceId: string) {
591
595
  try {
592
- const invoice = (await Invoice.findByPk(invoiceId, {
596
+ const invoice = (await systemFindByPk(Invoice, invoiceId, {
593
597
  include: [
594
598
  {
595
599
  model: Customer,
@@ -619,7 +623,7 @@ async function handleInvoiceCredit(invoiceId: string) {
619
623
  customerId: invoice.customer.id,
620
624
  });
621
625
  if (invoice.metadata?.auto_recharge?.config_id) {
622
- const autoRechargeConfig = await AutoRechargeConfig.findByPk(invoice.metadata?.auto_recharge?.config_id);
626
+ const autoRechargeConfig = await systemFindByPk(AutoRechargeConfig, invoice.metadata?.auto_recharge?.config_id);
623
627
  if (autoRechargeConfig) {
624
628
  await autoRechargeConfig.updateDailyStats(invoice.total);
625
629
  }
@@ -635,7 +639,7 @@ async function handleInvoiceCredit(invoiceId: string) {
635
639
 
636
640
  const items = await Promise.all(
637
641
  lineItems.map(async (lineItem) => {
638
- const price = (await Price.findByPk(lineItem.price_id, {
642
+ const price = (await systemFindByPk(Price, lineItem.price_id, {
639
643
  include: [{ model: Product, as: 'product' }],
640
644
  })) as TPriceExpanded | null;
641
645
 
@@ -664,7 +668,7 @@ async function handleInvoiceCredit(invoiceId: string) {
664
668
  }
665
669
 
666
670
  const quantity = lineItem.quantity || 1;
667
- const currency = await PaymentCurrency.findByPk(creditConfig.currency_id);
671
+ const currency = await systemFindByPk(PaymentCurrency, creditConfig.currency_id);
668
672
 
669
673
  if (!currency) {
670
674
  logger.error('Currency not found', { currencyId: creditConfig.currency_id, invoiceId });
@@ -726,7 +730,7 @@ async function handleInvoiceCredit(invoiceId: string) {
726
730
  return;
727
731
  }
728
732
 
729
- const existingGrants = await CreditGrant.findAll({
733
+ const existingGrants = await systemFindAll(CreditGrant, {
730
734
  where: {
731
735
  customer_id: invoice.customer.id,
732
736
  metadata: {
@@ -972,7 +976,7 @@ events.on('invoice.paid', async (invoice: Invoice) => {
972
976
  try {
973
977
  await addInvoiceCreditJob(invoice.id);
974
978
  if (invoice.subscription_id) {
975
- const subscription = await Subscription.findByPk(invoice.subscription_id);
979
+ const subscription = await systemFindByPk(Subscription, invoice.subscription_id);
976
980
  if (subscription) {
977
981
  const jobs = await activateAfterFirstPaymentSchedules(subscription, invoice.status_transitions?.paid_at);
978
982
  await Promise.all(jobs.map((job) => addScheduledCreditJob(job.jobId, job.job, job.runAt, true)));
@@ -989,11 +993,12 @@ events.on('invoice.paid', async (invoice: Invoice) => {
989
993
  events.on('customer.credit_grant.created', async (data: { id: string }) => {
990
994
  try {
991
995
  // Fetch instance since event emits dataValues (plain object)
992
- const creditGrant = await CreditGrant.findByPk(data.id);
996
+ const creditGrant = await systemFindByPk(CreditGrant, data.id);
993
997
  if (!creditGrant) {
994
998
  logger.error('Credit grant not found for scheduling', { creditGrantId: data.id });
995
999
  return;
996
1000
  }
1001
+ assertJobObjectTenant(creditGrant);
997
1002
  await scheduleCreditGrantJobs(creditGrant);
998
1003
  logger.info('Credit grant jobs scheduled', {
999
1004
  creditGrantId: creditGrant.id,
@@ -1053,7 +1058,7 @@ events.on('credit-grant.queued', async (id, job, args = {}) => {
1053
1058
  * Used by both subscription.created and subscription.started events
1054
1059
  */
1055
1060
  async function initializeAndScheduleCreditJobs(subscriptionId: string, eventName: string): Promise<void> {
1056
- const sub = await Subscription.findByPk(subscriptionId);
1061
+ const sub = await systemFindByPk(Subscription, subscriptionId);
1057
1062
  if (!sub) {
1058
1063
  logger.warn('Subscription not found for credit schedule initialization', {
1059
1064
  subscriptionId,
@@ -1061,6 +1066,7 @@ async function initializeAndScheduleCreditJobs(subscriptionId: string, eventName
1061
1066
  });
1062
1067
  return;
1063
1068
  }
1069
+ assertJobObjectTenant(sub);
1064
1070
 
1065
1071
  // initializeCreditSchedule only works for active/trialing subscriptions
1066
1072
  // and skips if already initialized
@@ -1147,10 +1153,11 @@ events.on('customer.subscription.trial_start', async (subscription: Subscription
1147
1153
  */
1148
1154
  events.on('customer.subscription.deleted', async (subscription: Subscription) => {
1149
1155
  try {
1150
- const sub = await Subscription.findByPk(subscription.id);
1156
+ const sub = await systemFindByPk(Subscription, subscription.id);
1151
1157
  if (!sub) {
1152
1158
  return;
1153
1159
  }
1160
+ assertJobObjectTenant(sub);
1154
1161
 
1155
1162
  const jobIds = await stopCreditSchedule(sub);
1156
1163
 
@@ -7,10 +7,11 @@
7
7
 
8
8
  import { BN } from '@ocap/util';
9
9
  import logger from '../libs/logger';
10
- import createQueue from '../libs/queue';
10
+ import createQueue, { assertJobObjectTenant } from '../libs/queue';
11
11
  import { getAccountState, getCustomerTokenBalance } from '../integrations/arcblock/token';
12
12
  import { CreditGrant, CreditTransaction, Customer, PaymentCurrency } from '../store/models';
13
13
  import { events } from '../libs/event';
14
+ import { systemFindAll, systemFindByPk } from '../store/scoped';
14
15
 
15
16
  type ReconciliationJob = {
16
17
  customerId: string;
@@ -39,7 +40,7 @@ type BalanceInfo = {
39
40
  */
40
41
  async function getBalanceInfo(customerId: string, currencyId: string): Promise<BalanceInfo> {
41
42
  // Get all granted credit grants that have been minted on-chain
42
- const grants = await CreditGrant.findAll({
43
+ const grants = await systemFindAll(CreditGrant, {
43
44
  where: {
44
45
  customer_id: customerId,
45
46
  currency_id: currencyId,
@@ -53,7 +54,7 @@ async function getBalanceInfo(customerId: string, currencyId: string): Promise<B
53
54
  .toString();
54
55
 
55
56
  // Get pending transfers (consumed but token not yet transferred on-chain)
56
- const pendingTransactions = await CreditTransaction.findAll({
57
+ const pendingTransactions = await systemFindAll(CreditTransaction, {
57
58
  where: {
58
59
  customer_id: customerId,
59
60
  transfer_status: 'pending',
@@ -98,13 +99,14 @@ async function handleReconciliation(job: ReconciliationJob) {
98
99
  });
99
100
 
100
101
  try {
101
- const currency = await PaymentCurrency.findByPk(currencyId);
102
+ const currency = await systemFindByPk(PaymentCurrency, currencyId);
102
103
  if (!currency || !currency.isOnChainCredit()) {
103
104
  logger.warn('Currency not found or not on-chain credit', { currencyId });
104
105
  return;
105
106
  }
106
107
 
107
- const customer = await Customer.findByPk(customerId);
108
+ const customer = await systemFindByPk(Customer, customerId);
109
+ assertJobObjectTenant(customer);
108
110
  if (!customer?.did) {
109
111
  logger.warn('Customer not found for reconciliation', { customerId });
110
112
  return;
@@ -1,9 +1,10 @@
1
1
  import pAll from 'p-all';
2
- import createQueue from '../libs/queue';
2
+ import createQueue, { assertJobObjectTenant } from '../libs/queue';
3
3
  import { Coupon, PromotionCode } from '../store/models';
4
4
  import logger from '../libs/logger';
5
5
  import { events } from '../libs/event';
6
6
  import { validCoupon, validPromotionCode } from '../libs/discount/coupon';
7
+ import { systemFindAll, systemFindByPk } from '../store/scoped';
7
8
 
8
9
  export type DiscountType = 'coupon' | 'promotion-code';
9
10
 
@@ -30,10 +31,11 @@ export async function processDiscountStatus(job: DiscountStatusJobData) {
30
31
  * Process coupon status check
31
32
  */
32
33
  async function processCouponStatus(couponId: string) {
33
- const coupon = await Coupon.findByPk(couponId);
34
+ const coupon = await systemFindByPk(Coupon, couponId);
34
35
  if (!coupon) {
35
36
  return;
36
37
  }
38
+ assertJobObjectTenant(coupon);
37
39
 
38
40
  // Skip if already invalid
39
41
  if (!coupon.valid) {
@@ -54,7 +56,7 @@ async function processCouponStatus(couponId: string) {
54
56
  max_redemptions: coupon.max_redemptions,
55
57
  times_redeemed: coupon.times_redeemed,
56
58
  });
57
- const promotionCodes = await PromotionCode.findAll({ where: { coupon_id: couponId, active: true } });
59
+ const promotionCodes = await systemFindAll(PromotionCode, { where: { coupon_id: couponId, active: true } });
58
60
  await pAll(
59
61
  promotionCodes.map(
60
62
  (promotionCode) => () =>
@@ -76,11 +78,12 @@ async function processCouponStatus(couponId: string) {
76
78
  * Process promotion code status check
77
79
  */
78
80
  async function processPromotionCodeStatus(promotionCodeId: string) {
79
- const promotionCode = await PromotionCode.findByPk(promotionCodeId);
81
+ const promotionCode = await systemFindByPk(PromotionCode, promotionCodeId);
80
82
  if (!promotionCode) {
81
83
  logger.warn('Promotion code not found for status check', { promotionCodeId });
82
84
  return;
83
85
  }
86
+ assertJobObjectTenant(promotionCode);
84
87
 
85
88
  // Skip if already inactive
86
89
  if (!promotionCode.active) {
@@ -146,8 +149,8 @@ export const discountStatusQueue = createQueue<DiscountStatusJobData>({
146
149
  export const startDiscountStatusQueue = async () => {
147
150
  logger.info('Starting discount status queue...');
148
151
 
149
- const coupons = await Coupon.findAll({ where: { valid: true } });
150
- const promotionCodes = await PromotionCode.findAll({ where: { active: true } });
152
+ const coupons = await systemFindAll(Coupon, { where: { valid: true } });
153
+ const promotionCodes = await systemFindAll(PromotionCode, { where: { active: true } });
151
154
  await pAll(
152
155
  coupons.map(
153
156
  (coupon) => () =>
@@ -1,8 +1,12 @@
1
1
  import { Op } from 'sequelize';
2
+ import { isCfWorker } from '../libs/env';
3
+ import { systemFindAll, systemFindByPk } from '../store/scoped';
2
4
 
5
+ import { withTenant } from '../libs/context';
3
6
  import { events } from '../libs/event';
4
7
  import logger from '../libs/logger';
5
8
  import createQueue from '../libs/queue';
9
+ import { resolveRowTenant } from '../libs/tenant';
6
10
  import { Event } from '../store/models/event';
7
11
  import { WebhookAttempt } from '../store/models/webhook-attempt';
8
12
  import { WebhookEndpoint } from '../store/models/webhook-endpoint';
@@ -15,7 +19,7 @@ type EventJob = {
15
19
  export const handleEvent = async (job: EventJob) => {
16
20
  logger.info('Starting to handle event', job);
17
21
 
18
- const event = await Event.findByPk(job.eventId);
22
+ const event = await systemFindByPk(Event, job.eventId);
19
23
  if (!event) {
20
24
  logger.warn('Event not found', job);
21
25
  return;
@@ -26,7 +30,12 @@ export const handleEvent = async (job: EventJob) => {
26
30
  return;
27
31
  }
28
32
 
29
- const webhooks = await WebhookEndpoint.findAll({ where: { status: 'enabled', livemode: event.livemode } });
33
+ // Phase 4 (W1-3): fanout only to endpoints of the event's own tenant.
34
+ // resolveRowTenant fails closed for NULL-tenant events in multi mode.
35
+ const eventTenant = resolveRowTenant(event);
36
+ const webhooks = await systemFindAll(WebhookEndpoint, {
37
+ where: { status: 'enabled', livemode: event.livemode, instance_did: eventTenant },
38
+ });
30
39
  const eventWebhooks = webhooks.filter((webhook) => webhook.enabled_events.includes(event.type));
31
40
  if (eventWebhooks.length === 0) {
32
41
  logger.info('no webhook endpoint for event', job);
@@ -75,22 +84,43 @@ export const eventQueue = createQueue<EventJob>({
75
84
  });
76
85
 
77
86
  export const startEventQueue = async () => {
78
- const docs = await Event.findAll({
87
+ // Cross-tenant recovery scan: fetch instance_did too so the recovered push
88
+ // carries its tenant. In multi mode startup runs with NO tenant context, so a
89
+ // push without instance_did would hit injectJobTenant -> getInstanceDid ->
90
+ // TENANT_CONTEXT_MISSING (a FATAL unhandledRejection from the async forEach
91
+ // below). The handler re-derives the tenant from the row, but the PUSH itself
92
+ // must already be tenant-stamped to survive multi mode.
93
+ const docs = await systemFindAll(Event, {
79
94
  where: {
80
95
  pending_webhooks: { [Op.gt]: 0 },
81
96
  },
82
- attributes: ['id'],
97
+ attributes: ['id', 'instance_did'],
83
98
  });
84
99
 
85
100
  logger.info(`Found ${docs.length} events with pending webhooks`);
86
101
 
87
- docs.forEach(async (x) => {
88
- const exist = await eventQueue.get(x.id);
89
- if (!exist) {
90
- logger.info(`Pushing event ${x.id} to queue`);
91
- eventQueue.push({ id: x.id, job: { eventId: x.id }, persist: false });
102
+ // Sequential + per-doc try/catch: one bad row must never crash startup
103
+ // recovery (await in the original forEach left rejections unhandled). The
104
+ // push runs inside withTenant(event.instance_did) so injectJobTenant stamps
105
+ // the correct tenant — in multi mode there is no ambient context at startup.
106
+ for (const x of docs) {
107
+ if (!x.instance_did) {
108
+ logger.warn('skip pending-webhook event with no tenant', { id: x.id });
109
+ } else {
110
+ try {
111
+ // eslint-disable-next-line no-await-in-loop -- bounded startup recovery
112
+ await withTenant(x.instance_did, async () => {
113
+ const exist = await eventQueue.get(x.id);
114
+ if (!exist) {
115
+ logger.info(`Pushing event ${x.id} to queue`);
116
+ eventQueue.push({ id: x.id, job: { eventId: x.id }, persist: false });
117
+ }
118
+ });
119
+ } catch (error) {
120
+ logger.error('failed to recover pending-webhook event', { id: x.id, error });
121
+ }
92
122
  }
93
- });
123
+ }
94
124
 
95
125
  logger.info('Finished starting event queue');
96
126
  };
@@ -100,7 +130,7 @@ eventQueue.on('failed', ({ id, job, error }) => {
100
130
  });
101
131
 
102
132
  events.on('event.created', async (event) => {
103
- if ((globalThis as any).__CF_ENV__) {
133
+ if (isCfWorker()) {
104
134
  // CF Workers: execute inline to save 2 CF Queue ops per event.
105
135
  // eventQueue only dispatches webhooks — lightweight DB lookup + webhookQueue.push.
106
136
  // Webhook delivery still goes through webhookQueue with full retry guarantees.