payment-kit 1.29.1 → 1.29.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/api/dev.ts +41 -2
  2. package/api/hono.d.ts +42 -0
  3. package/api/node-sqlite.d.ts +12 -0
  4. package/api/src/bootstrap.ts +47 -0
  5. package/api/src/crons/base.ts +3 -3
  6. package/api/src/crons/currency.ts +1 -1
  7. package/api/src/crons/index.ts +41 -37
  8. package/api/src/crons/metering-subscription-detection.ts +1 -1
  9. package/api/src/crons/overdue-detection.ts +2 -2
  10. package/api/src/crons/retry-pending-events.ts +6 -0
  11. package/api/src/crons/tenant-fanout.ts +82 -0
  12. package/api/src/host-node/did-connect-runtime-node.ts +33 -0
  13. package/api/src/host-node/serve-static-arc.ts +68 -0
  14. package/api/src/host-node/serve-static.ts +41 -0
  15. package/api/src/index.ts +22 -161
  16. package/api/src/integrations/app-store/client.ts +3 -4
  17. package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
  18. package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
  19. package/api/src/integrations/arcblock/token.ts +21 -7
  20. package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
  21. package/api/src/integrations/google-play/handlers/voided.ts +2 -2
  22. package/api/src/integrations/google-play/verify.ts +3 -2
  23. package/api/src/integrations/iap-reconcile.ts +3 -5
  24. package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
  25. package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
  26. package/api/src/libs/archive/query.ts +19 -0
  27. package/api/src/libs/audit.ts +61 -4
  28. package/api/src/libs/auth.ts +247 -47
  29. package/api/src/libs/context.ts +89 -1
  30. package/api/src/libs/currency.ts +2 -2
  31. package/api/src/libs/dayjs.ts +8 -2
  32. package/api/src/libs/did-connect/runtime-did-connect-js.ts +88 -0
  33. package/api/src/libs/did-connect/tenant-identity.ts +221 -0
  34. package/api/src/libs/drivers/auth-storage.ts +118 -0
  35. package/api/src/libs/drivers/cron.ts +264 -0
  36. package/api/src/libs/drivers/db.ts +170 -0
  37. package/api/src/libs/drivers/identity.ts +142 -0
  38. package/api/src/libs/drivers/index.ts +40 -0
  39. package/api/src/libs/drivers/locks.ts +226 -0
  40. package/api/src/libs/drivers/migrate-runner.ts +70 -0
  41. package/api/src/libs/drivers/queue.ts +104 -0
  42. package/api/src/libs/drivers/secrets.ts +194 -0
  43. package/api/src/libs/env.ts +170 -54
  44. package/api/src/libs/exchange-rate/service.ts +7 -6
  45. package/api/src/libs/http-fetch-adapter.ts +60 -0
  46. package/api/src/libs/invoice.ts +1 -1
  47. package/api/src/libs/lock.ts +51 -47
  48. package/api/src/libs/logger.ts +48 -8
  49. package/api/src/libs/notification/index.ts +1 -1
  50. package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
  51. package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
  52. package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
  53. package/api/src/libs/overdraft-protection.ts +1 -1
  54. package/api/src/libs/payout.ts +1 -1
  55. package/api/src/libs/queue/index.ts +271 -52
  56. package/api/src/libs/queue/runtime.ts +175 -0
  57. package/api/src/libs/resource.ts +3 -3
  58. package/api/src/libs/secrets.ts +38 -0
  59. package/api/src/libs/session.ts +3 -2
  60. package/api/src/libs/subscription.ts +5 -5
  61. package/api/src/libs/tenant.ts +92 -0
  62. package/api/src/libs/url.ts +3 -3
  63. package/api/src/libs/util.ts +21 -13
  64. package/api/src/middlewares/hono/cdn.ts +63 -0
  65. package/api/src/middlewares/hono/context.ts +80 -0
  66. package/api/src/middlewares/hono/csrf.ts +83 -0
  67. package/api/src/middlewares/hono/fallback.ts +194 -0
  68. package/api/src/middlewares/hono/pipeline.ts +73 -0
  69. package/api/src/middlewares/hono/resource-mount.ts +42 -0
  70. package/api/src/middlewares/hono/resource.ts +63 -0
  71. package/api/src/middlewares/hono/security.ts +209 -0
  72. package/api/src/middlewares/hono/session.ts +114 -0
  73. package/api/src/middlewares/hono/xss.ts +61 -0
  74. package/api/src/queues/auto-recharge.ts +12 -10
  75. package/api/src/queues/checkout-session.ts +38 -21
  76. package/api/src/queues/credit-consume.ts +40 -36
  77. package/api/src/queues/credit-grant.ts +25 -18
  78. package/api/src/queues/credit-reconciliation.ts +7 -5
  79. package/api/src/queues/discount-status.ts +9 -6
  80. package/api/src/queues/event.ts +41 -11
  81. package/api/src/queues/exchange-rate-health.ts +49 -30
  82. package/api/src/queues/invoice.ts +18 -15
  83. package/api/src/queues/notification.ts +14 -7
  84. package/api/src/queues/payment.ts +64 -37
  85. package/api/src/queues/payout.ts +37 -21
  86. package/api/src/queues/refund.ts +36 -18
  87. package/api/src/queues/subscription.ts +83 -53
  88. package/api/src/queues/token-transfer.ts +15 -10
  89. package/api/src/queues/usage-record.ts +8 -5
  90. package/api/src/queues/vendors/commission.ts +7 -5
  91. package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
  92. package/api/src/queues/vendors/fulfillment.ts +4 -2
  93. package/api/src/queues/vendors/return-processor.ts +5 -3
  94. package/api/src/queues/vendors/return-scanner.ts +5 -4
  95. package/api/src/queues/vendors/status-check.ts +10 -7
  96. package/api/src/queues/webhook.ts +60 -32
  97. package/api/src/routes/connect/shared.ts +1 -2
  98. package/api/src/routes/connect/subscribe.ts +3 -3
  99. package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
  100. package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
  101. package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
  102. package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
  103. package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
  104. package/api/src/routes/hono/credit-tokens.ts +43 -0
  105. package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
  106. package/api/src/routes/{customers.ts → hono/customers.ts} +199 -224
  107. package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
  108. package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
  109. package/api/src/routes/{events.ts → hono/events.ts} +107 -71
  110. package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
  111. package/api/src/routes/hono/exchange-rates.ts +77 -0
  112. package/api/src/routes/hono/index.ts +115 -0
  113. package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
  114. package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
  115. package/api/src/routes/hono/integrations/stripe.ts +74 -0
  116. package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
  117. package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
  118. package/api/src/routes/hono/meters.ts +288 -0
  119. package/api/src/routes/hono/passports.ts +73 -0
  120. package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
  121. package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
  122. package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
  123. package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
  124. package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
  125. package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
  126. package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
  127. package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
  128. package/api/src/routes/{products.ts → hono/products.ts} +172 -159
  129. package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
  130. package/api/src/routes/hono/redirect.ts +24 -0
  131. package/api/src/routes/{refunds.ts → hono/refunds.ts} +98 -83
  132. package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
  133. package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
  134. package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
  135. package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
  136. package/api/src/routes/hono/tool.ts +69 -0
  137. package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
  138. package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
  139. package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
  140. package/api/src/routes/hono/webhook-endpoints.ts +126 -0
  141. package/api/src/service.ts +814 -0
  142. package/api/src/store/migrations/20230911-seeding.ts +2 -1
  143. package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
  144. package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
  145. package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
  146. package/api/src/store/models/auto-recharge-config.ts +22 -10
  147. package/api/src/store/models/checkout-session.ts +15 -14
  148. package/api/src/store/models/coupon.ts +29 -20
  149. package/api/src/store/models/credit-grant.ts +38 -29
  150. package/api/src/store/models/credit-transaction.ts +32 -21
  151. package/api/src/store/models/customer.ts +19 -17
  152. package/api/src/store/models/discount.ts +11 -2
  153. package/api/src/store/models/entitlement-grant.ts +21 -9
  154. package/api/src/store/models/entitlement-product.ts +21 -9
  155. package/api/src/store/models/entitlement.ts +19 -10
  156. package/api/src/store/models/event.ts +18 -9
  157. package/api/src/store/models/exchange-rate-provider.ts +17 -4
  158. package/api/src/store/models/invoice-item.ts +18 -9
  159. package/api/src/store/models/invoice.ts +16 -8
  160. package/api/src/store/models/meter-event.ts +27 -9
  161. package/api/src/store/models/meter.ts +31 -22
  162. package/api/src/store/models/payment-currency.ts +25 -8
  163. package/api/src/store/models/payment-intent.ts +15 -6
  164. package/api/src/store/models/payment-link.ts +15 -6
  165. package/api/src/store/models/payment-method.ts +38 -22
  166. package/api/src/store/models/payment-stat.ts +18 -9
  167. package/api/src/store/models/payout.ts +15 -6
  168. package/api/src/store/models/price-quote.ts +17 -8
  169. package/api/src/store/models/price.ts +24 -12
  170. package/api/src/store/models/pricing-table.ts +29 -20
  171. package/api/src/store/models/product-vendor.ts +20 -10
  172. package/api/src/store/models/product.ts +15 -6
  173. package/api/src/store/models/promotion-code.ts +14 -6
  174. package/api/src/store/models/refund.ts +15 -6
  175. package/api/src/store/models/revenue-snapshot.ts +21 -9
  176. package/api/src/store/models/setting.ts +18 -9
  177. package/api/src/store/models/setup-intent.ts +36 -27
  178. package/api/src/store/models/subscription-item.ts +21 -9
  179. package/api/src/store/models/subscription-schedule.ts +21 -9
  180. package/api/src/store/models/subscription.ts +21 -10
  181. package/api/src/store/models/tax-rate.ts +29 -21
  182. package/api/src/store/models/usage-record.ts +11 -2
  183. package/api/src/store/models/webhook-attempt.ts +18 -9
  184. package/api/src/store/models/webhook-endpoint.ts +18 -9
  185. package/api/src/store/scoped-core.ts +55 -0
  186. package/api/src/store/scoped.ts +247 -0
  187. package/api/src/store/sequelize.ts +82 -23
  188. package/api/src/store/sql-migrations.ts +20 -0
  189. package/api/src/store/tenant-backfill.ts +260 -0
  190. package/api/src/store/tenant-model.ts +124 -0
  191. package/api/src/store/tenant-tables.ts +50 -0
  192. package/api/tests/bootstrap/bootstrap.spec.ts +162 -0
  193. package/api/tests/crons/tenant-fanout.spec.ts +158 -0
  194. package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
  195. package/api/tests/fixtures/bare-query-violation.ts +13 -0
  196. package/api/tests/fixtures/core-env-violation.ts +10 -0
  197. package/api/tests/fixtures/host-read-violation.ts +19 -0
  198. package/api/tests/fixtures/tenants.ts +4 -0
  199. package/api/tests/integrations/iap-tenant.spec.ts +284 -0
  200. package/api/tests/libs/archive-query.spec.ts +26 -0
  201. package/api/tests/libs/audit-tenant.spec.ts +153 -0
  202. package/api/tests/libs/context.spec.ts +204 -0
  203. package/api/tests/libs/core-config.spec.ts +115 -0
  204. package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
  205. package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
  206. package/api/tests/libs/did-connect-runtime-js.spec.ts +98 -0
  207. package/api/tests/libs/did-connect-tenant-identity.spec.ts +159 -0
  208. package/api/tests/libs/lock-tenant.spec.ts +66 -0
  209. package/api/tests/libs/scoped.spec.ts +222 -0
  210. package/api/tests/libs/secrets-facade.spec.ts +52 -0
  211. package/api/tests/libs/service-host.spec.ts +37 -0
  212. package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
  213. package/api/tests/libs/tenant-middleware.spec.ts +42 -0
  214. package/api/tests/libs/tenant-scanner.spec.ts +120 -0
  215. package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
  216. package/api/tests/middlewares/hono/context.spec.ts +113 -0
  217. package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
  218. package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
  219. package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
  220. package/api/tests/middlewares/hono/security.spec.ts +181 -0
  221. package/api/tests/middlewares/hono/session.spec.ts +42 -0
  222. package/api/tests/middlewares/hono/xss.spec.ts +81 -0
  223. package/api/tests/models/tenant-backfill.spec.ts +287 -0
  224. package/api/tests/models/tenant-columns-model.spec.ts +46 -0
  225. package/api/tests/models/tenant-columns.spec.ts +161 -0
  226. package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
  227. package/api/tests/queues/credit-consume.spec.ts +8 -1
  228. package/api/tests/queues/event-tenant.spec.ts +292 -0
  229. package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
  230. package/api/tests/queues/queue-parity.spec.ts +249 -0
  231. package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
  232. package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
  233. package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
  234. package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
  235. package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
  236. package/api/tests/service/collapse.spec.ts +96 -0
  237. package/api/tests/service/didconnect-storage-slot.spec.ts +60 -0
  238. package/api/tests/service/fail-closed-http.spec.ts +79 -0
  239. package/api/tests/service/static-arc-handler.spec.ts +101 -0
  240. package/api/tests/service/static-externalized.spec.ts +48 -0
  241. package/api/tests/store/tenant-crosscut.spec.ts +202 -0
  242. package/api/tests/store/tenant-model-spike.spec.ts +177 -0
  243. package/api/tests/store/tenant-model.spec.ts +162 -0
  244. package/api/tests/store/tenant-residual.spec.ts +196 -0
  245. package/api/third.d.ts +4 -0
  246. package/blocklet.yml +1 -1
  247. package/cloudflare/MIGRATION-RUNBOOK.md +3 -8
  248. package/cloudflare/README.md +34 -27
  249. package/cloudflare/STAGING-MIGRATION-GUIDE.md +3 -15
  250. package/cloudflare/build.ts +33 -13
  251. package/cloudflare/cf-adapter.ts +419 -0
  252. package/cloudflare/did-connect-runtime.ts +96 -0
  253. package/cloudflare/did-connect-token-storage.ts +151 -0
  254. package/cloudflare/esbuild-cf-config.cjs +407 -0
  255. package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
  256. package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
  257. package/cloudflare/migrations/0008_schema_parity.sql +16 -0
  258. package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
  259. package/cloudflare/queue-runtime-mode.ts +13 -0
  260. package/cloudflare/run-build.js +33 -403
  261. package/cloudflare/scripts/cf-package-import-probe.mjs +90 -0
  262. package/cloudflare/scripts/didconnect-mock-smoke.mjs +140 -0
  263. package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
  264. package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
  265. package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
  266. package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
  267. package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
  268. package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
  269. package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +16 -1
  270. package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +18 -3
  271. package/cloudflare/shims/cron.ts +38 -158
  272. package/cloudflare/shims/events.ts +124 -0
  273. package/cloudflare/shims/fastq.ts +15 -1
  274. package/cloudflare/shims/nedb-storage.ts +16 -8
  275. package/cloudflare/shims/xss.ts +8 -0
  276. package/cloudflare/tenant-middleware.ts +36 -0
  277. package/cloudflare/tests/cf-adapter.spec.ts +244 -0
  278. package/cloudflare/tests/did-connect-token-storage.spec.ts +105 -0
  279. package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
  280. package/cloudflare/tests/worker-handler-gate.spec.ts +69 -0
  281. package/cloudflare/vite.config.ts +53 -45
  282. package/cloudflare/worker.ts +261 -448
  283. package/cloudflare/wrangler.json +0 -6
  284. package/cloudflare/wrangler.jsonc +0 -6
  285. package/cloudflare/wrangler.local-e2e.jsonc +25 -0
  286. package/cloudflare/wrangler.staging.json +0 -6
  287. package/jest.config.js +3 -1
  288. package/package.json +33 -38
  289. package/scripts/bootstrap-inject.ts +166 -0
  290. package/scripts/core-env-whitelist.json +1 -0
  291. package/scripts/e2e-12b-runtime.ts +149 -0
  292. package/scripts/e2e-core-config.ts +125 -0
  293. package/scripts/e2e-d1-tenancy.ts +116 -0
  294. package/scripts/e2e-d2-cron-queue.ts +139 -0
  295. package/scripts/e2e-d3-embedded-multi.ts +171 -0
  296. package/scripts/e2e-hono-s2.ts +125 -0
  297. package/scripts/e2e-hono-s3e.ts +135 -0
  298. package/scripts/e2e-hono-s4.ts +114 -0
  299. package/scripts/e2e-migration-contract.ts +100 -0
  300. package/scripts/e2e-s0.ts +61 -0
  301. package/scripts/e2e-s1.ts +107 -0
  302. package/scripts/e2e-s2.ts +178 -0
  303. package/scripts/e2e-s3.ts +110 -0
  304. package/scripts/e2e-s4.ts +191 -0
  305. package/scripts/e2e-s5.ts +139 -0
  306. package/scripts/e2e-s6.ts +127 -0
  307. package/scripts/e2e-tenant-model.ts +119 -0
  308. package/scripts/e2e-tenant-worker.ts +199 -0
  309. package/scripts/gen-sql-migrations.js +46 -0
  310. package/scripts/phase8-codemod.js +219 -0
  311. package/scripts/phase9a-env-getters-codemod.js +82 -0
  312. package/scripts/scan-core-env.js +109 -0
  313. package/scripts/scan-tenant-queries.js +235 -0
  314. package/scripts/schema-drift-guard.ts +210 -0
  315. package/scripts/tenant-scan-whitelist.json +1 -0
  316. package/src/app.tsx +2 -1
  317. package/src/env.d.ts +13 -1
  318. package/src/libs/service-host.ts +13 -0
  319. package/tsconfig.json +1 -1
  320. package/vite.arc.config.ts +159 -0
  321. package/api/src/libs/did-space.ts +0 -235
  322. package/api/src/libs/middleware.ts +0 -50
  323. package/api/src/libs/security.ts +0 -192
  324. package/api/src/queues/space.ts +0 -662
  325. package/api/src/routes/credit-tokens.ts +0 -38
  326. package/api/src/routes/exchange-rates.ts +0 -87
  327. package/api/src/routes/index.ts +0 -142
  328. package/api/src/routes/integrations/stripe.ts +0 -61
  329. package/api/src/routes/meters.ts +0 -274
  330. package/api/src/routes/passports.ts +0 -68
  331. package/api/src/routes/redirect.ts +0 -20
  332. package/api/src/routes/tool.ts +0 -65
  333. package/api/src/routes/webhook-endpoints.ts +0 -126
  334. package/api/tests/routes/credit-grants.spec.ts +0 -1261
  335. package/cloudflare/did-connect-auth.ts +0 -527
  336. package/cloudflare/shims/did-space-js.ts +0 -17
  337. package/cloudflare/shims/did-space.ts +0 -11
  338. package/cloudflare/shims/express-compat/index.ts +0 -80
  339. package/cloudflare/shims/express-compat/types.ts +0 -41
  340. package/cloudflare/shims/lock.ts +0 -115
  341. package/cloudflare/shims/queue.ts +0 -611
  342. package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
  343. package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
@@ -1,68 +1,184 @@
1
1
  import { env } from '@blocklet/sdk/lib/env';
2
2
 
3
- export const paymentStatCronTime: string = '0 1 0 * * *'; // 默认每天一次,计算前一天的
4
- export const subscriptionCronTime: string = process.env.SUBSCRIPTION_CRON_TIME || '0 */30 * * * *'; // 默认每 30 min 行一次
5
- export const notificationCronTime: string = process.env.NOTIFICATION_CRON_TIME || '0 5 */6 * * *'; // 默认每6个小时执行一次
6
- export const expiredSessionCleanupCronTime: string = process.env.EXPIRED_SESSION_CLEANUP_CRON_TIME || '0 1 * * * *'; // 默认每小时执行一次
7
- export const notificationCronConcurrency: number = Number(process.env.NOTIFICATION_CRON_CONCURRENCY) || 8; // 默认并发数为 8
8
- export const stripeInvoiceCronTime: string = process.env.STRIPE_INVOICE_CRON_TIME || '0 */30 * * * *'; // 默认每 30min 执行一次
9
- export const stripePaymentCronTime: string = process.env.STRIPE_PAYMENT_CRON_TIME || '0 */20 * * * *'; // 默认每 20min 执行一次
10
- export const stripeSubscriptionCronTime: string = process.env.STRIPE_SUBSCRIPTION_CRON_TIME || '0 10 */8 * * *'; // 默认每 8小时 执行一次
11
- export const revokeStakeCronTime: string = process.env.REVOKE_STAKE_CRON_TIME || '0 */5 * * * *'; // 默认每 5 min 执行一次
12
- export const daysUntilCancel: string | undefined = process.env.DAYS_UNTIL_CANCEL;
13
- export const meteringSubscriptionDetectionCronTime: string =
14
- process.env.METERING_SUBSCRIPTION_DETECTION_CRON_TIME || '0 0 10 * * *'; // 默认每天 10:00 执行
15
- export const overdueDetectionCronTime: string = process.env.OVERDUE_DETECTION_CRON_TIME || '0 0 10 * * *'; // 默认每天 10:00 执行
16
- export const overdueThreshold: number = process.env.OVERDUE_THRESHOLD ? +process.env.OVERDUE_THRESHOLD : 5; // 默认超额阈值为 5
17
- export const depositVaultCronTime: string = process.env.DEPOSIT_VAULT_CRON_TIME || '0 */5 * * * *'; // 默认每 5 min 执行一次
18
- export const creditConsumptionCronTime: string = process.env.CREDIT_CONSUMPTION_CRON_TIME || '0 */10 * * * *'; // 默认每 10 min 执行一次
19
- export const vendorStatusCheckCronTime: string = process.env.VENDOR_STATUS_CHECK_CRON_TIME || '0 */10 * * * *'; // 默认每 10 min 执行一次
20
- export const vendorReturnScanCronTime: string = process.env.VENDOR_RETURN_SCAN_CRON_TIME || '0 */10 * * * *'; // 默认每 10 min 执行一次
21
- export const iapReconcileCronTime: string = process.env.IAP_RECONCILE_CRON_TIME || '0 */5 * * * *'; // 默认每 5 min 执行一次:webhook 兜底,拉 App Store / Google Play 订阅最新状态
22
- export const eventRetryCronTime: string = process.env.EVENT_RETRY_CRON_TIME || '30 */5 * * * *'; // 默认每 5 min 执行一次(错开整点避开 iap-reconcile):扫 pending_webhooks>0 的事件兜底投递
23
- export const quoteCleanupCronTime: string = process.env.QUOTE_CLEANUP_CRON_TIME || '0 0 2 * * *'; // 默认每天凌晨 2 点执行一次
24
- export const vendorTimeoutMinutes: number = process.env.VENDOR_TIMEOUT_MINUTES
25
- ? +process.env.VENDOR_TIMEOUT_MINUTES
26
- : 10; // 默认 10 分钟超时
27
- export const webhookAlertWindowMinutes: number = process.env.WEBHOOK_ALERT_WINDOW_MINUTES
28
- ? +process.env.WEBHOOK_ALERT_WINDOW_MINUTES
29
- : 10; // webhook 连续失败告警时间窗口,默认 10 分钟
30
- export const webhookAlertMinFailures: number = process.env.WEBHOOK_ALERT_MIN_FAILURES
31
- ? +process.env.WEBHOOK_ALERT_MIN_FAILURES
32
- : 3; // webhook 触发告警的最小失败次数,默认 3
33
-
34
- export const shortUrlApiKey: string = process.env.SHORT_URL_API_KEY || '';
35
- export const shortUrlDomain: string = process.env.SHORT_URL_DOMAIN || 's.abtnet.io';
3
+ // ──────────────────────────────────────────────────────────────────────────
4
+ // Phase 8 (W2′): the injected-config slot, made authoritative here.
5
+ //
6
+ // libs/env.ts is the ONE module the core-env scanner exempts the single
7
+ // allowed reader of process.env / the CF env mirror. Phase 8 converges every
8
+ // scattered `process.env.X` read in api/src INTO the accessors below so the
9
+ // whitelist goes to zero.
10
+ //
11
+ // The factory (createEmbeddedPaymentService) calls setCoreConfig(config) so the
12
+ // injected config object becomes the source of truth. Reads fall back to
13
+ // process.env when a key is absent from the injected config — which keeps every
14
+ // host working unchanged: the blocklet server populates process.env natively,
15
+ // and the CF worker mirrors CF env into process.env per request (that mirror is
16
+ // deleted in Phase 12, once the worker routes through the factory config slot).
17
+ // ──────────────────────────────────────────────────────────────────────────
18
+ let activeConfig: Record<string, any> | undefined;
19
+
20
+ /** Wire the injected config object (factory only). Pass undefined to clear (tests). */
21
+ export function setCoreConfig(config: Record<string, any> | undefined): void {
22
+ activeConfig = config;
23
+ }
24
+
25
+ /** The injected config object, or undefined before the factory has run. */
26
+ export function getCoreConfig(): Record<string, any> | undefined {
27
+ return activeConfig;
28
+ }
29
+
30
+ /**
31
+ * Read a config key: the injected config wins; otherwise the process.env
32
+ * fallback (blocklet server native / worker mirror). Returns a string or
33
+ * undefined — callers parse/typecheck. This is the single boundary read.
34
+ */
35
+ export function readConfig(key: string): string | undefined {
36
+ const injected = activeConfig?.[key];
37
+ if (injected !== undefined && injected !== null) return String(injected);
38
+ const fromEnv = process.env[key];
39
+ return fromEnv === undefined ? undefined : fromEnv;
40
+ }
41
+
42
+ /** True iff the key is present (non-empty) in injected config or process.env. */
43
+ export function hasConfig(key: string): boolean {
44
+ const v = readConfig(key);
45
+ return v !== undefined && v !== '';
46
+ }
47
+
48
+ // ──────────────────────────────────────────────────────────────────────────
49
+ // P1 (9a review fix): these were import-time `process.env` consts — frozen
50
+ // before the factory could setCoreConfig, so injected config could never
51
+ // override them (now that 9a bundles the canonical core into the published
52
+ // package, that mattered for arc). They are LAZY getters now, reading via the
53
+ // readConfig boundary, honored at call time. Consumers call them at use time.
54
+ // ──────────────────────────────────────────────────────────────────────────
55
+ const numConfig = (key: string, fallback: number): number => {
56
+ const v = readConfig(key);
57
+ return v ? +v : fallback;
58
+ };
59
+
60
+ export const paymentStatCronTime = (): string => '0 1 0 * * *'; // 默认每天一次,计算前一天的
61
+ export const subscriptionCronTime = (): string => readConfig('SUBSCRIPTION_CRON_TIME') || '0 */30 * * * *';
62
+ export const notificationCronTime = (): string => readConfig('NOTIFICATION_CRON_TIME') || '0 5 */6 * * *';
63
+ export const expiredSessionCleanupCronTime = (): string =>
64
+ readConfig('EXPIRED_SESSION_CLEANUP_CRON_TIME') || '0 1 * * * *';
65
+ export const notificationCronConcurrency = (): number => Number(readConfig('NOTIFICATION_CRON_CONCURRENCY')) || 8;
66
+ export const stripeInvoiceCronTime = (): string => readConfig('STRIPE_INVOICE_CRON_TIME') || '0 */30 * * * *';
67
+ export const stripePaymentCronTime = (): string => readConfig('STRIPE_PAYMENT_CRON_TIME') || '0 */20 * * * *';
68
+ export const stripeSubscriptionCronTime = (): string => readConfig('STRIPE_SUBSCRIPTION_CRON_TIME') || '0 10 */8 * * *';
69
+ export const revokeStakeCronTime = (): string => readConfig('REVOKE_STAKE_CRON_TIME') || '0 */5 * * * *';
70
+ export const daysUntilCancel = (): string | undefined => readConfig('DAYS_UNTIL_CANCEL');
71
+ export const meteringSubscriptionDetectionCronTime = (): string =>
72
+ readConfig('METERING_SUBSCRIPTION_DETECTION_CRON_TIME') || '0 0 10 * * *';
73
+ export const overdueDetectionCronTime = (): string => readConfig('OVERDUE_DETECTION_CRON_TIME') || '0 0 10 * * *';
74
+ export const overdueThreshold = (): number => numConfig('OVERDUE_THRESHOLD', 5);
75
+ export const depositVaultCronTime = (): string => readConfig('DEPOSIT_VAULT_CRON_TIME') || '0 */5 * * * *';
76
+ export const creditConsumptionCronTime = (): string => readConfig('CREDIT_CONSUMPTION_CRON_TIME') || '0 */10 * * * *';
77
+ export const vendorStatusCheckCronTime = (): string => readConfig('VENDOR_STATUS_CHECK_CRON_TIME') || '0 */10 * * * *';
78
+ export const vendorReturnScanCronTime = (): string => readConfig('VENDOR_RETURN_SCAN_CRON_TIME') || '0 */10 * * * *';
79
+ export const iapReconcileCronTime = (): string => readConfig('IAP_RECONCILE_CRON_TIME') || '0 */5 * * * *';
80
+ export const eventRetryCronTime = (): string => readConfig('EVENT_RETRY_CRON_TIME') || '30 */5 * * * *';
81
+ export const quoteCleanupCronTime = (): string => readConfig('QUOTE_CLEANUP_CRON_TIME') || '0 0 2 * * *';
82
+ export const vendorTimeoutMinutes = (): number => numConfig('VENDOR_TIMEOUT_MINUTES', 10);
83
+ export const webhookAlertWindowMinutes = (): number => numConfig('WEBHOOK_ALERT_WINDOW_MINUTES', 10);
84
+ export const webhookAlertMinFailures = (): number => numConfig('WEBHOOK_ALERT_MIN_FAILURES', 3);
85
+
86
+ export const shortUrlApiKey = (): string => readConfig('SHORT_URL_API_KEY') || '';
87
+ export const shortUrlDomain = (): string => readConfig('SHORT_URL_DOMAIN') || 's.abtnet.io';
36
88
 
37
89
  // sequelize 配置相关
38
- export const sequelizeOptionsPoolMin: number = process.env.SEQUELIZE_OPTIONS_POOL_MIN
39
- ? +process.env.SEQUELIZE_OPTIONS_POOL_MIN
40
- : 0;
41
- export const sequelizeOptionsPoolMax: number = process.env.SEQUELIZE_OPTIONS_POOL_MAX
42
- ? +process.env.SEQUELIZE_OPTIONS_POOL_MAX
43
- : 5;
44
- export const sequelizeOptionsPoolIdle: number = process.env.SEQUELIZE_OPTIONS_POOL_IDLE
45
- ? +process.env.SEQUELIZE_OPTIONS_POOL_IDLE
46
- : 10 * 1000;
47
-
48
- export const updateDataConcurrency: number = process.env.UPDATE_DATA_CONCURRENCY
49
- ? +process.env.UPDATE_DATA_CONCURRENCY
50
- : 5; // 默认并发数为 5
90
+ export const sequelizeOptionsPoolMin = (): number => numConfig('SEQUELIZE_OPTIONS_POOL_MIN', 0);
91
+ export const sequelizeOptionsPoolMax = (): number => numConfig('SEQUELIZE_OPTIONS_POOL_MAX', 5);
92
+ export const sequelizeOptionsPoolIdle = (): number => numConfig('SEQUELIZE_OPTIONS_POOL_IDLE', 10 * 1000);
93
+
94
+ export const updateDataConcurrency = (): number => numConfig('UPDATE_DATA_CONCURRENCY', 5);
51
95
 
52
96
  // When set to 'true' or '1', the system stops accepting new orders.
53
97
  // Existing checkout sessions can still be viewed but new submissions will be rejected.
54
- export const stopAcceptingOrders: boolean =
55
- process.env.PAYMENT_KIT_STOP_ACCEPTING_ORDERS === 'true' || process.env.PAYMENT_KIT_STOP_ACCEPTING_ORDERS === '1';
98
+ export const stopAcceptingOrders = (): boolean =>
99
+ readConfig('PAYMENT_KIT_STOP_ACCEPTING_ORDERS') === 'true' || readConfig('PAYMENT_KIT_STOP_ACCEPTING_ORDERS') === '1';
56
100
 
57
- export const exchangeRateCacheTTLSeconds: number = process.env.EXCHANGE_RATE_CACHE_TTL_SECONDS
58
- ? +process.env.EXCHANGE_RATE_CACHE_TTL_SECONDS
59
- : 10 * 60;
101
+ export const exchangeRateCacheTTLSeconds = (): number => numConfig('EXCHANGE_RATE_CACHE_TTL_SECONDS', 10 * 60);
60
102
 
61
103
  // System-level maximum pending amount limit (in token format, e.g., "10")
62
104
  // Default is 0 (disabled). Set PAYMENT_KIT_MAX_PENDING_AMOUNT to enable this limit.
63
- export const systemMaxPendingAmount: number = process.env.PAYMENT_KIT_MAX_PENDING_AMOUNT
64
- ? +process.env.PAYMENT_KIT_MAX_PENDING_AMOUNT
65
- : 5;
105
+ export const systemMaxPendingAmount = (): number => numConfig('PAYMENT_KIT_MAX_PENDING_AMOUNT', 5);
106
+
107
+ // Whether a locked price may still be edited. Lazy (reads injected config via
108
+ // the boundary at call time) like every other Phase 8 accessor.
109
+ export const allowChangeLockedPrice = (): boolean => readConfig('PAYMENT_CHANGE_LOCKED_PRICE') === '1';
110
+
111
+ // ──────────────────────────────────────────────────────────────────────────
112
+ // Phase 8 (W2′): converged accessors. Every read below used to live inline in
113
+ // api/src as a direct process.env / __CF_ENV__ access (the 57-entry whitelist).
114
+ // They are LAZY (functions, not import-time consts) so the injected config —
115
+ // wired by the factory AFTER module import — is honored at call time, and so
116
+ // tests that flip process.env at runtime keep working.
117
+ // ──────────────────────────────────────────────────────────────────────────
118
+
119
+ // -- runtime mode / environment --
120
+ export const blockletMode = (): string | undefined => readConfig('BLOCKLET_MODE');
121
+ export const isProduction = (): boolean => blockletMode() === 'production';
122
+ export const nodeEnv = (): string | undefined => readConfig('NODE_ENV');
123
+ export const isTestEnv = (): boolean => nodeEnv() === 'test';
124
+ export const isDevelopmentEnv = (): boolean => nodeEnv() === 'development';
125
+ export const enableDevFakeAuth = (): boolean => readConfig('ENABLE_DEV_FAKE_AUTH') === '1';
126
+
127
+ // -- tenant mode-source (getTenantMode / getDefaultInstanceDid read these) --
128
+ export const tenantModeRaw = (): string | undefined => readConfig('PAYMENT_TENANT_MODE');
129
+ export const blockletAppPid = (): string | undefined => readConfig('BLOCKLET_APP_PID');
130
+
131
+ // -- app identity / urls --
132
+ export const blockletAppId = (): string | undefined => readConfig('BLOCKLET_APP_ID');
133
+ export const blockletAppName = (): string | undefined => readConfig('BLOCKLET_APP_NAME');
134
+ export const blockletAppUrl = (): string | undefined => readConfig('BLOCKLET_APP_URL');
135
+ export const blockletAppHost = (): string | undefined => readConfig('BLOCKLET_APP_HOST');
136
+ export const blockletAppDir = (): string | undefined => readConfig('BLOCKLET_APP_DIR');
137
+ export const blockletPort = (): string | undefined => readConfig('BLOCKLET_PORT');
138
+ export const blockletMountPoints = (): string | undefined => readConfig('BLOCKLET_MOUNT_POINTS');
139
+
140
+ // -- integrations --
141
+ export const appStoreWriteEnabled = (): boolean => readConfig('APP_STORE_WRITE_ENABLED') === 'true';
142
+ export const appStoreSkipSignatureVerify = (): boolean => readConfig('APP_STORE_SKIP_SIGNATURE_VERIFY') === 'true';
143
+ export const googlePubsubSkipSignatureVerify = (): boolean =>
144
+ readConfig('GOOGLE_PUBSUB_SKIP_SIGNATURE_VERIFY') === 'true';
145
+ export const googlePubsubPushServiceAccount = (): string | undefined =>
146
+ readConfig('GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT');
147
+ export const googlePubsubAllowUnverifiedSender = (): boolean =>
148
+ readConfig('GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER') === 'true';
149
+ export const googlePlayWebhookUrl = (): string | undefined => readConfig('GOOGLE_PLAY_WEBHOOK_URL');
150
+ export const stripeWebhookSecret = (): string | undefined => readConfig('STRIPE_WEBHOOK_SECRET');
151
+ export const iapReconcileBatchSize = (): number => Number(readConfig('IAP_RECONCILE_BATCH_SIZE') ?? '100');
152
+
153
+ // -- payment params --
154
+ export const paymentBillingThreshold = (): number => +(readConfig('PAYMENT_BILLING_THRESHOLD') as string);
155
+ export const paymentMinStakeAmount = (): number => +(readConfig('PAYMENT_MIN_STAKE_AMOUNT') as string);
156
+ export const paymentDaysUntilDue = (): string | undefined => readConfig('PAYMENT_DAYS_UNTIL_DUE');
157
+ export const paymentDaysUntilCancel = (): string | undefined => readConfig('PAYMENT_DAYS_UNTIL_CANCEL');
158
+ export const paymentReloadSubscriptionJobs = (): boolean => readConfig('PAYMENT_RELOAD_SUBSCRIPTION_JOBS') === '1';
159
+ export const paymentRateVolatilityThreshold = (): string | undefined => readConfig('PAYMENT_RATE_VOLATILITY_THRESHOLD');
160
+ export const paymentLivemode = (): boolean => readConfig('PAYMENT_LIVEMODE') !== 'false';
161
+
162
+ // -- credit queue --
163
+ export const creditLowBalanceThresholdPercentage = (): number =>
164
+ parseInt(readConfig('CREDIT_LOW_BALANCE_THRESHOLD_PERCENTAGE') || '10', 10);
165
+ export const creditBatchSize = (): number => Math.max(1, parseInt(readConfig('CREDIT_BATCH_SIZE') || '50', 10));
166
+ export const creditBatchWindowMs = (): number =>
167
+ Math.max(10, parseInt(readConfig('CREDIT_BATCH_WINDOW_MS') || '3000', 10));
168
+ export const creditQueueConcurrency = (): number =>
169
+ Math.max(1, Math.min(20, parseInt(readConfig('CREDIT_QUEUE_CONCURRENCY') || '5', 10) || 5));
170
+
171
+ // -- exchange rate cache TTL source (the value itself is exchangeRateCacheTTLSeconds above) --
172
+ export const exchangeRateCacheTTLFromEnv = (): boolean => hasConfig('EXCHANGE_RATE_CACHE_TTL_SECONDS');
173
+
174
+ // -- store / sequelize logging --
175
+ export const sqlLog = (): boolean => readConfig('SQL_LOG') === '1';
176
+ export const sqlBenchmark = (): boolean => readConfig('SQL_BENCHMARK') === '1';
177
+
178
+ // -- CF worker runtime detection + env mirror (set by cloudflare/worker.ts on
179
+ // globalThis; the boundary so core never reads the global directly) --
180
+ export const cfEnv = (): any => (globalThis as any).__CF_ENV__;
181
+ export const isCfWorker = (): boolean => !!cfEnv();
66
182
 
67
183
  export default {
68
184
  ...env,
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable no-await-in-loop */
2
2
  import BigNumber from 'bignumber.js';
3
+ import { exchangeRateCacheTTLFromEnv, exchangeRateCacheTTLSeconds } from '../env';
3
4
  import { ExchangeRateProvider } from '../../store/models/exchange-rate-provider';
4
5
  import logger from '../logger';
5
6
  import { events } from '../event';
@@ -8,15 +9,15 @@ import { SymbolNotSupportedError } from './types';
8
9
  import { TokenDataProvider } from './token-data-provider';
9
10
  import { CoinGeckoProvider } from './coingecko-provider';
10
11
  import { CoinMarketCapProvider } from './coinmarketcap-provider';
11
- import { exchangeRateCacheTTLSeconds } from '../env';
12
12
 
13
13
  interface CacheEntry {
14
14
  data: ExchangeRateResult;
15
15
  timestamp: number;
16
16
  }
17
17
 
18
- // Cache TTL from environment variable, default 10 minutes
19
- const CACHE_TTL_MS = (exchangeRateCacheTTLSeconds || 10 * 60) * 1000;
18
+ // Cache TTL from config, default 10 minutes. Lazy so injected config (set after
19
+ // module import) is honored not frozen in a module-level const.
20
+ const cacheTtlMs = (): number => (exchangeRateCacheTTLSeconds() || 10 * 60) * 1000;
20
21
  const MAX_RATE_AGE_MS = 5 * 60 * 1000; // 5 minutes
21
22
  const MAX_DEVIATION_PERCENT = 5; // 5%
22
23
  const HISTORY_WINDOW_SIZE = 10;
@@ -185,7 +186,7 @@ export class ExchangeRateService {
185
186
 
186
187
  // Check cache first
187
188
  const cached = this.cache.get(cacheKey);
188
- if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
189
+ if (cached && Date.now() - cached.timestamp < cacheTtlMs()) {
189
190
  logger.debug('Exchange rate cache hit', { symbol, age: Date.now() - cached.timestamp });
190
191
  return cached.data;
191
192
  }
@@ -575,8 +576,8 @@ export function getExchangeRateService(): ExchangeRateService {
575
576
  if (!serviceInstance) {
576
577
  serviceInstance = new ExchangeRateService();
577
578
  logger.info('Exchange rate service initialized', {
578
- cache_ttl_seconds: CACHE_TTL_MS / 1000,
579
- cache_ttl_source: process.env.EXCHANGE_RATE_CACHE_TTL_SECONDS ? 'env' : 'default',
579
+ cache_ttl_seconds: cacheTtlMs() / 1000,
580
+ cache_ttl_source: exchangeRateCacheTTLFromEnv() ? 'env' : 'default',
580
581
  });
581
582
  }
582
583
  return serviceInstance;
@@ -0,0 +1,60 @@
1
+ // D5 — the Web-Fetch entry for hosts that own their own app shell (arc-node's
2
+ // registerPrefixHandler): `svc.http.fetch(req, {basePath})` with no express bridge.
3
+ //
4
+ // Phase 4 (express→hono): the loopback http.Server + express app are gone, so this
5
+ // is just a base-strip wrapper over `honoApp.fetch`. The outward signature +
6
+ // strip semantics are unchanged (arc consumes this contract); raw-body fidelity
7
+ // (Stripe webhook signature) holds because the stripped path reuses the exact
8
+ // request bytes/headers, and status/Set-Cookie/redirect come from the hono Response.
9
+
10
+ import type { Hono } from 'hono';
11
+
12
+ /** Options for svc.http.fetch — basePath is stripped to reach the internal /api/*. */
13
+ export interface PaymentFetchOptions {
14
+ /** the host's mount prefix (e.g. "/.well-known/payment"); stripped, no alias special-case */
15
+ basePath?: string;
16
+ }
17
+
18
+ export interface FetchHandler {
19
+ (request: Request, opts?: PaymentFetchOptions): Promise<Response>;
20
+ /** teardown hook (host lifecycle). No-op now that there is no loopback server. */
21
+ close(): Promise<void>;
22
+ }
23
+
24
+ export function createFetchHandler(app: Hono): FetchHandler {
25
+ const handler = (async (request: Request, opts?: PaymentFetchOptions): Promise<Response> => {
26
+ const url = new URL(request.url);
27
+
28
+ // ① base-strip: remove basePath → internal /api/* path (no alias special-case).
29
+ // Precise segment-boundary check (=== basePath || startsWith(basePath + '/'))
30
+ // so "/foo" never matches "/foobar" — byte-identical to the old loopback strip.
31
+ const basePath = opts?.basePath ?? '';
32
+ if (basePath && (url.pathname === basePath || url.pathname.startsWith(`${basePath}/`))) {
33
+ url.pathname = url.pathname.slice(basePath.length) || '/';
34
+ // ② rebuild the request at the stripped URL. The body is read ONCE into a
35
+ // fixed buffer (raw bytes preserved for Stripe webhook signature) and the
36
+ // original headers/method are carried verbatim (Host preserved).
37
+ const method = request.method.toUpperCase();
38
+ const hasBody = method !== 'GET' && method !== 'HEAD';
39
+ const body = hasBody ? await request.arrayBuffer() : undefined;
40
+ // ③ re-expose the stripped mount prefix as `x-path-prefix` so internal
41
+ // absolute-URL builders reconstruct the PUBLIC url. The DID-Connect
42
+ // authenticator's wallet callback URL is built by did-connect-js
43
+ // `prepareBaseUrl`, which reads `x-path-prefix`; without it the wallet is
44
+ // told to call <origin>/api/did/payment/auth instead of
45
+ // <origin><basePath>/api/did/payment/auth and the connect times out. The
46
+ // Request's headers are immutable, so set it on a fresh Headers; never
47
+ // clobber a host-provided value (blocklet-server sets its own mount).
48
+ const headers = new Headers(request.headers);
49
+ if (!headers.has('x-path-prefix')) headers.set('x-path-prefix', basePath);
50
+ return app.fetch(new Request(url.toString(), { method, headers, body }));
51
+ }
52
+
53
+ // no strip needed — hand the request straight to hono (it owns body parsing).
54
+ return app.fetch(request);
55
+ }) as FetchHandler;
56
+
57
+ handler.close = (): Promise<void> => Promise.resolve();
58
+
59
+ return handler;
60
+ }
@@ -1,4 +1,4 @@
1
- import { component } from '@blocklet/sdk';
1
+ import component from '@blocklet/sdk/lib/component';
2
2
  import type { LiteralUnion } from 'type-fest';
3
3
  import { withQuery } from 'ufo';
4
4
 
@@ -1,53 +1,57 @@
1
- const { EventEmitter } = require('events');
2
-
3
- export class Lock {
4
- name: string;
5
- locked: boolean;
6
- events: typeof EventEmitter;
7
-
8
- constructor(name: string) {
9
- this.name = name;
10
- this.locked = false;
11
- this.events = new EventEmitter();
12
- }
13
-
14
- acquire() {
15
- return new Promise((resolve) => {
16
- // If somebody has the lock, wait until he/she releases the lock and try again
17
- if (this.locked) {
18
- const tryAcquire = () => {
19
- if (!this.locked) {
20
- this.locked = true;
21
- this.events.removeListener('release', tryAcquire);
22
- resolve(true);
23
- }
24
- };
1
+ // Phase 8 (W2-1a): lock facade over the locks slot driver.
2
+ //
3
+ // Call sites keep using `getLock(name)` unchanged. Behind the facade:
4
+ // - the active locks driver is injectable (`setLocksDriver`) — default is the
5
+ // in-process memory driver (Blocklet Server / Node). The worker injects the
6
+ // D1 driver through the locks slot.
7
+ // - lock names are tenant-prefixed (W1 §2.2) by default. Per-resource locks
8
+ // are tenant-scoped; process-level singleton guards (queue start guards,
9
+ // recovery) opt out with `{ scope: 'global' }`. In single-tenant mode the
10
+ // prefix is the constant deployment app DID, so lock identity is unchanged.
11
+
12
+ import { getInstanceDid } from './context';
13
+ import {
14
+ createMemoryLocksDriver,
15
+ scopedLockName,
16
+ MemoryLock,
17
+ type LockHandle,
18
+ type LocksDriver,
19
+ type LockScope,
20
+ } from './drivers';
21
+
22
+ let activeDriver: LocksDriver = createMemoryLocksDriver();
23
+
24
+ /** Inject the locks driver (worker wires the D1 driver here). */
25
+ export function setLocksDriver(driver: LocksDriver): void {
26
+ activeDriver = driver;
27
+ }
25
28
 
26
- this.events.on('release', tryAcquire);
27
- } else {
28
- // Otherwise, take the lock and resolve immediately
29
- this.locked = true;
30
- resolve(true);
31
- }
32
- });
33
- }
29
+ export function getLocksDriver(): LocksDriver {
30
+ return activeDriver;
31
+ }
34
32
 
35
- release() {
36
- // Release the lock immediately
37
- this.locked = false;
38
- setImmediate(() => this.events.emit('release'));
39
- }
33
+ export interface GetLockOptions {
34
+ ttl?: number;
35
+ /** 'tenant' (default) prefixes the lock name with the current tenant; 'global' keeps the bare name. */
36
+ scope?: LockScope;
40
37
  }
41
38
 
42
- const locks = new Map<string, Lock>();
43
- export function getLock(name: string): Lock {
44
- const exist = locks.get(name);
45
- if (exist instanceof Lock) {
46
- return exist;
39
+ /**
40
+ * Acquire a handle to a named lock. Tenant-scoped by default; resolving the
41
+ * tenant fails closed in multi-tenant mode when no context is present (matching
42
+ * the scoped-query helpers from Phase 3).
43
+ */
44
+ export function getLock(name: string, options: GetLockOptions = {}): LockHandle {
45
+ if (!name || typeof name !== 'string') {
46
+ throw new Error('getLock: a non-empty lock name is required');
47
47
  }
48
-
49
- const lock = new Lock(name);
50
- locks.set(name, lock);
51
-
52
- return lock;
48
+ const scope: LockScope = options.scope ?? 'tenant';
49
+ const instanceDid = scope === 'tenant' ? getInstanceDid() : null;
50
+ const resolvedName = scopedLockName(name, instanceDid, scope);
51
+ return activeDriver.getLock(resolvedName, options.ttl !== undefined ? { ttl: options.ttl } : undefined);
53
52
  }
53
+
54
+ // `Lock` is the in-process memory lock class — kept as a public export so the
55
+ // standalone constructor form (`new Lock(name)`) and `LockHandle` annotations
56
+ // both keep working.
57
+ export { MemoryLock as Lock };
@@ -1,4 +1,13 @@
1
- const createLogger = require('@blocklet/logger');
1
+ // Phase 13b: lazy logging boundary.
2
+ //
3
+ // @blocklet/logger throws at REQUIRE time when BLOCKLET_LOG_DIR is absent
4
+ // ("valid BLOCKLET_LOG_DIR env is required by logger"). That made importing the
5
+ // core — and therefore createEmbeddedPaymentService / rpc.entitlements.check,
6
+ // which transitively import this module — demand a blocklet log env even for a
7
+ // bare host (arc embedding before its blocklet runtime is wired). Defer the
8
+ // require to first use and fall back to console when no blocklet log env exists.
9
+ // The blocklet server (BLOCKLET_LOG_DIR set) and the CF worker (aliased shim)
10
+ // still get the real logger on first call — behavior unchanged for them.
2
11
 
3
12
  interface Logger {
4
13
  debug: (...args: any[]) => void;
@@ -7,14 +16,45 @@ interface Logger {
7
16
  warn: (...args: any[]) => void;
8
17
  }
9
18
 
10
- const init = (label: string): Logger => {
11
- const instance = createLogger(label || '');
12
- return instance;
13
- };
19
+ let resolved: Logger | undefined;
14
20
 
15
- const logger = init('app');
21
+ function resolveLogger(): Logger {
22
+ if (resolved) return resolved;
23
+ try {
24
+ // eslint-disable-next-line global-require, import/no-extraneous-dependencies
25
+ const createLogger = require('@blocklet/logger');
26
+ resolved = createLogger('app') as Logger;
27
+ } catch {
28
+ // bare host without a blocklet log env — console fallback.
29
+ /* eslint-disable no-console */
30
+ resolved = {
31
+ debug: (...args: any[]) => console.debug(...args),
32
+ info: (...args: any[]) => console.info(...args),
33
+ error: (...args: any[]) => console.error(...args),
34
+ warn: (...args: any[]) => console.warn(...args),
35
+ };
36
+ /* eslint-enable no-console */
37
+ }
38
+ return resolved;
39
+ }
40
+
41
+ const logger: Logger = {
42
+ debug: (...args: any[]) => resolveLogger().debug(...args),
43
+ info: (...args: any[]) => resolveLogger().info(...args),
44
+ error: (...args: any[]) => resolveLogger().error(...args),
45
+ warn: (...args: any[]) => resolveLogger().warn(...args),
46
+ };
16
47
 
17
48
  export default logger;
18
49
 
19
- const { setupAccessLogger } = createLogger;
20
- export { setupAccessLogger };
50
+ // Express access logger also lazy. A bare host that never mounts the node
51
+ // handler (where setupAccessLogger runs) never needs the blocklet log env.
52
+ export function setupAccessLogger(app: any): void {
53
+ try {
54
+ // eslint-disable-next-line global-require, import/no-extraneous-dependencies
55
+ const createLogger = require('@blocklet/logger');
56
+ createLogger.setupAccessLogger?.(app);
57
+ } catch {
58
+ // bare host: no access logging
59
+ }
60
+ }
@@ -1,4 +1,4 @@
1
- import { Notification as BlockletNotification } from '@blocklet/sdk';
1
+ import BlockletNotification from '@blocklet/sdk/service/notification';
2
2
 
3
3
  import type { BaseEmailTemplate, BaseEmailTemplateType } from './template/base';
4
4
  import { CheckoutSession, CreditGrant, Invoice, Meter, MeterEvent, Subscription } from '../../store/models';
@@ -2,6 +2,7 @@
2
2
  /* eslint-disable @typescript-eslint/indent */
3
3
  import { withQuery } from 'ufo';
4
4
  import { BN } from '@ocap/util';
5
+ import { creditLowBalanceThresholdPercentage } from '../../env';
5
6
  import { getUserLocale } from '../../../integrations/blocklet/notification';
6
7
  import { translate } from '../../../locales';
7
8
  import { Customer, PaymentCurrency, CreditGrant } from '../../../store/models';
@@ -35,7 +36,7 @@ export class CustomerCreditLowBalanceEmailTemplate implements BaseEmailTemplate<
35
36
  * @returns threshold percentage (default: 10)
36
37
  */
37
38
  static getLowBalanceThresholdPercent(): number {
38
- return parseInt(process.env.CREDIT_LOW_BALANCE_THRESHOLD_PERCENTAGE || '10', 10);
39
+ return creditLowBalanceThresholdPercentage();
39
40
  }
40
41
 
41
42
  /**
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/brace-style */
2
2
  /* eslint-disable @typescript-eslint/indent */
3
- import { getUrl } from '@blocklet/sdk';
3
+ import { getUrl } from '@blocklet/sdk/lib/component';
4
4
  import { getUserLocale } from '../../../integrations/blocklet/notification';
5
5
  import { translate } from '../../../locales';
6
6
  import { CheckoutSession, Customer, PaymentLink, PaymentMethod, Payout } from '../../../store/models';
@@ -4,7 +4,7 @@ import isEmpty from 'lodash/isEmpty';
4
4
  import pWaitFor from 'p-wait-for';
5
5
  import type { LiteralUnion } from 'type-fest';
6
6
 
7
- import { getUrl } from '@blocklet/sdk';
7
+ import { getUrl } from '@blocklet/sdk/lib/component';
8
8
  import { getUserLocale } from '../../../integrations/blocklet/notification';
9
9
  import { translate } from '../../../locales';
10
10
  import {
@@ -1,4 +1,4 @@
1
- import { createProductAndPrices } from '../routes/products';
1
+ import { createProductAndPrices } from '../routes/hono/products';
2
2
  import { PaymentCurrency, PaymentMethod, Price, Product } from '../store/models';
3
3
  import logger from './logger';
4
4
 
@@ -1,4 +1,4 @@
1
- import { component } from '@blocklet/sdk';
1
+ import component from '@blocklet/sdk/lib/component';
2
2
  import type { LiteralUnion } from 'type-fest';
3
3
  import { withQuery } from 'ufo';
4
4
  import { getConnectQueryParam } from './util';