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
@@ -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,11 @@
1
1
  import { Op } from 'sequelize';
2
+ import { isCfWorker } from '../libs/env';
3
+ import { systemFindAll, systemFindByPk } from '../store/scoped';
2
4
 
3
5
  import { events } from '../libs/event';
4
6
  import logger from '../libs/logger';
5
7
  import createQueue from '../libs/queue';
8
+ import { resolveRowTenant } from '../libs/tenant';
6
9
  import { Event } from '../store/models/event';
7
10
  import { WebhookAttempt } from '../store/models/webhook-attempt';
8
11
  import { WebhookEndpoint } from '../store/models/webhook-endpoint';
@@ -15,7 +18,7 @@ type EventJob = {
15
18
  export const handleEvent = async (job: EventJob) => {
16
19
  logger.info('Starting to handle event', job);
17
20
 
18
- const event = await Event.findByPk(job.eventId);
21
+ const event = await systemFindByPk(Event, job.eventId);
19
22
  if (!event) {
20
23
  logger.warn('Event not found', job);
21
24
  return;
@@ -26,7 +29,12 @@ export const handleEvent = async (job: EventJob) => {
26
29
  return;
27
30
  }
28
31
 
29
- const webhooks = await WebhookEndpoint.findAll({ where: { status: 'enabled', livemode: event.livemode } });
32
+ // Phase 4 (W1-3): fanout only to endpoints of the event's own tenant.
33
+ // resolveRowTenant fails closed for NULL-tenant events in multi mode.
34
+ const eventTenant = resolveRowTenant(event);
35
+ const webhooks = await systemFindAll(WebhookEndpoint, {
36
+ where: { status: 'enabled', livemode: event.livemode, instance_did: eventTenant },
37
+ });
30
38
  const eventWebhooks = webhooks.filter((webhook) => webhook.enabled_events.includes(event.type));
31
39
  if (eventWebhooks.length === 0) {
32
40
  logger.info('no webhook endpoint for event', job);
@@ -75,7 +83,7 @@ export const eventQueue = createQueue<EventJob>({
75
83
  });
76
84
 
77
85
  export const startEventQueue = async () => {
78
- const docs = await Event.findAll({
86
+ const docs = await systemFindAll(Event, {
79
87
  where: {
80
88
  pending_webhooks: { [Op.gt]: 0 },
81
89
  },
@@ -100,7 +108,7 @@ eventQueue.on('failed', ({ id, job, error }) => {
100
108
  });
101
109
 
102
110
  events.on('event.created', async (event) => {
103
- if ((globalThis as any).__CF_ENV__) {
111
+ if (isCfWorker()) {
104
112
  // CF Workers: execute inline to save 2 CF Queue ops per event.
105
113
  // eventQueue only dispatches webhooks — lightweight DB lookup + webhookQueue.push.
106
114
  // Webhook delivery still goes through webhookQueue with full retry guarantees.
@@ -11,6 +11,9 @@ const REPRESENTATIVE_TOKENS = ['ABT', 'ETH'];
11
11
  interface HealthCheckJob {
12
12
  type: 'health_check';
13
13
  timestamp: number;
14
+ /** D6: the tenant this check belongs to — carried in the payload so the
15
+ * re-schedule survives outside any ALS context (multi mode). */
16
+ instance_did?: string;
14
17
  }
15
18
 
16
19
  interface HealthCheckResult {
@@ -201,43 +204,59 @@ export const exchangeRateHealthQueue = createQueue<HealthCheckJob>({
201
204
  // Schedule health checks every 6 hours
202
205
  const HEALTH_CHECK_INTERVAL = 6 * 60 * 60; // 6 hours in seconds
203
206
 
207
+ // D6: every scheduled health-check job carries its tenant in the PAYLOAD so the
208
+ // re-schedule (on 'finished') preserves it WITHOUT relying on the ALS context —
209
+ // the 'finished' listener fires outside any withTenant() scope. In multi mode a
210
+ // tenant-less push would throw TENANT_CONTEXT_MISSING; here `instance_did` is set
211
+ // on the job, so injectJobTenant honors it instead of reading the (absent) ALS.
212
+ let scheduleListenerBound = false;
213
+
214
+ function scheduleNextHealthCheck(instanceDid?: string): void {
215
+ const now = Math.floor(Date.now() / 1000);
216
+ const nextRun = now + HEALTH_CHECK_INTERVAL;
217
+
218
+ exchangeRateHealthQueue.push({
219
+ job: {
220
+ type: 'health_check',
221
+ timestamp: Date.now(),
222
+ // carry the tenant in the payload (multi mode has no ALS context here)
223
+ ...(instanceDid ? { instance_did: instanceDid } : {}),
224
+ } as HealthCheckJob,
225
+ runAt: nextRun,
226
+ persist: true,
227
+ });
228
+
229
+ logger.info('Scheduled next exchange rate health check', {
230
+ nextRunAt: new Date(nextRun * 1000).toISOString(),
231
+ intervalHours: HEALTH_CHECK_INTERVAL / 3600,
232
+ instanceDid,
233
+ });
234
+ }
235
+
204
236
  /**
205
- * Initialize and schedule periodic health checks
237
+ * Schedule periodic health checks for a tenant. The initial push AND every
238
+ * re-schedule carry `instance_did` in the job payload, so the cancel-then-replay
239
+ * recovery (D2) and the post-restart redispatch stay under the correct tenant.
206
240
  */
207
- export function scheduleHealthChecks(): void {
208
- const scheduleNext = () => {
209
- const now = Math.floor(Date.now() / 1000);
210
- const nextRun = now + HEALTH_CHECK_INTERVAL;
211
-
212
- exchangeRateHealthQueue.push({
213
- job: {
214
- type: 'health_check',
215
- timestamp: Date.now(),
216
- },
217
- runAt: nextRun,
218
- persist: true,
241
+ export function scheduleHealthChecks(instanceDid?: string): void {
242
+ scheduleNextHealthCheck(instanceDid);
243
+
244
+ // bind the re-schedule listener ONCE (idempotent across bootstrapTenant calls);
245
+ // it reads the finished job's own instance_did to re-schedule under that tenant.
246
+ if (!scheduleListenerBound) {
247
+ scheduleListenerBound = true;
248
+ exchangeRateHealthQueue.on('finished', (data: { job?: HealthCheckJob }) => {
249
+ scheduleNextHealthCheck(data?.job?.instance_did);
219
250
  });
220
-
221
- logger.info('Scheduled next exchange rate health check', {
222
- nextRunAt: new Date(nextRun * 1000).toISOString(),
223
- intervalHours: HEALTH_CHECK_INTERVAL / 3600,
224
- });
225
- };
226
-
227
- // Schedule initial check
228
- scheduleNext();
229
-
230
- // Listen for completed checks and schedule next one
231
- exchangeRateHealthQueue.on('finished', () => {
232
- scheduleNext();
233
- });
251
+ }
234
252
 
235
253
  logger.info('Exchange rate health check scheduling initialized', {
236
254
  intervalHours: HEALTH_CHECK_INTERVAL / 3600,
255
+ instanceDid,
237
256
  });
238
257
  }
239
258
 
240
- // Export for explicit initialization in index.ts
241
- export function startExchangeRateHealthQueue(): void {
242
- scheduleHealthChecks();
259
+ // Export for explicit initialization (single mode / per-tenant bootstrap).
260
+ export function startExchangeRateHealthQueue(instanceDid?: string): void {
261
+ scheduleHealthChecks(instanceDid);
243
262
  }
@@ -1,10 +1,11 @@
1
1
  import { Op } from 'sequelize';
2
+ import { systemFindAll, systemFindByPk } from '../store/scoped';
2
3
 
3
4
  import { getInvoiceShouldPayTotal, handleOverdraftProtectionInvoiceAfterPayment } from '../libs/invoice';
4
- import { createEvent } from '../libs/audit';
5
+ import { createEvent, reportAuditFailure } from '../libs/audit';
5
6
  import dayjs from '../libs/dayjs';
6
7
  import logger from '../libs/logger';
7
- import createQueue from '../libs/queue';
8
+ import createQueue, { assertJobObjectTenant } from '../libs/queue';
8
9
  import { PaymentMethod, Invoice, InvoiceItem } from '../store/models';
9
10
  import { CheckoutSession } from '../store/models/checkout-session';
10
11
  import { PaymentIntent } from '../store/models/payment-intent';
@@ -29,11 +30,12 @@ type InvoiceJob = {
29
30
  export const handleInvoice = async (job: InvoiceJob) => {
30
31
  logger.info('handle invoice', job);
31
32
 
32
- const invoice = await Invoice.findByPk(job.invoiceId);
33
+ const invoice = await systemFindByPk(Invoice, job.invoiceId);
33
34
  if (!invoice) {
34
35
  logger.warn('invoice not found', { invoiceId: job.invoiceId });
35
36
  return;
36
37
  }
38
+ assertJobObjectTenant(invoice);
37
39
  if (invoice.status !== 'open') {
38
40
  logger.warn('invoice not open', { invoiceId: job.invoiceId });
39
41
  return;
@@ -63,7 +65,7 @@ export const handleInvoice = async (job: InvoiceJob) => {
63
65
  logger.info('Invoice updated to paid status', { invoiceId: invoice.id });
64
66
 
65
67
  if (invoice.subscription_id) {
66
- const subscription = await Subscription.findByPk(invoice.subscription_id);
68
+ const subscription = await systemFindByPk(Subscription, invoice.subscription_id);
67
69
  if (subscription) {
68
70
  if (subscription.status === 'incomplete') {
69
71
  await subscription.start();
@@ -78,17 +80,17 @@ export const handleInvoice = async (job: InvoiceJob) => {
78
80
  }
79
81
  } else {
80
82
  if (invoice.billing_reason === 'subscription_cycle') {
81
- createEvent('Subscription', 'customer.subscription.renewed', subscription).catch(console.error);
83
+ createEvent('Subscription', 'customer.subscription.renewed', subscription).catch(reportAuditFailure);
82
84
  }
83
85
  if (invoice.billing_reason === 'subscription_update') {
84
- createEvent('Subscription', 'customer.subscription.upgraded', subscription).catch(console.error);
86
+ createEvent('Subscription', 'customer.subscription.upgraded', subscription).catch(reportAuditFailure);
85
87
  }
86
88
  }
87
89
  }
88
90
  }
89
91
 
90
92
  if (invoice.checkout_session_id) {
91
- const checkoutSession = await CheckoutSession.findByPk(invoice.checkout_session_id);
93
+ const checkoutSession = await systemFindByPk(CheckoutSession, invoice.checkout_session_id);
92
94
  if (checkoutSession && checkoutSession.status === 'open') {
93
95
  await checkoutSession.update({ status: 'complete', payment_status: 'no_payment_required' });
94
96
  logger.info('invoice checkout session updated', { invoice: invoice.id, checkoutSession: checkoutSession.id });
@@ -96,7 +98,7 @@ export const handleInvoice = async (job: InvoiceJob) => {
96
98
  }
97
99
 
98
100
  if (invoice.payment_intent_id) {
99
- const paymentIntent = await PaymentIntent.findByPk(invoice.payment_intent_id);
101
+ const paymentIntent = await systemFindByPk(PaymentIntent, invoice.payment_intent_id);
100
102
  if (
101
103
  paymentIntent &&
102
104
  ['requires_action', 'requires_capture', 'requires_payment_method'].includes(paymentIntent.status)
@@ -119,7 +121,7 @@ export const handleInvoice = async (job: InvoiceJob) => {
119
121
  invoiceId: job.invoiceId,
120
122
  paymentIntent: invoice.payment_intent_id,
121
123
  });
122
- paymentIntent = await PaymentIntent.findByPk(invoice.payment_intent_id);
124
+ paymentIntent = await systemFindByPk(PaymentIntent, invoice.payment_intent_id);
123
125
  if (paymentIntent && paymentIntent.isImmutable() === false) {
124
126
  await paymentIntent.update({ status: 'requires_capture', customer_id: invoice.customer_id });
125
127
  logger.info('PaymentIntent updated for invoice', {
@@ -166,7 +168,7 @@ export const handleInvoice = async (job: InvoiceJob) => {
166
168
  });
167
169
 
168
170
  if (invoice.checkout_session_id) {
169
- const checkoutSession = await CheckoutSession.findByPk(invoice.checkout_session_id);
171
+ const checkoutSession = await systemFindByPk(CheckoutSession, invoice.checkout_session_id);
170
172
  if (checkoutSession && checkoutSession.status === 'open') {
171
173
  await checkoutSession.update({ payment_intent_id: paymentIntent.id });
172
174
  logger.info('PaymentIntent attached to invoice checkout session', {
@@ -212,14 +214,14 @@ export const invoiceQueue = createQueue<InvoiceJob>({
212
214
  });
213
215
 
214
216
  export const startInvoiceQueue = async () => {
215
- const lock = getLock('startInvoiceQueue');
217
+ const lock = getLock('startInvoiceQueue', { scope: 'global' });
216
218
  if (lock.locked) {
217
219
  return;
218
220
  }
219
221
 
220
222
  try {
221
223
  await lock.acquire();
222
- const invoices = await Invoice.findAll({
224
+ const invoices = await systemFindAll(Invoice, {
223
225
  where: {
224
226
  status: 'open',
225
227
  collection_method: 'charge_automatically',
@@ -259,11 +261,12 @@ invoiceQueue.on('failed', ({ id, job, error }) => {
259
261
  });
260
262
 
261
263
  events.on('invoice.paid', async ({ id: invoiceId }) => {
262
- const invoice = await Invoice.findByPk(invoiceId);
264
+ const invoice = await systemFindByPk(Invoice, invoiceId);
263
265
  if (!invoice) {
264
266
  logger.error('Invoice not found for paid event', { invoiceId });
265
267
  return;
266
268
  }
269
+ assertJobObjectTenant(invoice);
267
270
  logger.info('Processing paid invoice', { invoiceId, billingReason: invoice.billing_reason });
268
271
 
269
272
  const checkBillingReason = ['subscription_cycle', 'subscription_cancel'];
@@ -286,7 +289,7 @@ events.on('invoice.paid', async ({ id: invoiceId }) => {
286
289
  // Separate listener to mark quotes as paid - independent of other invoice.paid handlers
287
290
  events.on('invoice.paid', async ({ id: invoiceId }) => {
288
291
  try {
289
- const invoiceItems = await InvoiceItem.findAll({
292
+ const invoiceItems = await systemFindAll(InvoiceItem, {
290
293
  where: { invoice_id: invoiceId },
291
294
  });
292
295
 
@@ -304,7 +307,7 @@ events.on('invoice.paid', async ({ id: invoiceId }) => {
304
307
  await Promise.all(
305
308
  quoteIds.map(async (quoteId) => {
306
309
  try {
307
- const quote = await PriceQuote.findByPk(quoteId);
310
+ const quote = await systemFindByPk(PriceQuote, quoteId);
308
311
  if (quote && quote.status !== 'paid') {
309
312
  await quote.update({ invoice_id: invoiceId });
310
313
  await quoteService.markAsPaid(quoteId);
@@ -1,5 +1,6 @@
1
1
  import { Op } from 'sequelize';
2
2
  import debounce from 'lodash/debounce';
3
+ import { systemFindByPk } from '../store/scoped';
3
4
  /* eslint-disable @typescript-eslint/indent */
4
5
  import { events } from '../libs/event';
5
6
  import logger from '../libs/logger';
@@ -62,7 +63,7 @@ import {
62
63
  SubscriptionStakeSlashSucceededEmailTemplate,
63
64
  SubscriptionStakeSlashSucceededEmailTemplateOptions,
64
65
  } from '../libs/notification/template/subscription-stake-slash-succeeded';
65
- import createQueue from '../libs/queue';
66
+ import createQueue, { assertJobObjectTenant } from '../libs/queue';
66
67
  import {
67
68
  CheckoutSession,
68
69
  EventType,
@@ -263,7 +264,8 @@ async function getNotificationTemplate(job: NotificationQueueJob): Promise<BaseE
263
264
  }
264
265
  if (job.type === 'refund.succeeded') {
265
266
  const { refundId } = job.options as { refundId: string };
266
- const refund = await Refund.findByPk(refundId);
267
+ const refund = await systemFindByPk(Refund, refundId);
268
+ if (refund) assertJobObjectTenant(refund);
267
269
  if (refund?.subscription_id) {
268
270
  return new SubscriptionRefundSucceededEmailTemplate(
269
271
  job.options as SubscriptionRefundSucceededEmailTemplateOptions
@@ -570,7 +572,8 @@ export async function startNotificationQueue() {
570
572
  // 一次性购买成功
571
573
  events.on('checkout.session.completed', async (checkoutSession: CheckoutSession) => {
572
574
  if (checkoutSession.mode === 'payment') {
573
- const paymentLink = await PaymentLink.findByPk(checkoutSession.payment_link_id);
575
+ const paymentLink = await systemFindByPk(PaymentLink, checkoutSession.payment_link_id);
576
+ if (paymentLink) assertJobObjectTenant(paymentLink);
574
577
  if (paymentLink?.submit_type === 'donate') {
575
578
  addNotificationJob('customer.reward.succeeded', { checkoutSessionId: checkoutSession.id }, [
576
579
  checkoutSession.id,
@@ -583,7 +586,8 @@ export async function startNotificationQueue() {
583
586
  });
584
587
 
585
588
  events.on('customer.subscription.renewed', async (subscription: Subscription) => {
586
- const customer = await Customer.findByPk(subscription.customer_id);
589
+ const customer = await systemFindByPk(Customer, subscription.customer_id);
590
+ if (customer) assertJobObjectTenant(customer);
587
591
  const preference = customer?.preference?.notification;
588
592
  if (!subscription.latest_invoice_id) {
589
593
  logger.warn('Missing latest_invoice_id for subscription', { subscriptionId: subscription.id });
@@ -619,7 +623,8 @@ export async function startNotificationQueue() {
619
623
  });
620
624
 
621
625
  events.on('customer.subscription.renew_failed', async (subscription: Subscription, { invoiceId }) => {
622
- const invoice = await Invoice.findByPk(invoiceId || subscription.latest_invoice_id);
626
+ const invoice = await systemFindByPk(Invoice, invoiceId || subscription.latest_invoice_id);
627
+ if (invoice) assertJobObjectTenant(invoice);
623
628
 
624
629
  logger.info('events.on', 'customer.subscription.renew_failed', {
625
630
  subscriptionId: subscription.id,
@@ -731,7 +736,8 @@ export async function startNotificationQueue() {
731
736
  events.on(
732
737
  'customer.auto_recharge.failed',
733
738
  async (customer: Customer, { autoRechargeConfigId, invoiceId, paymentIntentId }) => {
734
- const invoice = await Invoice.findByPk(invoiceId);
739
+ const invoice = await systemFindByPk(Invoice, invoiceId);
740
+ if (invoice) assertJobObjectTenant(invoice);
735
741
  logger.info('addNotificationJob:customer.auto_recharge.failed', autoRechargeConfigId);
736
742
  if (invoice && autoRechargeConfigId && invoice.metadata?.auto_recharge_failed_reason) {
737
743
  addNotificationJob(
@@ -819,7 +825,8 @@ export async function startNotificationQueue() {
819
825
 
820
826
  // Reuse customer.auto_recharge.failed notification with skipped reason
821
827
  // This ensures users are notified when auto-recharge cannot proceed
822
- const customer = await Customer.findByPk(data.customer_id);
828
+ const customer = await systemFindByPk(Customer, data.customer_id);
829
+ if (customer) assertJobObjectTenant(customer);
823
830
  if (customer) {
824
831
  addNotificationJob(
825
832
  'customer.auto_recharge.failed',