payment-kit 1.29.1 → 1.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/api/dev.ts +41 -2
  2. package/api/hono.d.ts +42 -0
  3. package/api/node-sqlite.d.ts +12 -0
  4. package/api/src/bootstrap.ts +36 -0
  5. package/api/src/crons/base.ts +3 -3
  6. package/api/src/crons/currency.ts +1 -1
  7. package/api/src/crons/index.ts +27 -24
  8. package/api/src/crons/metering-subscription-detection.ts +1 -1
  9. package/api/src/crons/overdue-detection.ts +2 -2
  10. package/api/src/crons/retry-pending-events.ts +6 -0
  11. package/api/src/index.ts +22 -161
  12. package/api/src/integrations/app-store/client.ts +3 -4
  13. package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
  14. package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
  15. package/api/src/integrations/arcblock/token.ts +21 -7
  16. package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
  17. package/api/src/integrations/google-play/handlers/voided.ts +2 -2
  18. package/api/src/integrations/google-play/verify.ts +3 -2
  19. package/api/src/integrations/iap-reconcile.ts +3 -5
  20. package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
  21. package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
  22. package/api/src/libs/archive/query.ts +19 -0
  23. package/api/src/libs/audit.ts +61 -4
  24. package/api/src/libs/auth.ts +99 -38
  25. package/api/src/libs/context.ts +78 -1
  26. package/api/src/libs/currency.ts +2 -2
  27. package/api/src/libs/dayjs.ts +8 -2
  28. package/api/src/libs/drivers/auth-storage.ts +118 -0
  29. package/api/src/libs/drivers/cron.ts +264 -0
  30. package/api/src/libs/drivers/db.ts +170 -0
  31. package/api/src/libs/drivers/identity.ts +81 -0
  32. package/api/src/libs/drivers/index.ts +40 -0
  33. package/api/src/libs/drivers/locks.ts +226 -0
  34. package/api/src/libs/drivers/migrate-runner.ts +70 -0
  35. package/api/src/libs/drivers/queue.ts +104 -0
  36. package/api/src/libs/drivers/secrets.ts +194 -0
  37. package/api/src/libs/env.ts +170 -54
  38. package/api/src/libs/exchange-rate/service.ts +7 -6
  39. package/api/src/libs/http-fetch-adapter.ts +50 -0
  40. package/api/src/libs/invoice.ts +1 -1
  41. package/api/src/libs/lock.ts +51 -47
  42. package/api/src/libs/logger.ts +48 -8
  43. package/api/src/libs/notification/index.ts +1 -1
  44. package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
  45. package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
  46. package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
  47. package/api/src/libs/overdraft-protection.ts +1 -1
  48. package/api/src/libs/payout.ts +1 -1
  49. package/api/src/libs/queue/index.ts +259 -52
  50. package/api/src/libs/queue/runtime.ts +175 -0
  51. package/api/src/libs/resource.ts +3 -3
  52. package/api/src/libs/secrets.ts +38 -0
  53. package/api/src/libs/session.ts +3 -2
  54. package/api/src/libs/subscription.ts +5 -5
  55. package/api/src/libs/tenant.ts +92 -0
  56. package/api/src/libs/url.ts +3 -3
  57. package/api/src/libs/util.ts +21 -13
  58. package/api/src/middlewares/hono/cdn.ts +63 -0
  59. package/api/src/middlewares/hono/context.ts +73 -0
  60. package/api/src/middlewares/hono/csrf.ts +72 -0
  61. package/api/src/middlewares/hono/fallback.ts +194 -0
  62. package/api/src/middlewares/hono/pipeline.ts +73 -0
  63. package/api/src/middlewares/hono/resource-mount.ts +42 -0
  64. package/api/src/middlewares/hono/resource.ts +63 -0
  65. package/api/src/middlewares/hono/security.ts +214 -0
  66. package/api/src/middlewares/hono/session.ts +114 -0
  67. package/api/src/middlewares/hono/xss.ts +61 -0
  68. package/api/src/queues/auto-recharge.ts +12 -10
  69. package/api/src/queues/checkout-session.ts +17 -12
  70. package/api/src/queues/credit-consume.ts +40 -36
  71. package/api/src/queues/credit-grant.ts +25 -18
  72. package/api/src/queues/credit-reconciliation.ts +7 -5
  73. package/api/src/queues/discount-status.ts +9 -6
  74. package/api/src/queues/event.ts +12 -4
  75. package/api/src/queues/exchange-rate-health.ts +49 -30
  76. package/api/src/queues/invoice.ts +18 -15
  77. package/api/src/queues/notification.ts +14 -7
  78. package/api/src/queues/payment.ts +41 -28
  79. package/api/src/queues/payout.ts +9 -5
  80. package/api/src/queues/refund.ts +18 -12
  81. package/api/src/queues/subscription.ts +83 -53
  82. package/api/src/queues/token-transfer.ts +15 -10
  83. package/api/src/queues/usage-record.ts +8 -5
  84. package/api/src/queues/vendors/commission.ts +7 -5
  85. package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
  86. package/api/src/queues/vendors/fulfillment.ts +4 -2
  87. package/api/src/queues/vendors/return-processor.ts +5 -3
  88. package/api/src/queues/vendors/return-scanner.ts +5 -4
  89. package/api/src/queues/vendors/status-check.ts +10 -7
  90. package/api/src/queues/webhook.ts +60 -32
  91. package/api/src/routes/connect/shared.ts +1 -2
  92. package/api/src/routes/connect/subscribe.ts +3 -3
  93. package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
  94. package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
  95. package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
  96. package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
  97. package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
  98. package/api/src/routes/hono/credit-tokens.ts +43 -0
  99. package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
  100. package/api/src/routes/{customers.ts → hono/customers.ts} +193 -223
  101. package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
  102. package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
  103. package/api/src/routes/{events.ts → hono/events.ts} +107 -71
  104. package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
  105. package/api/src/routes/hono/exchange-rates.ts +77 -0
  106. package/api/src/routes/hono/index.ts +115 -0
  107. package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
  108. package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
  109. package/api/src/routes/hono/integrations/stripe.ts +74 -0
  110. package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
  111. package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
  112. package/api/src/routes/hono/meters.ts +288 -0
  113. package/api/src/routes/hono/passports.ts +73 -0
  114. package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
  115. package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
  116. package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
  117. package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
  118. package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
  119. package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
  120. package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
  121. package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
  122. package/api/src/routes/{products.ts → hono/products.ts} +172 -159
  123. package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
  124. package/api/src/routes/hono/redirect.ts +24 -0
  125. package/api/src/routes/{refunds.ts → hono/refunds.ts} +96 -80
  126. package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
  127. package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
  128. package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
  129. package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
  130. package/api/src/routes/hono/tool.ts +69 -0
  131. package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
  132. package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
  133. package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
  134. package/api/src/routes/hono/webhook-endpoints.ts +126 -0
  135. package/api/src/service.ts +667 -0
  136. package/api/src/store/migrations/20230911-seeding.ts +2 -1
  137. package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
  138. package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
  139. package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
  140. package/api/src/store/models/auto-recharge-config.ts +22 -10
  141. package/api/src/store/models/checkout-session.ts +15 -14
  142. package/api/src/store/models/coupon.ts +29 -20
  143. package/api/src/store/models/credit-grant.ts +38 -29
  144. package/api/src/store/models/credit-transaction.ts +32 -21
  145. package/api/src/store/models/customer.ts +19 -17
  146. package/api/src/store/models/discount.ts +11 -2
  147. package/api/src/store/models/entitlement-grant.ts +21 -9
  148. package/api/src/store/models/entitlement-product.ts +21 -9
  149. package/api/src/store/models/entitlement.ts +19 -10
  150. package/api/src/store/models/event.ts +18 -9
  151. package/api/src/store/models/exchange-rate-provider.ts +17 -4
  152. package/api/src/store/models/invoice-item.ts +18 -9
  153. package/api/src/store/models/invoice.ts +16 -8
  154. package/api/src/store/models/meter-event.ts +27 -9
  155. package/api/src/store/models/meter.ts +31 -22
  156. package/api/src/store/models/payment-currency.ts +25 -8
  157. package/api/src/store/models/payment-intent.ts +15 -6
  158. package/api/src/store/models/payment-link.ts +15 -6
  159. package/api/src/store/models/payment-method.ts +38 -22
  160. package/api/src/store/models/payment-stat.ts +18 -9
  161. package/api/src/store/models/payout.ts +15 -6
  162. package/api/src/store/models/price-quote.ts +17 -8
  163. package/api/src/store/models/price.ts +24 -12
  164. package/api/src/store/models/pricing-table.ts +29 -20
  165. package/api/src/store/models/product-vendor.ts +20 -10
  166. package/api/src/store/models/product.ts +15 -6
  167. package/api/src/store/models/promotion-code.ts +14 -6
  168. package/api/src/store/models/refund.ts +15 -6
  169. package/api/src/store/models/revenue-snapshot.ts +21 -9
  170. package/api/src/store/models/setting.ts +18 -9
  171. package/api/src/store/models/setup-intent.ts +36 -27
  172. package/api/src/store/models/subscription-item.ts +21 -9
  173. package/api/src/store/models/subscription-schedule.ts +21 -9
  174. package/api/src/store/models/subscription.ts +21 -10
  175. package/api/src/store/models/tax-rate.ts +29 -21
  176. package/api/src/store/models/usage-record.ts +11 -2
  177. package/api/src/store/models/webhook-attempt.ts +18 -9
  178. package/api/src/store/models/webhook-endpoint.ts +18 -9
  179. package/api/src/store/scoped-core.ts +55 -0
  180. package/api/src/store/scoped.ts +247 -0
  181. package/api/src/store/sequelize.ts +66 -22
  182. package/api/src/store/sql-migrations.ts +20 -0
  183. package/api/src/store/tenant-backfill.ts +260 -0
  184. package/api/src/store/tenant-model.ts +124 -0
  185. package/api/src/store/tenant-tables.ts +50 -0
  186. package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
  187. package/api/tests/fixtures/bare-query-violation.ts +13 -0
  188. package/api/tests/fixtures/core-env-violation.ts +10 -0
  189. package/api/tests/fixtures/host-read-violation.ts +19 -0
  190. package/api/tests/fixtures/tenants.ts +4 -0
  191. package/api/tests/integrations/iap-tenant.spec.ts +284 -0
  192. package/api/tests/libs/archive-query.spec.ts +26 -0
  193. package/api/tests/libs/audit-tenant.spec.ts +153 -0
  194. package/api/tests/libs/context.spec.ts +204 -0
  195. package/api/tests/libs/core-config.spec.ts +115 -0
  196. package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
  197. package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
  198. package/api/tests/libs/lock-tenant.spec.ts +66 -0
  199. package/api/tests/libs/scoped.spec.ts +222 -0
  200. package/api/tests/libs/secrets-facade.spec.ts +52 -0
  201. package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
  202. package/api/tests/libs/tenant-middleware.spec.ts +42 -0
  203. package/api/tests/libs/tenant-scanner.spec.ts +120 -0
  204. package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
  205. package/api/tests/middlewares/hono/context.spec.ts +113 -0
  206. package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
  207. package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
  208. package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
  209. package/api/tests/middlewares/hono/security.spec.ts +181 -0
  210. package/api/tests/middlewares/hono/session.spec.ts +42 -0
  211. package/api/tests/middlewares/hono/xss.spec.ts +81 -0
  212. package/api/tests/models/tenant-backfill.spec.ts +287 -0
  213. package/api/tests/models/tenant-columns-model.spec.ts +46 -0
  214. package/api/tests/models/tenant-columns.spec.ts +161 -0
  215. package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
  216. package/api/tests/queues/credit-consume.spec.ts +8 -1
  217. package/api/tests/queues/event-tenant.spec.ts +236 -0
  218. package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
  219. package/api/tests/queues/queue-parity.spec.ts +249 -0
  220. package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
  221. package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
  222. package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
  223. package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
  224. package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
  225. package/api/tests/service/collapse.spec.ts +96 -0
  226. package/api/tests/store/tenant-crosscut.spec.ts +202 -0
  227. package/api/tests/store/tenant-model-spike.spec.ts +177 -0
  228. package/api/tests/store/tenant-model.spec.ts +162 -0
  229. package/api/tests/store/tenant-residual.spec.ts +196 -0
  230. package/api/third.d.ts +4 -0
  231. package/blocklet.yml +1 -1
  232. package/cloudflare/README.md +26 -6
  233. package/cloudflare/build.ts +28 -13
  234. package/cloudflare/did-connect-auth.ts +0 -217
  235. package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
  236. package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
  237. package/cloudflare/migrations/0008_schema_parity.sql +16 -0
  238. package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
  239. package/cloudflare/queue-runtime-mode.ts +13 -0
  240. package/cloudflare/run-build.js +10 -56
  241. package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
  242. package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
  243. package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
  244. package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
  245. package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
  246. package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
  247. package/cloudflare/shims/blocklet-sdk/util-csrf.ts +13 -0
  248. package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
  249. package/cloudflare/shims/cron.ts +38 -158
  250. package/cloudflare/shims/events.ts +124 -0
  251. package/cloudflare/shims/fastq.ts +15 -1
  252. package/cloudflare/shims/nedb-storage.ts +16 -8
  253. package/cloudflare/shims/xss.ts +8 -0
  254. package/cloudflare/tenant-middleware.ts +36 -0
  255. package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
  256. package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
  257. package/cloudflare/worker.ts +204 -433
  258. package/cloudflare/wrangler.local-e2e.jsonc +26 -0
  259. package/jest.config.js +3 -1
  260. package/package.json +33 -38
  261. package/scripts/core-env-whitelist.json +1 -0
  262. package/scripts/e2e-12b-runtime.ts +149 -0
  263. package/scripts/e2e-core-config.ts +125 -0
  264. package/scripts/e2e-d1-tenancy.ts +116 -0
  265. package/scripts/e2e-d2-cron-queue.ts +139 -0
  266. package/scripts/e2e-d3-embedded-multi.ts +171 -0
  267. package/scripts/e2e-hono-s2.ts +125 -0
  268. package/scripts/e2e-hono-s3e.ts +135 -0
  269. package/scripts/e2e-hono-s4.ts +114 -0
  270. package/scripts/e2e-migration-contract.ts +100 -0
  271. package/scripts/e2e-s0.ts +61 -0
  272. package/scripts/e2e-s1.ts +107 -0
  273. package/scripts/e2e-s2.ts +178 -0
  274. package/scripts/e2e-s3.ts +110 -0
  275. package/scripts/e2e-s4.ts +191 -0
  276. package/scripts/e2e-s5.ts +139 -0
  277. package/scripts/e2e-s6.ts +127 -0
  278. package/scripts/e2e-tenant-model.ts +119 -0
  279. package/scripts/e2e-tenant-worker.ts +199 -0
  280. package/scripts/gen-sql-migrations.js +46 -0
  281. package/scripts/phase8-codemod.js +219 -0
  282. package/scripts/phase9a-env-getters-codemod.js +82 -0
  283. package/scripts/scan-core-env.js +109 -0
  284. package/scripts/scan-tenant-queries.js +235 -0
  285. package/scripts/schema-drift-guard.ts +210 -0
  286. package/scripts/tenant-scan-whitelist.json +1 -0
  287. package/src/env.d.ts +13 -1
  288. package/tsconfig.json +1 -1
  289. package/api/src/libs/did-space.ts +0 -235
  290. package/api/src/libs/middleware.ts +0 -50
  291. package/api/src/libs/security.ts +0 -192
  292. package/api/src/queues/space.ts +0 -662
  293. package/api/src/routes/credit-tokens.ts +0 -38
  294. package/api/src/routes/exchange-rates.ts +0 -87
  295. package/api/src/routes/index.ts +0 -142
  296. package/api/src/routes/integrations/stripe.ts +0 -61
  297. package/api/src/routes/meters.ts +0 -274
  298. package/api/src/routes/passports.ts +0 -68
  299. package/api/src/routes/redirect.ts +0 -20
  300. package/api/src/routes/tool.ts +0 -65
  301. package/api/src/routes/webhook-endpoints.ts +0 -126
  302. package/api/tests/routes/credit-grants.spec.ts +0 -1261
  303. package/cloudflare/shims/did-space-js.ts +0 -17
  304. package/cloudflare/shims/did-space.ts +0 -11
  305. package/cloudflare/shims/express-compat/index.ts +0 -80
  306. package/cloudflare/shims/express-compat/types.ts +0 -41
  307. package/cloudflare/shims/lock.ts +0 -115
  308. package/cloudflare/shims/queue.ts +0 -611
  309. package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
  310. package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
package/api/dev.ts CHANGED
@@ -1,10 +1,49 @@
1
+ // Dev shell — express→hono Phase 4. Dev-only (NOT bundled into production:
2
+ // tsconfig.api excludes api/dev.ts; the production entry is ./src → index.ts).
3
+ //
4
+ // Hosts the Vite client + HMR on a `connect` app and routes backend paths to the
5
+ // hono service. `vite-plugin-blocklet`'s setupClient only knows `app.use(vite
6
+ // .middlewares)`, so the dev shell hosts Vite on connect (NOT express — core is
7
+ // express-free). Backend paths are routed to service.fetch EXPLICITLY by prefix:
8
+ // a Web-Fetch 404 cannot call connect next(), so hono must never be the trailing
9
+ // catch-all (that would swallow client routes the SPA fallback should serve).
10
+ import { createServer } from 'http';
11
+
12
+ import { getRequestListener } from '@hono/node-server';
13
+ // eslint-disable-next-line import/no-extraneous-dependencies
14
+ import connect from 'connect';
1
15
  // eslint-disable-next-line import/no-extraneous-dependencies
2
16
  import { setupClient } from 'vite-plugin-blocklet';
3
17
 
4
- import { app, server } from './src';
18
+ import { buildService, onListening } from './src/bootstrap';
19
+
20
+ const { service, port } = buildService();
21
+
22
+ // Hand backend paths to the hono app; everything else falls through to the Vite
23
+ // middleware setupClient() mounts after this router.
24
+ const honoListener = getRequestListener(service.fetch);
25
+ const isBackendPath = (url: string): boolean => {
26
+ const pathname = url.split('?')[0] || '';
27
+ return pathname === '/api' || pathname.startsWith('/api/') || pathname.startsWith('/.well-known/');
28
+ };
5
29
 
30
+ const app = connect();
31
+ app.use((req, res, next) => {
32
+ if (isBackendPath(req.url || '')) {
33
+ honoListener(req, res);
34
+ return;
35
+ }
36
+ next();
37
+ });
38
+
39
+ const server = createServer(app);
40
+
41
+ // setupClient appends the Vite connect middleware (client assets + HMR + SPA
42
+ // fallback) AFTER our backend router, and wires the HMR ws upgrade onto `server`.
6
43
  setupClient(app, {
7
44
  server,
8
- // @ts-ignore
45
+ // @ts-ignore — import.meta.hot is injected by tsx/vite-node at runtime
9
46
  importMetaHot: import.meta.hot,
10
47
  });
48
+
49
+ server.listen(port, () => onListening(service, port));
package/api/hono.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ // ContextVariableMap skeleton (Phase 0, express→hono migration).
2
+ //
3
+ // Mirrors the express `Request` augmentation in api/third.d.ts so that
4
+ // `c.set(...)` / `c.get(...)` are typed everywhere across the migration. This
5
+ // file only declares the key→type contract; the actual injection lands later:
6
+ // - user / customer / doc / customer_id → Phase 1 (libs/security.ts)
7
+ // - locale / t → Phase 1 (libs/middleware.ts)
8
+ // - sanitizedBody → Phase 1 (forked xss middleware)
9
+ // - livemode / baseCurrency → Phase 3 (routes/index.ts middleware)
10
+ // - stripeEvent / stripeClient → Phase 3e (stripe verifyWebhookSig)
11
+ //
12
+ // Two keys exist only on the hono side (no express equivalent), because hono's
13
+ // Request is immutable and cannot be rewritten in place the way express was:
14
+ // - sanitizedBody : xss is the single body read-point; routes read this, never
15
+ // c.req.json() (a re-read would return the UN-sanitized
16
+ // original — see design §7 security note).
17
+ // - customer_id : security "mine" mode injects the verified customer id here
18
+ // (express wrote req.query.customer_id =, impossible on hono).
19
+ import 'hono';
20
+
21
+ declare module 'hono' {
22
+ interface ContextVariableMap {
23
+ user: {
24
+ did: string;
25
+ role: string;
26
+ provider: string;
27
+ fullName: string;
28
+ walletOS: string;
29
+ via?: string;
30
+ };
31
+ livemode: boolean;
32
+ locale: string;
33
+ t: (key: string, ...args: any[]) => string;
34
+ doc: any;
35
+ customer: any;
36
+ baseCurrency: any;
37
+ customer_id: string;
38
+ sanitizedBody: any;
39
+ stripeEvent: any;
40
+ stripeClient: any;
41
+ }
42
+ }
@@ -0,0 +1,12 @@
1
+ // Ambient declaration for node:sqlite (experimental, Node 22+). It exists at
2
+ // runtime (the tests + schema-drift-guard run on a node that ships it) but
3
+ // @types/node does not yet declare it, so tsc --noEmit fails to resolve the import.
4
+ // Declared as a class so it works both as a value (`new DatabaseSync()`) and as a
5
+ // type (`let db: DatabaseSync`). Typed loosely — test/tooling-only consumers.
6
+ declare module 'node:sqlite' {
7
+ export class DatabaseSync {
8
+ constructor(path?: string, options?: any);
9
+
10
+ [key: string]: any;
11
+ }
12
+ }
@@ -0,0 +1,36 @@
1
+ // Blocklet server service bootstrap — shared by the production entry
2
+ // (./index.ts → api/dist/index.js) and the dev shell (../dev.ts).
3
+ //
4
+ // Phase 4 (express→hono): the listened surface is the hono app (service.fetch).
5
+ // Production serves it directly via @hono/node-server. Dev hosts the Vite client
6
+ // + HMR on a `connect` shell (../dev.ts) and routes backend paths to the same
7
+ // service.fetch — so both entries build the service identically here and only
8
+ // differ in HOW they put it behind a socket.
9
+
10
+ import logger from './libs/logger';
11
+ import { getDefaultInstanceDid, getTenantMode } from './libs/tenant';
12
+ import { createEmbeddedPaymentService, type EmbeddedPaymentService } from './service';
13
+ import { sequelize } from './store/sequelize';
14
+ import { blockletPort } from './libs/env';
15
+
16
+ export function buildService(): { service: EmbeddedPaymentService; port: number } {
17
+ const tenancy =
18
+ getTenantMode() === 'multi'
19
+ ? ({ mode: 'multi' } as const)
20
+ : ({ mode: 'single', instanceDid: getDefaultInstanceDid() } as const);
21
+
22
+ const service = createEmbeddedPaymentService({
23
+ config: { ...process.env }, // Phase 12 narrows this to an explicit schema
24
+ db: { sequelize },
25
+ tenancy,
26
+ });
27
+
28
+ const port = parseInt(blockletPort()!, 10);
29
+ return { service, port };
30
+ }
31
+
32
+ /** Start background services once the socket is bound (the listen callback). */
33
+ export function onListening(service: EmbeddedPaymentService, port: number): void {
34
+ logger.info(`> payment-kit ready on ${port}`);
35
+ service.lifecycle.start().catch((startErr) => logger.error('failed to start background services', startErr));
36
+ }
@@ -175,7 +175,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
175
175
  await pAll(
176
176
  deleteTasks.map((task) => () => task()),
177
177
  {
178
- concurrency: notificationCronConcurrency,
178
+ concurrency: notificationCronConcurrency(),
179
179
  stopOnError: false,
180
180
  }
181
181
  );
@@ -192,7 +192,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
192
192
  };
193
193
  }),
194
194
  {
195
- concurrency: notificationCronConcurrency,
195
+ concurrency: notificationCronConcurrency(),
196
196
  stopOnError: false,
197
197
  }
198
198
  );
@@ -231,7 +231,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
231
231
  );
232
232
 
233
233
  await pAll(deletePromises, {
234
- concurrency: notificationCronConcurrency,
234
+ concurrency: notificationCronConcurrency(),
235
235
  stopOnError: false,
236
236
  });
237
237
  }
@@ -1,5 +1,5 @@
1
1
  import { Op } from 'sequelize';
2
- import { getUrl } from '@blocklet/sdk';
2
+ import { getUrl } from '@blocklet/sdk/lib/component';
3
3
  import { PaymentMethod, PaymentCurrency } from '../store/models';
4
4
  import logger from '../libs/logger';
5
5
 
@@ -1,4 +1,4 @@
1
- import Cron from '@abtnode/cron';
1
+ import { getCronDriver } from '../libs/drivers/cron';
2
2
 
3
3
  import { checkStakeRevokeTx } from '../integrations/arcblock/stake';
4
4
  import { runIapReconcile } from '../integrations/iap-reconcile';
@@ -63,30 +63,33 @@ function init() {
63
63
  },
64
64
  ]
65
65
  : [];
66
- Cron.init({
67
- context: {},
68
- jobs: [
66
+ // D2: register through the injected cron driver (no bare @abtnode/cron). On
67
+ // the node host the driver self-schedules via @abtnode/cron; on CF the host
68
+ // drives runDue() from scheduled(). register() is idempotent (clears + re-adds
69
+ // + restarts the scheduler), so a stop()/start() cycle never double-registers.
70
+ getCronDriver().register(
71
+ [
69
72
  {
70
73
  name: 'subscription.will.renew',
71
- time: notificationCronTime,
74
+ time: notificationCronTime(),
72
75
  fn: () => new SubscriptionWillRenewSchedule().run(),
73
76
  options: { runOnInit: true },
74
77
  },
75
78
  {
76
79
  name: 'subscription.trial.will.end',
77
- time: notificationCronTime,
80
+ time: notificationCronTime(),
78
81
  fn: () => new SubscriptionTrialWillEndSchedule().run(),
79
82
  options: { runOnInit: true },
80
83
  },
81
84
  {
82
85
  name: 'customer.subscription.will_canceled',
83
- time: notificationCronTime,
86
+ time: notificationCronTime(),
84
87
  fn: () => new SubscriptionWillCanceledSchedule().run(),
85
88
  options: { runOnInit: true },
86
89
  },
87
90
  {
88
91
  name: 'subscription.schedule.retry',
89
- time: subscriptionCronTime,
92
+ time: subscriptionCronTime(),
90
93
  fn: startSubscriptionQueue,
91
94
  options: { runOnInit: false },
92
95
  },
@@ -101,7 +104,7 @@ function init() {
101
104
  },
102
105
  {
103
106
  name: 'checkoutSession.cleanup.expired',
104
- time: expiredSessionCleanupCronTime,
107
+ time: expiredSessionCleanupCronTime(),
105
108
  fn: async () => {
106
109
  const removedCount = await CheckoutSession.cleanupExpiredSessions();
107
110
  logger.info('CheckoutSession.cleanupExpiredSessions', { removedCount });
@@ -110,25 +113,25 @@ function init() {
110
113
  },
111
114
  {
112
115
  name: 'stripe.invoice.sync',
113
- time: stripeInvoiceCronTime,
116
+ time: stripeInvoiceCronTime(),
114
117
  fn: batchHandleStripeInvoices,
115
118
  options: { runOnInit: false },
116
119
  },
117
120
  {
118
121
  name: 'stripe.payment.sync',
119
- time: stripePaymentCronTime,
122
+ time: stripePaymentCronTime(),
120
123
  fn: batchHandleStripePayments,
121
124
  options: { runOnInit: false },
122
125
  },
123
126
  {
124
127
  name: 'stripe.subscription.sync',
125
- time: stripeSubscriptionCronTime,
128
+ time: stripeSubscriptionCronTime(),
126
129
  fn: batchHandleStripeSubscriptions,
127
130
  options: { runOnInit: false },
128
131
  },
129
132
  {
130
133
  name: 'customer.stake.revoked',
131
- time: revokeStakeCronTime,
134
+ time: revokeStakeCronTime(),
132
135
  fn: checkStakeRevokeTx,
133
136
  options: { runOnInit: false },
134
137
  },
@@ -137,7 +140,7 @@ function init() {
137
140
  // subscription state and patches local drift (refunds/renewals the
138
141
  // webhook missed). See blocklets/core/api/src/integrations/iap-reconcile.ts.
139
142
  name: 'iap.reconcile',
140
- time: iapReconcileCronTime,
143
+ time: iapReconcileCronTime(),
141
144
  fn: () => runIapReconcile(),
142
145
  options: { runOnInit: false },
143
146
  },
@@ -146,52 +149,52 @@ function init() {
146
149
  // with pending_webhooks>0 older than 60s and re-invokes handleEvent.
147
150
  // See blocklets/core/api/src/crons/retry-pending-events.ts.
148
151
  name: 'event.retry',
149
- time: eventRetryCronTime,
152
+ time: eventRetryCronTime(),
150
153
  fn: () => retryPendingEvents(),
151
154
  options: { runOnInit: false },
152
155
  },
153
156
  {
154
157
  name: 'payment.stat',
155
- time: paymentStatCronTime,
158
+ time: paymentStatCronTime(),
156
159
  fn: () => createPaymentStat(),
157
160
  options: { runOnInit: false },
158
161
  },
159
162
  {
160
163
  name: 'payment.daily.report',
161
- time: overdueDetectionCronTime,
164
+ time: overdueDetectionCronTime(),
162
165
  fn: () => createOverdueDetection(),
163
166
  options: { runOnInit: false },
164
167
  },
165
168
  {
166
169
  name: 'deposit.vault',
167
- time: depositVaultCronTime,
170
+ time: depositVaultCronTime(),
168
171
  fn: () => startDepositVaultQueue(),
169
172
  options: { runOnInit: true },
170
173
  },
171
174
  {
172
175
  name: 'credit.consumption',
173
- time: creditConsumptionCronTime,
176
+ time: creditConsumptionCronTime(),
174
177
  fn: () => startCreditConsumeQueue(),
175
178
  options: { runOnInit: true },
176
179
  },
177
180
  {
178
181
  name: 'vendor.status.check',
179
- time: vendorStatusCheckCronTime,
182
+ time: vendorStatusCheckCronTime(),
180
183
  fn: () => startVendorStatusCheckSchedule(),
181
184
  options: { runOnInit: false },
182
185
  },
183
186
  {
184
187
  name: 'vendor.return.scan',
185
- time: vendorReturnScanCronTime,
188
+ time: vendorReturnScanCronTime(),
186
189
  fn: () => scheduleVendorReturnScan(),
187
190
  options: { runOnInit: false },
188
191
  },
189
192
  ...archiveJobs,
190
193
  ],
191
- onError: (error: Error, name: string) => {
194
+ (error: Error, name: string) => {
192
195
  logger.error('run job failed', { name, error });
193
- },
194
- });
196
+ }
197
+ );
195
198
  }
196
199
 
197
200
  export default {
@@ -1,4 +1,4 @@
1
- import { Notification } from '@blocklet/sdk';
1
+ import Notification from '@blocklet/sdk/service/notification';
2
2
  import { Op } from 'sequelize';
3
3
  import { env } from '@blocklet/sdk/lib/config';
4
4
  import dayjs from '../libs/dayjs';
@@ -1,4 +1,4 @@
1
- import { Notification } from '@blocklet/sdk';
1
+ import Notification from '@blocklet/sdk/service/notification';
2
2
  import { getUrl } from '@blocklet/sdk/lib/component';
3
3
  import { env } from '@blocklet/sdk/lib/config';
4
4
  import { fromUnitToToken, BN } from '@ocap/util';
@@ -175,7 +175,7 @@ export class HealthReportTemplate implements BaseEmailTemplate<HealthReportConte
175
175
  currencyTotals.set(currencyId, currentTotal.add(group.total));
176
176
 
177
177
  // Check if this user's pending exceeds threshold for this currency
178
- const thresholdInUnit = new BN(overdueThreshold.toString()).mul(new BN(10).pow(new BN(currency.decimal)));
178
+ const thresholdInUnit = new BN(overdueThreshold().toString()).mul(new BN(10).pow(new BN(currency.decimal)));
179
179
  if (group.total.gt(thresholdInUnit)) {
180
180
  exceedsThreshold = true;
181
181
  }
@@ -33,6 +33,12 @@ const REALTIME_GRACE_SECONDS = 60;
33
33
  // generous headroom and we still drain the backlog over a few minutes.
34
34
  const BATCH_LIMIT = 5;
35
35
 
36
+ // Phase 4 (W1-3): this scan is deliberately tenant-agnostic — it only
37
+ // re-enqueues event IDs. Tenant enforcement happens downstream in
38
+ // handleEvent's fanout (endpoints filtered by the event's instance_did) and
39
+ // handleWebhook's event/endpoint tenant invariant. Events whose creation was
40
+ // tenant-rejected never produced a row, so this backstop cannot "rescue"
41
+ // them — permanent loss of rejected events is the intended semantic.
36
42
  export async function retryPendingEvents(): Promise<void> {
37
43
  const threshold = new Date(Date.now() - REALTIME_GRACE_SECONDS * 1000);
38
44
  const docs = await Event.findAll({
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
  }