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
package/api/src/index.ts CHANGED
@@ -1,168 +1,29 @@
1
- import 'express-async-errors';
2
-
3
- import path from 'path';
4
-
5
- import { fallback } from '@blocklet/sdk/lib/middlewares/fallback';
6
- import cookieParser from 'cookie-parser';
7
- import cors from 'cors';
1
+ // Blocklet server shell — Phase 7 (W2-0); express→hono Phase 4.
2
+ //
3
+ // Service assembly lives in ./bootstrap (shared with the dev shell ../dev.ts).
4
+ // This file is the PRODUCTION entry (api/dist/index.js): build the service and
5
+ // LISTEN the hono app via @hono/node-server `serve({ fetch })`, then start
6
+ // background services in the listen callback. Other hosts import
7
+ // @arcblock/payment-service and own listen/lifecycle themselves.
8
+ //
9
+ // Phase 4: the loopback bridge + express app shell are gone — the hono app
10
+ // (service.fetch) serves every route natively (resource domains + DID-Connect +
11
+ // production static/SPA fallback). The dev client + HMR are served by ../dev.ts
12
+ // (a connect shell), never by this production entry.
8
13
  import dotenv from 'dotenv-flow';
9
- import express, { ErrorRequestHandler } from 'express';
10
- // eslint-disable-next-line import/no-extraneous-dependencies
11
- import { CustomError, formatError, getStatusFromError } from '@blocklet/error';
12
- import { csrf } from '@blocklet/sdk/lib/middlewares';
13
- import { cdn } from '@blocklet/sdk/lib/middlewares/cdn';
14
- import { xss } from '@blocklet/xss';
15
-
16
- import { syncCurrencyLogo } from './crons/currency';
17
- import crons from './crons/index';
18
- import { ensureStakedForGas } from './integrations/arcblock/stake';
19
- import { initResourceHandler } from './integrations/blocklet/resource';
20
- import { initUserHandler } from './integrations/blocklet/user';
21
- import { ensureWebhookRegistered } from './integrations/stripe/setup';
22
- import { handlers } from './libs/auth';
23
- import logger, { setupAccessLogger } from './libs/logger';
24
- import { contextMiddleware, ensureI18n } from './libs/middleware';
25
- import { ensureCreateOverdraftProtectionPrices } from './libs/overdraft-protection';
26
- import { initEventBroadcast } from './libs/ws';
27
- import { startCheckoutSessionQueue } from './queues/checkout-session';
28
- import { startCreditConsumeQueue } from './queues/credit-consume';
29
- import { startCreditGrantQueue } from './queues/credit-grant';
30
- import { startReconciliationQueue } from './queues/credit-reconciliation';
31
- import { startDiscountStatusQueue } from './queues/discount-status';
32
- import { startEventQueue } from './queues/event';
33
- import { startExchangeRateHealthQueue } from './queues/exchange-rate-health';
34
- import { startInvoiceQueue } from './queues/invoice';
35
- import { startNotificationQueue } from './queues/notification';
36
- import { startPaymentQueue } from './queues/payment';
37
- import { startPayoutQueue } from './queues/payout';
38
- import { startRefundQueue } from './queues/refund';
39
- import { startUploadBillingInfoListener } from './queues/space';
40
- import { startSubscriptionQueue } from './queues/subscription';
41
- import { startTokenTransferQueue } from './queues/token-transfer';
42
- import { startVendorCommissionQueue } from './queues/vendors/commission';
43
- import { startVendorFulfillmentQueue } from './queues/vendors/fulfillment';
44
- import { startCoordinatedFulfillmentQueue } from './queues/vendors/fulfillment-coordinator';
45
- import routes from './routes';
46
- import autoRechargeAuthorizationHandlers from './routes/connect/auto-recharge-auth';
47
- import changePaymentHandlers from './routes/connect/change-payment';
48
- import changePlanHandlers from './routes/connect/change-plan';
49
- import collectHandlers from './routes/connect/collect';
50
- import collectBatchHandlers from './routes/connect/collect-batch';
51
- import delegationHandlers from './routes/connect/delegation';
52
- import overdraftProtectionHandlers from './routes/connect/overdraft-protection';
53
- import payHandlers from './routes/connect/pay';
54
- import reStakeHandlers from './routes/connect/re-stake';
55
- import rechargeHandlers from './routes/connect/recharge';
56
- import rechargeAccountHandlers from './routes/connect/recharge-account';
57
- import setupHandlers from './routes/connect/setup';
58
- import subscribeHandlers from './routes/connect/subscribe';
59
- import changePayerHandlers from './routes/connect/change-payer';
60
- import { initialize } from './store/models';
61
- import { sequelize } from './store/sequelize';
14
+ import { serve } from '@hono/node-server';
62
15
 
63
16
  dotenv.config();
64
17
 
65
- initialize(sequelize);
66
-
67
- export const app = express();
68
- setupAccessLogger(app);
69
- app.set('trust proxy', true);
70
- app.use(cookieParser());
71
- app.use((req, res, next) => {
72
- if (req.originalUrl.startsWith('/api/integrations/stripe/webhook')) {
73
- next();
74
- } else {
75
- express.json({ limit: '1 mb' })(req, res, next);
76
- }
77
- });
78
- app.use(express.urlencoded({ extended: true, limit: '1 mb' }));
79
- app.use(cors());
80
- app.use(xss({ allowedKeys: [] }));
81
- app.use(csrf());
82
- app.use(ensureI18n());
83
- app.use(cdn());
84
-
85
- const router = express.Router();
86
- handlers.attach(Object.assign({ app: router }, collectHandlers));
87
- handlers.attach(Object.assign({ app: router }, collectBatchHandlers));
88
- handlers.attach(Object.assign({ app: router }, payHandlers));
89
- handlers.attach(Object.assign({ app: router }, setupHandlers));
90
- handlers.attach(Object.assign({ app: router }, subscribeHandlers));
91
- handlers.attach(Object.assign({ app: router }, changePaymentHandlers));
92
- handlers.attach(Object.assign({ app: router }, changePlanHandlers));
93
- handlers.attach(Object.assign({ app: router }, rechargeHandlers));
94
- handlers.attach(Object.assign({ app: router }, rechargeAccountHandlers));
95
- handlers.attach(Object.assign({ app: router }, delegationHandlers));
96
- handlers.attach(Object.assign({ app: router }, overdraftProtectionHandlers));
97
- handlers.attach(Object.assign({ app: router }, reStakeHandlers));
98
- handlers.attach(Object.assign({ app: router }, autoRechargeAuthorizationHandlers));
99
- handlers.attach(Object.assign({ app: router }, changePayerHandlers));
100
- router.use('/api', routes);
101
-
102
- const isProduction = process.env.BLOCKLET_MODE === 'production';
103
-
104
- app.use(contextMiddleware);
105
- app.use(router);
106
-
107
- if (isProduction) {
108
- const staticDir = path.resolve(process.env.BLOCKLET_APP_DIR!, 'dist');
109
- app.use(express.static(staticDir, { maxAge: '30d', index: false }));
110
- app.use(fallback('index.html', { root: staticDir }));
111
- }
112
-
113
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
114
- app.use(<ErrorRequestHandler>((err, req, res, _next) => {
115
- logger.error('handle router error', err);
116
- if (err instanceof CustomError) {
117
- res.status(getStatusFromError(err)).json({ error: formatError(err) });
118
- return;
119
- }
120
- if (req.accepts('json')) {
121
- res.status(500).send({ error: err.message });
122
- } else {
123
- res.status(500).send('Something broke!');
124
- }
125
- }));
126
-
127
- const port = parseInt(process.env.BLOCKLET_PORT!, 10);
128
-
129
- export const server = app.listen(port, (err?: any) => {
130
- if (err) throw err;
131
- logger.info(`> payment-kit ready on ${port}`);
132
-
133
- syncCurrencyLogo();
134
- ensureCreateOverdraftProtectionPrices();
135
- startPaymentQueue().then(() => logger.info('payment queue started'));
136
- startInvoiceQueue().then(() => logger.info('invoice queue started'));
137
- startSubscriptionQueue().then(() => logger.info('subscription queue started'));
138
- startEventQueue().then(() => logger.info('event queue started'));
139
- startPayoutQueue().then(() => logger.info('payout queue started'));
140
- startVendorCommissionQueue().then(() => logger.info('vendor commission queue started'));
141
- startVendorFulfillmentQueue().then(() => logger.info('vendor fulfillment queue started'));
142
- startCoordinatedFulfillmentQueue().then(() => logger.info('coordinated fulfillment queue started'));
143
- startCheckoutSessionQueue().then(() => logger.info('checkoutSession queue started'));
144
- startNotificationQueue().then(() => logger.info('notification queue started'));
145
- startRefundQueue().then(() => logger.info('refund queue started'));
146
- startCreditConsumeQueue().then(() => logger.info('credit queue started'));
147
- startCreditGrantQueue().then(() => logger.info('credit grant queue started'));
148
- startTokenTransferQueue().then(() => logger.info('token transfer queue started'));
149
- startReconciliationQueue().then(() => logger.info('credit reconciliation queue started'));
150
- startDiscountStatusQueue().then(() => logger.info('discount status queue started'));
151
- startExchangeRateHealthQueue();
152
- logger.info('exchange rate health queue started');
153
- startUploadBillingInfoListener();
154
-
155
- if (process.env.BLOCKLET_MODE === 'production') {
156
- ensureWebhookRegistered().catch(console.error);
157
- }
158
-
159
- ensureStakedForGas().catch(console.error);
160
-
161
- crons.init();
18
+ // eslint-disable-next-line import/first
19
+ import { buildService, onListening } from './bootstrap';
162
20
 
163
- initEventBroadcast();
21
+ const { service, port } = buildService();
164
22
 
165
- initResourceHandler();
23
+ // @hono/node-server serve(): the listeningListener is the exact equivalent of the
24
+ // old express app.listen(port, cb) — it fires once the socket is bound, which is
25
+ // where background services start. serve() returns the underlying Node Server
26
+ // (used by dev tooling for HMR upgrades and by lifecycle teardown).
27
+ export const server = serve({ fetch: service.fetch, port }, (info) => onListening(service, info.port));
166
28
 
167
- initUserHandler();
168
- });
29
+ export { service };
@@ -18,6 +18,7 @@
18
18
  // Tests replace this class via jest mocks.
19
19
 
20
20
  import { config as configApple, validate as validateAppleReceipt } from 'node-apple-receipt-verify';
21
+ import { appStoreWriteEnabled } from '../../libs/env';
21
22
 
22
23
  import logger from '../../libs/logger';
23
24
  import {
@@ -112,8 +113,6 @@ export type AppStoreSettings = {
112
113
  private_key_pem?: string;
113
114
  };
114
115
 
115
- const WRITE_ENABLED = process.env.APP_STORE_WRITE_ENABLED === 'true';
116
-
117
116
  export class AppStoreClient {
118
117
  declare readonly bundleId: string;
119
118
 
@@ -348,7 +347,7 @@ export class AppStoreClient {
348
347
  /** Refund — Apple actually doesn't let third parties refund subscriptions; this is here for symmetry with GooglePlayClient. */
349
348
  // eslint-disable-next-line class-methods-use-this, require-await
350
349
  public async refundSubscription(originalTransactionId: string): Promise<void> {
351
- if (!WRITE_ENABLED) {
350
+ if (!appStoreWriteEnabled()) {
352
351
  throw new Error(
353
352
  `refundSubscription(${originalTransactionId}) is gated behind APP_STORE_WRITE_ENABLED env. Note: Apple Server API has no third-party refund endpoint — refunds must be requested by the end user via reportProblem.apple.com or by Apple support.`
354
353
  );
@@ -359,7 +358,7 @@ export class AppStoreClient {
359
358
  /** Cancel — same caveat as refund: Apple expects the user to cancel via App Store > Subscriptions. */
360
359
  // eslint-disable-next-line class-methods-use-this, require-await
361
360
  public async cancelSubscription(originalTransactionId: string): Promise<void> {
362
- if (!WRITE_ENABLED) {
361
+ if (!appStoreWriteEnabled()) {
363
362
  throw new Error(
364
363
  `cancelSubscription(${originalTransactionId}) is gated behind APP_STORE_WRITE_ENABLED env. Apple does not expose a server-initiated cancel — the user must cancel from their device.`
365
364
  );
@@ -10,7 +10,7 @@
10
10
  // `transactionId` changes per charge. We store both in payment_details for
11
11
  // audit, but uniqueness/de-dup keys off originalTransactionId.
12
12
 
13
- import { createEvent } from '../../../libs/audit';
13
+ import { createEvent, reportAuditFailure } from '../../../libs/audit';
14
14
  import logger from '../../../libs/logger';
15
15
  import { Customer, PaymentMethod, Price, Subscription, SubscriptionItem } from '../../../store/models';
16
16
  import {
@@ -156,7 +156,7 @@ export async function ingestVerifiedAppStorePurchase({
156
156
  }
157
157
  }
158
158
  await existing.update(updatePatch);
159
- createEvent('Subscription', 'customer.subscription.started', existing).catch(console.error);
159
+ createEvent('Subscription', 'customer.subscription.started', existing).catch(reportAuditFailure);
160
160
  logger.info('app_store verify: reactivated lapsed subscription from fresh transaction', {
161
161
  subscriptionId: existing.id,
162
162
  originalTransactionId: transaction.originalTransactionId,
@@ -331,7 +331,7 @@ export async function ingestVerifiedAppStorePurchase({
331
331
  } as any);
332
332
  }
333
333
 
334
- createEvent('Subscription', 'customer.subscription.started', subscription).catch(console.error);
334
+ createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
335
335
  logger.info('app_store verify: subscription created', {
336
336
  subscriptionId: subscription.id,
337
337
  customerId: customer.id,
@@ -545,7 +545,7 @@ async function handleAppStoreSubscribed({
545
545
  pending_invoice_item_interval: { interval: 'month', interval_count: 1 } as any,
546
546
  } as any);
547
547
 
548
- createEvent('Subscription', 'customer.subscription.started', subscription).catch(console.error);
548
+ createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
549
549
  logger.info('app_store SUBSCRIBED — subscription created from webhook', {
550
550
  subscriptionId: subscription.id,
551
551
  customerId: customer.id,
@@ -574,13 +574,13 @@ async function handleAppStoreRenewed(
574
574
  },
575
575
  },
576
576
  });
577
- createEvent('Subscription', 'customer.subscription.started', subscription).catch(console.error);
577
+ createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
578
578
  }
579
579
 
580
580
  async function markAppStoreExpired(subscription: Subscription): Promise<void> {
581
581
  if (['canceled', 'incomplete_expired'].includes(subscription.status as string)) return;
582
582
  await subscription.update({ status: 'canceled', ended_at: Math.floor(Date.now() / 1000) });
583
- createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(console.error);
583
+ createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(reportAuditFailure);
584
584
  }
585
585
 
586
586
  async function markAppStorePastDue(subscription: Subscription): Promise<void> {
@@ -631,5 +631,5 @@ async function handleAppStoreRevoked(
631
631
  refunded: isRefund,
632
632
  });
633
633
 
634
- createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(console.error);
634
+ createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(reportAuditFailure);
635
635
  }
@@ -26,6 +26,7 @@ import type {
26
26
  } from '@apple/app-store-server-library';
27
27
 
28
28
  import logger from '../../libs/logger';
29
+ import { appStoreSkipSignatureVerify, isProduction } from '../../libs/env';
29
30
  import { APPLE_ROOT_CERTS } from './apple-root-certs';
30
31
 
31
32
  const verifierCache = new Map<string, SignedDataVerifier>();
@@ -46,13 +47,13 @@ async function getVerifier(bundleId: string, environment: 'production' | 'sandbo
46
47
  }
47
48
 
48
49
  export function isSignatureVerificationSkipped(): boolean {
49
- if (process.env.APP_STORE_SKIP_SIGNATURE_VERIFY !== 'true') return false;
50
+ if (!appStoreSkipSignatureVerify()) return false;
50
51
  // Production fail-closed: even when the bypass flag is set we refuse to
51
52
  // honor it in production, and log loudly so the misconfiguration is
52
53
  // visible. The flag exists for unit tests / local sandbox debugging where
53
54
  // the synthetic JWS isn't signed by Apple; in production it would silently
54
55
  // downgrade a critical trust boundary to decode-only (CWE-347).
55
- if (process.env.BLOCKLET_MODE === 'production') {
56
+ if (isProduction()) {
56
57
  logger.error(
57
58
  'app_store: APP_STORE_SKIP_SIGNATURE_VERIFY=true ignored in production — JWS signature verification stays enabled'
58
59
  );
@@ -5,16 +5,30 @@ import { BN, fromTokenToUnit, fromUnitToToken, toBN, toBase58, toBase64 } from '
5
5
  import { types } from '@ocap/mcrypto';
6
6
  import { verify as verifyVC } from '@arcblock/vc';
7
7
  import { BlockletService } from '@blocklet/sdk/service/blocklet';
8
+ import env, { blockletAppHost } from '../../libs/env';
8
9
 
9
10
  import { PaymentMethod } from '../../store/models';
10
11
  import type { TPaymentCurrency } from '../../store/models';
11
12
  import { wallet } from '../../libs/auth';
12
13
  import logger from '../../libs/logger';
13
- import env from '../../libs/env';
14
14
  import { sleep } from '../../libs/util';
15
15
  import { getGasPayerExtra } from '../../libs/payment';
16
16
 
17
- const blockletService = new BlockletService();
17
+ // Lazy singleton: `new BlockletService()` runs `checkBlockletEnvironment()`,
18
+ // which throws unless BLOCKLET_APP_ID/DID/EK/ABT_NODE_* are present. Those env
19
+ // vars only exist inside a blocklet runtime — when payment-core is embedded in
20
+ // a host like arc, constructing this at module-init crashes the whole graph on
21
+ // import. Defer to first use so the import is environment-agnostic; the routing
22
+ // -rule feature (the only consumer) simply requires the host to supply that env
23
+ // when it is actually exercised.
24
+ // eslint-disable-next-line @typescript-eslint/naming-convention -- intentional _-prefixed module singleton
25
+ let _blockletService: InstanceType<typeof BlockletService> | undefined;
26
+ function getBlockletService(): InstanceType<typeof BlockletService> {
27
+ if (!_blockletService) {
28
+ _blockletService = new BlockletService();
29
+ }
30
+ return _blockletService;
31
+ }
18
32
 
19
33
  export function isOnchainCredit(paymentCurrency: TPaymentCurrency) {
20
34
  return paymentCurrency.type === 'credit' && !!paymentCurrency.token_config;
@@ -154,7 +168,7 @@ async function createTokenVC(data: { tokenAddress: string; symbol: string; websi
154
168
  * Step 2: Publish VC (Verifiable Credential) for token via routing rule
155
169
  */
156
170
  async function publishTokenVC(vc: any) {
157
- const { blocklet: blockletInfo } = await blockletService.getBlocklet();
171
+ const { blocklet: blockletInfo } = await getBlockletService().getBlocklet();
158
172
  const site = blockletInfo?.site;
159
173
 
160
174
  if (!site?.id) {
@@ -226,7 +240,7 @@ async function publishTokenVC(vc: any) {
226
240
  try {
227
241
  let result;
228
242
  if (isUpdate) {
229
- result = await blockletService.updateRoutingRule({
243
+ result = await getBlockletService().updateRoutingRule({
230
244
  id: site.id,
231
245
  rule: {
232
246
  id: existingRule.id,
@@ -234,7 +248,7 @@ async function publishTokenVC(vc: any) {
234
248
  },
235
249
  });
236
250
  } else {
237
- result = await blockletService.addRoutingRule({
251
+ result = await getBlockletService().addRoutingRule({
238
252
  id: site.id,
239
253
  rule,
240
254
  });
@@ -279,7 +293,7 @@ export async function createToken(data: { name: string; symbol: string; decimal?
279
293
  name: data.name,
280
294
  symbol: data.symbol,
281
295
  description: `Token created by ${env.appName || 'Payment Kit'}`,
282
- website: env.appUrl || process.env.BLOCKLET_APP_HOST,
296
+ website: env.appUrl || blockletAppHost(),
283
297
  icon: '',
284
298
  maxTotalSupply: null,
285
299
  decimal: data.decimal ?? 10,
@@ -307,7 +321,7 @@ export async function createToken(data: { name: string; symbol: string; decimal?
307
321
  const vc = await createTokenVC({
308
322
  tokenAddress: factoryItx.token.address,
309
323
  symbol: data.symbol,
310
- website: env.appUrl || process.env.BLOCKLET_APP_HOST!,
324
+ website: env.appUrl || blockletAppHost()!,
311
325
  chainId,
312
326
  });
313
327
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { Op } from 'sequelize';
9
9
 
10
- import { createEvent } from '../../../libs/audit';
10
+ import { createEvent, reportAuditFailure } from '../../../libs/audit';
11
11
  import logger from '../../../libs/logger';
12
12
  import { Customer, PaymentMethod, Price, Subscription, SubscriptionItem } from '../../../store/models';
13
13
  import { GooglePlayClient, GooglePlaySubscriptionPurchase } from '../client';
@@ -268,12 +268,12 @@ async function handleRenewedOrDeferred({
268
268
  },
269
269
  },
270
270
  });
271
- createEvent('Subscription', 'customer.subscription.started', subscription).catch(console.error);
271
+ createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
272
272
  }
273
273
 
274
274
  async function handleResumed(subscription: Subscription): Promise<void> {
275
275
  await subscription.update({ status: 'active' });
276
- createEvent('Subscription', 'customer.subscription.started', subscription).catch(console.error);
276
+ createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
277
277
  }
278
278
 
279
279
  async function markPastDue(subscription: Subscription): Promise<void> {
@@ -293,7 +293,7 @@ async function scheduleCancelAtPeriodEnd(subscription: Subscription): Promise<vo
293
293
  async function markExpired(subscription: Subscription): Promise<void> {
294
294
  if (['canceled', 'incomplete_expired'].includes(subscription.status as string)) return;
295
295
  await subscription.update({ status: 'canceled', canceled_at: Math.floor(Date.now() / 1000) });
296
- createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(console.error);
296
+ createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(reportAuditFailure);
297
297
  }
298
298
 
299
299
  /**
@@ -526,7 +526,7 @@ export async function ingestVerifiedGooglePlayPurchase({
526
526
  return { subscription: winner, isFirstSubscribe: false, purchase };
527
527
  }
528
528
 
529
- createEvent('Subscription', 'customer.subscription.started', subscription).catch(console.error);
529
+ createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
530
530
  logger.info('google_play verify: subscription created', {
531
531
  subscriptionId: subscription.id,
532
532
  customerId: customer.id,
@@ -561,5 +561,5 @@ async function handleRevoked(subscription: Subscription): Promise<void> {
561
561
  subscriptionId: subscription.id,
562
562
  });
563
563
 
564
- createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(console.error);
564
+ createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(reportAuditFailure);
565
565
  }
@@ -11,7 +11,7 @@
11
11
  // refundType: 1 = FULL, 2 = QUANTITY_BASED
12
12
  // }
13
13
 
14
- import { createEvent } from '../../../libs/audit';
14
+ import { createEvent, reportAuditFailure } from '../../../libs/audit';
15
15
  import logger from '../../../libs/logger';
16
16
  import { Subscription } from '../../../store/models';
17
17
 
@@ -91,7 +91,7 @@ export async function handleGooglePlayVoidedPurchase({
91
91
  google_play_voided_refund_type: refundType,
92
92
  },
93
93
  });
94
- createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(console.error);
94
+ createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(reportAuditFailure);
95
95
  }
96
96
 
97
97
  // TODO: create a Refund row for audit. Blocked on payment_intent_id schema —
@@ -9,6 +9,7 @@
9
9
  // Cloudflare Workers(payment-kit 是统一代码)。
10
10
 
11
11
  import logger from '../../libs/logger';
12
+ import { googlePubsubSkipSignatureVerify, isProduction } from '../../libs/env';
12
13
 
13
14
  export type PubSubJwtClaims = {
14
15
  iss: string;
@@ -80,7 +81,7 @@ export type VerifyOptions = {
80
81
  };
81
82
 
82
83
  function defaultSkipSignature(): boolean {
83
- return process.env.GOOGLE_PUBSUB_SKIP_SIGNATURE_VERIFY === 'true';
84
+ return googlePubsubSkipSignatureVerify();
84
85
  }
85
86
 
86
87
  /**
@@ -93,7 +94,7 @@ function defaultSkipSignature(): boolean {
93
94
  */
94
95
  function effectiveSkipSignature(requested: boolean): boolean {
95
96
  if (!requested) return false;
96
- if (process.env.BLOCKLET_MODE === 'production') {
97
+ if (isProduction()) {
97
98
  logger.error(
98
99
  'google_play: signature verification skip refused in production — Pub/Sub JWT signature verification stays enabled'
99
100
  );
@@ -20,6 +20,7 @@
20
20
  // every 5 minutes by default; tuneable via `IAP_RECONCILE_CRON_TIME`.
21
21
 
22
22
  import { Op } from 'sequelize';
23
+ import { iapReconcileBatchSize } from '../libs/env';
23
24
 
24
25
  import { createEvent } from '../libs/audit';
25
26
  import logger from '../libs/logger';
@@ -30,9 +31,6 @@ import { GooglePlayClient, GooglePlaySubscriptionPurchase } from './google-play/
30
31
  /** Don't re-check subs that were updated by a webhook within the last 5 minutes. */
31
32
  const RECENT_UPDATE_GUARD_MS = 5 * 60 * 1000;
32
33
 
33
- /** Per-channel batch cap so a single cron tick can't stall on Apple/Google rate limits. */
34
- const DEFAULT_BATCH_SIZE = Number(process.env.IAP_RECONCILE_BATCH_SIZE ?? '100');
35
-
36
34
  type ReconcileStats = { checked: number; updated: number; errors: number };
37
35
 
38
36
  const emptyStats = (): ReconcileStats => ({ checked: 0, updated: 0, errors: 0 });
@@ -149,7 +147,7 @@ async function backfillMissingSubscriptionItems(): Promise<void> {
149
147
  // App Store
150
148
  // ---------------------------------------------------------------------------
151
149
 
152
- export async function reconcileAppStore(batchSize = DEFAULT_BATCH_SIZE): Promise<ReconcileStats> {
150
+ export async function reconcileAppStore(batchSize = iapReconcileBatchSize()): Promise<ReconcileStats> {
153
151
  const stats = emptyStats();
154
152
  const methods = await PaymentMethod.findAll({ where: { type: 'app_store' } });
155
153
  if (methods.length === 0) return stats;
@@ -293,7 +291,7 @@ export async function applyAppStoreTransactionDrift(
293
291
  // Google Play
294
292
  // ---------------------------------------------------------------------------
295
293
 
296
- export async function reconcileGooglePlay(batchSize = DEFAULT_BATCH_SIZE): Promise<ReconcileStats> {
294
+ export async function reconcileGooglePlay(batchSize = iapReconcileBatchSize()): Promise<ReconcileStats> {
297
295
  const stats = emptyStats();
298
296
  const methods = await PaymentMethod.findAll({ where: { type: 'google_play' } });
299
297
  if (methods.length === 0) return stats;
@@ -6,7 +6,7 @@ import type Stripe from 'stripe';
6
6
 
7
7
  import type { WhereOptions } from 'sequelize';
8
8
  import { checkUsageReportEmpty } from '../../../libs/subscription';
9
- import { createEvent } from '../../../libs/audit';
9
+ import { createEvent, reportAuditFailure } from '../../../libs/audit';
10
10
  import { getLock } from '../../../libs/lock';
11
11
  import logger from '../../../libs/logger';
12
12
  import {
@@ -361,7 +361,7 @@ export async function handleStripeInvoiceCreated(event: TEventExpanded, client:
361
361
  createEvent('Subscription', 'usage.report.empty', subscription, {
362
362
  usageReportStart,
363
363
  usageReportEnd,
364
- }).catch(console.error);
364
+ }).catch(reportAuditFailure);
365
365
  logger.info('create usage report empty event', {
366
366
  subscriptionId: subscription.id,
367
367
  usageReportStart,
@@ -12,7 +12,7 @@ import {
12
12
  } from '../../../libs/subscription';
13
13
  import { CheckoutSession, PaymentMethod, Subscription, TEventExpanded } from '../../../store/models';
14
14
  import { getCheckoutSessionSubscriptionIds } from '../../../libs/session';
15
- import { createEvent } from '../../../libs/audit';
15
+ import { createEvent, reportAuditFailure } from '../../../libs/audit';
16
16
 
17
17
  export async function handleStripeSubscriptionSucceed(subscription: Subscription, status: string) {
18
18
  if (!subscription.payment_details?.stripe?.subscription_id) {
@@ -51,9 +51,9 @@ export async function handleStripeSubscriptionSucceed(subscription: Subscription
51
51
 
52
52
  await subscription.update({ status });
53
53
  if (subscription.trial_end && subscription.trial_end > Date.now() / 1000 && subscription.status === 'trialing') {
54
- createEvent('Subscription', 'customer.subscription.trial_start', subscription).catch(console.error);
54
+ createEvent('Subscription', 'customer.subscription.trial_start', subscription).catch(reportAuditFailure);
55
55
  } else if (subscription.status === 'active') {
56
- createEvent('Subscription', 'customer.subscription.started', subscription).catch(console.error);
56
+ createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
57
57
  }
58
58
  logger.info('subscription become active on stripe event', { id: subscription.id, status: subscription.status });
59
59
 
@@ -5,9 +5,14 @@ import path from 'path';
5
5
  import { QueryTypes, Op } from 'sequelize';
6
6
 
7
7
  import logger from '../logger';
8
+ import { getInstanceDid } from '../context';
9
+ import { getTenantMode } from '../tenant';
10
+ import { TENANT_TABLES } from '../../store/tenant-tables';
8
11
  import { ArchiveMetadata } from '../../store/models/archive-metadata';
9
12
  import { listArchiveFiles, openArchiveSequelize } from './store';
10
13
 
14
+ const TENANT_TABLE_SET: ReadonlySet<string> = new Set(TENANT_TABLES);
15
+
11
16
  type ArchiveQueryParams = {
12
17
  table: string;
13
18
  id?: string;
@@ -52,6 +57,20 @@ function buildWhereClause(params: ArchiveQueryParams) {
52
57
  replacements.customerId = params.customer_id;
53
58
  }
54
59
 
60
+ // 洞 G (Phase 4): archived rows are snapshots of tenant tables — scope the
61
+ // read by instance_did so an admin can't query another tenant's archive.
62
+ // multi mode is strict (fail-closed); single mode also accepts legacy
63
+ // pre-backfill snapshots (instance_did NULL), which all belong to the one
64
+ // deployment tenant (mirrors the resolveRowTenant single-mode default).
65
+ if (TENANT_TABLE_SET.has(params.table)) {
66
+ replacements.instance_did = getInstanceDid();
67
+ if (getTenantMode() === 'single') {
68
+ conditions.push('(instance_did = :instance_did OR instance_did IS NULL)');
69
+ } else {
70
+ conditions.push('instance_did = :instance_did');
71
+ }
72
+ }
73
+
55
74
  return { clause: conditions.join(' AND '), replacements };
56
75
  }
57
76