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.
- package/api/dev.ts +41 -2
- package/api/hono.d.ts +42 -0
- package/api/node-sqlite.d.ts +12 -0
- package/api/src/bootstrap.ts +47 -0
- package/api/src/crons/base.ts +3 -3
- package/api/src/crons/currency.ts +1 -1
- package/api/src/crons/index.ts +41 -37
- package/api/src/crons/metering-subscription-detection.ts +1 -1
- package/api/src/crons/overdue-detection.ts +2 -2
- package/api/src/crons/retry-pending-events.ts +6 -0
- package/api/src/crons/tenant-fanout.ts +82 -0
- package/api/src/host-node/did-connect-runtime-node.ts +33 -0
- package/api/src/host-node/serve-static-arc.ts +68 -0
- package/api/src/host-node/serve-static.ts +41 -0
- package/api/src/index.ts +22 -161
- package/api/src/integrations/app-store/client.ts +3 -4
- package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
- package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +21 -7
- package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
- package/api/src/integrations/google-play/handlers/voided.ts +2 -2
- package/api/src/integrations/google-play/verify.ts +3 -2
- package/api/src/integrations/iap-reconcile.ts +3 -5
- package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
- package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
- package/api/src/libs/archive/query.ts +19 -0
- package/api/src/libs/audit.ts +61 -4
- package/api/src/libs/auth.ts +247 -47
- package/api/src/libs/context.ts +89 -1
- package/api/src/libs/currency.ts +2 -2
- package/api/src/libs/dayjs.ts +8 -2
- package/api/src/libs/did-connect/runtime-did-connect-js.ts +88 -0
- package/api/src/libs/did-connect/tenant-identity.ts +221 -0
- package/api/src/libs/drivers/auth-storage.ts +118 -0
- package/api/src/libs/drivers/cron.ts +264 -0
- package/api/src/libs/drivers/db.ts +170 -0
- package/api/src/libs/drivers/identity.ts +142 -0
- package/api/src/libs/drivers/index.ts +40 -0
- package/api/src/libs/drivers/locks.ts +226 -0
- package/api/src/libs/drivers/migrate-runner.ts +70 -0
- package/api/src/libs/drivers/queue.ts +104 -0
- package/api/src/libs/drivers/secrets.ts +194 -0
- package/api/src/libs/env.ts +170 -54
- package/api/src/libs/exchange-rate/service.ts +7 -6
- package/api/src/libs/http-fetch-adapter.ts +60 -0
- package/api/src/libs/invoice.ts +1 -1
- package/api/src/libs/lock.ts +51 -47
- package/api/src/libs/logger.ts +48 -8
- package/api/src/libs/notification/index.ts +1 -1
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
- package/api/src/libs/overdraft-protection.ts +1 -1
- package/api/src/libs/payout.ts +1 -1
- package/api/src/libs/queue/index.ts +271 -52
- package/api/src/libs/queue/runtime.ts +175 -0
- package/api/src/libs/resource.ts +3 -3
- package/api/src/libs/secrets.ts +38 -0
- package/api/src/libs/session.ts +3 -2
- package/api/src/libs/subscription.ts +5 -5
- package/api/src/libs/tenant.ts +92 -0
- package/api/src/libs/url.ts +3 -3
- package/api/src/libs/util.ts +21 -13
- package/api/src/middlewares/hono/cdn.ts +63 -0
- package/api/src/middlewares/hono/context.ts +80 -0
- package/api/src/middlewares/hono/csrf.ts +83 -0
- package/api/src/middlewares/hono/fallback.ts +194 -0
- package/api/src/middlewares/hono/pipeline.ts +73 -0
- package/api/src/middlewares/hono/resource-mount.ts +42 -0
- package/api/src/middlewares/hono/resource.ts +63 -0
- package/api/src/middlewares/hono/security.ts +209 -0
- package/api/src/middlewares/hono/session.ts +114 -0
- package/api/src/middlewares/hono/xss.ts +61 -0
- package/api/src/queues/auto-recharge.ts +12 -10
- package/api/src/queues/checkout-session.ts +38 -21
- package/api/src/queues/credit-consume.ts +40 -36
- package/api/src/queues/credit-grant.ts +25 -18
- package/api/src/queues/credit-reconciliation.ts +7 -5
- package/api/src/queues/discount-status.ts +9 -6
- package/api/src/queues/event.ts +41 -11
- package/api/src/queues/exchange-rate-health.ts +49 -30
- package/api/src/queues/invoice.ts +18 -15
- package/api/src/queues/notification.ts +14 -7
- package/api/src/queues/payment.ts +64 -37
- package/api/src/queues/payout.ts +37 -21
- package/api/src/queues/refund.ts +36 -18
- package/api/src/queues/subscription.ts +83 -53
- package/api/src/queues/token-transfer.ts +15 -10
- package/api/src/queues/usage-record.ts +8 -5
- package/api/src/queues/vendors/commission.ts +7 -5
- package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
- package/api/src/queues/vendors/fulfillment.ts +4 -2
- package/api/src/queues/vendors/return-processor.ts +5 -3
- package/api/src/queues/vendors/return-scanner.ts +5 -4
- package/api/src/queues/vendors/status-check.ts +10 -7
- package/api/src/queues/webhook.ts +60 -32
- package/api/src/routes/connect/shared.ts +1 -2
- package/api/src/routes/connect/subscribe.ts +3 -3
- package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
- package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
- package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
- package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
- package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
- package/api/src/routes/hono/credit-tokens.ts +43 -0
- package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
- package/api/src/routes/{customers.ts → hono/customers.ts} +199 -224
- package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
- package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
- package/api/src/routes/{events.ts → hono/events.ts} +107 -71
- package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
- package/api/src/routes/hono/exchange-rates.ts +77 -0
- package/api/src/routes/hono/index.ts +115 -0
- package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
- package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
- package/api/src/routes/hono/integrations/stripe.ts +74 -0
- package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
- package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
- package/api/src/routes/hono/meters.ts +288 -0
- package/api/src/routes/hono/passports.ts +73 -0
- package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
- package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
- package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
- package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
- package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
- package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
- package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
- package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
- package/api/src/routes/{products.ts → hono/products.ts} +172 -159
- package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
- package/api/src/routes/hono/redirect.ts +24 -0
- package/api/src/routes/{refunds.ts → hono/refunds.ts} +98 -83
- package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
- package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
- package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
- package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
- package/api/src/routes/hono/tool.ts +69 -0
- package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
- package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
- package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
- package/api/src/routes/hono/webhook-endpoints.ts +126 -0
- package/api/src/service.ts +814 -0
- package/api/src/store/migrations/20230911-seeding.ts +2 -1
- package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
- package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
- package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
- package/api/src/store/models/auto-recharge-config.ts +22 -10
- package/api/src/store/models/checkout-session.ts +15 -14
- package/api/src/store/models/coupon.ts +29 -20
- package/api/src/store/models/credit-grant.ts +38 -29
- package/api/src/store/models/credit-transaction.ts +32 -21
- package/api/src/store/models/customer.ts +19 -17
- package/api/src/store/models/discount.ts +11 -2
- package/api/src/store/models/entitlement-grant.ts +21 -9
- package/api/src/store/models/entitlement-product.ts +21 -9
- package/api/src/store/models/entitlement.ts +19 -10
- package/api/src/store/models/event.ts +18 -9
- package/api/src/store/models/exchange-rate-provider.ts +17 -4
- package/api/src/store/models/invoice-item.ts +18 -9
- package/api/src/store/models/invoice.ts +16 -8
- package/api/src/store/models/meter-event.ts +27 -9
- package/api/src/store/models/meter.ts +31 -22
- package/api/src/store/models/payment-currency.ts +25 -8
- package/api/src/store/models/payment-intent.ts +15 -6
- package/api/src/store/models/payment-link.ts +15 -6
- package/api/src/store/models/payment-method.ts +38 -22
- package/api/src/store/models/payment-stat.ts +18 -9
- package/api/src/store/models/payout.ts +15 -6
- package/api/src/store/models/price-quote.ts +17 -8
- package/api/src/store/models/price.ts +24 -12
- package/api/src/store/models/pricing-table.ts +29 -20
- package/api/src/store/models/product-vendor.ts +20 -10
- package/api/src/store/models/product.ts +15 -6
- package/api/src/store/models/promotion-code.ts +14 -6
- package/api/src/store/models/refund.ts +15 -6
- package/api/src/store/models/revenue-snapshot.ts +21 -9
- package/api/src/store/models/setting.ts +18 -9
- package/api/src/store/models/setup-intent.ts +36 -27
- package/api/src/store/models/subscription-item.ts +21 -9
- package/api/src/store/models/subscription-schedule.ts +21 -9
- package/api/src/store/models/subscription.ts +21 -10
- package/api/src/store/models/tax-rate.ts +29 -21
- package/api/src/store/models/usage-record.ts +11 -2
- package/api/src/store/models/webhook-attempt.ts +18 -9
- package/api/src/store/models/webhook-endpoint.ts +18 -9
- package/api/src/store/scoped-core.ts +55 -0
- package/api/src/store/scoped.ts +247 -0
- package/api/src/store/sequelize.ts +82 -23
- package/api/src/store/sql-migrations.ts +20 -0
- package/api/src/store/tenant-backfill.ts +260 -0
- package/api/src/store/tenant-model.ts +124 -0
- package/api/src/store/tenant-tables.ts +50 -0
- package/api/tests/bootstrap/bootstrap.spec.ts +162 -0
- package/api/tests/crons/tenant-fanout.spec.ts +158 -0
- package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
- package/api/tests/fixtures/bare-query-violation.ts +13 -0
- package/api/tests/fixtures/core-env-violation.ts +10 -0
- package/api/tests/fixtures/host-read-violation.ts +19 -0
- package/api/tests/fixtures/tenants.ts +4 -0
- package/api/tests/integrations/iap-tenant.spec.ts +284 -0
- package/api/tests/libs/archive-query.spec.ts +26 -0
- package/api/tests/libs/audit-tenant.spec.ts +153 -0
- package/api/tests/libs/context.spec.ts +204 -0
- package/api/tests/libs/core-config.spec.ts +115 -0
- package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
- package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
- package/api/tests/libs/did-connect-runtime-js.spec.ts +98 -0
- package/api/tests/libs/did-connect-tenant-identity.spec.ts +159 -0
- package/api/tests/libs/lock-tenant.spec.ts +66 -0
- package/api/tests/libs/scoped.spec.ts +222 -0
- package/api/tests/libs/secrets-facade.spec.ts +52 -0
- package/api/tests/libs/service-host.spec.ts +37 -0
- package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
- package/api/tests/libs/tenant-middleware.spec.ts +42 -0
- package/api/tests/libs/tenant-scanner.spec.ts +120 -0
- package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
- package/api/tests/middlewares/hono/context.spec.ts +113 -0
- package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
- package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
- package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
- package/api/tests/middlewares/hono/security.spec.ts +181 -0
- package/api/tests/middlewares/hono/session.spec.ts +42 -0
- package/api/tests/middlewares/hono/xss.spec.ts +81 -0
- package/api/tests/models/tenant-backfill.spec.ts +287 -0
- package/api/tests/models/tenant-columns-model.spec.ts +46 -0
- package/api/tests/models/tenant-columns.spec.ts +161 -0
- package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
- package/api/tests/queues/credit-consume.spec.ts +8 -1
- package/api/tests/queues/event-tenant.spec.ts +292 -0
- package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
- package/api/tests/queues/queue-parity.spec.ts +249 -0
- package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
- package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
- package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
- package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
- package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
- package/api/tests/service/collapse.spec.ts +96 -0
- package/api/tests/service/didconnect-storage-slot.spec.ts +60 -0
- package/api/tests/service/fail-closed-http.spec.ts +79 -0
- package/api/tests/service/static-arc-handler.spec.ts +101 -0
- package/api/tests/service/static-externalized.spec.ts +48 -0
- package/api/tests/store/tenant-crosscut.spec.ts +202 -0
- package/api/tests/store/tenant-model-spike.spec.ts +177 -0
- package/api/tests/store/tenant-model.spec.ts +162 -0
- package/api/tests/store/tenant-residual.spec.ts +196 -0
- package/api/third.d.ts +4 -0
- package/blocklet.yml +1 -1
- package/cloudflare/MIGRATION-RUNBOOK.md +3 -8
- package/cloudflare/README.md +34 -27
- package/cloudflare/STAGING-MIGRATION-GUIDE.md +3 -15
- package/cloudflare/build.ts +33 -13
- package/cloudflare/cf-adapter.ts +419 -0
- package/cloudflare/did-connect-runtime.ts +96 -0
- package/cloudflare/did-connect-token-storage.ts +151 -0
- package/cloudflare/esbuild-cf-config.cjs +407 -0
- package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
- package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
- package/cloudflare/migrations/0008_schema_parity.sql +16 -0
- package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
- package/cloudflare/queue-runtime-mode.ts +13 -0
- package/cloudflare/run-build.js +33 -403
- package/cloudflare/scripts/cf-package-import-probe.mjs +90 -0
- package/cloudflare/scripts/didconnect-mock-smoke.mjs +140 -0
- package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
- package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
- package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +16 -1
- package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +18 -3
- package/cloudflare/shims/cron.ts +38 -158
- package/cloudflare/shims/events.ts +124 -0
- package/cloudflare/shims/fastq.ts +15 -1
- package/cloudflare/shims/nedb-storage.ts +16 -8
- package/cloudflare/shims/xss.ts +8 -0
- package/cloudflare/tenant-middleware.ts +36 -0
- package/cloudflare/tests/cf-adapter.spec.ts +244 -0
- package/cloudflare/tests/did-connect-token-storage.spec.ts +105 -0
- package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +69 -0
- package/cloudflare/vite.config.ts +53 -45
- package/cloudflare/worker.ts +261 -448
- package/cloudflare/wrangler.json +0 -6
- package/cloudflare/wrangler.jsonc +0 -6
- package/cloudflare/wrangler.local-e2e.jsonc +25 -0
- package/cloudflare/wrangler.staging.json +0 -6
- package/jest.config.js +3 -1
- package/package.json +33 -38
- package/scripts/bootstrap-inject.ts +166 -0
- package/scripts/core-env-whitelist.json +1 -0
- package/scripts/e2e-12b-runtime.ts +149 -0
- package/scripts/e2e-core-config.ts +125 -0
- package/scripts/e2e-d1-tenancy.ts +116 -0
- package/scripts/e2e-d2-cron-queue.ts +139 -0
- package/scripts/e2e-d3-embedded-multi.ts +171 -0
- package/scripts/e2e-hono-s2.ts +125 -0
- package/scripts/e2e-hono-s3e.ts +135 -0
- package/scripts/e2e-hono-s4.ts +114 -0
- package/scripts/e2e-migration-contract.ts +100 -0
- package/scripts/e2e-s0.ts +61 -0
- package/scripts/e2e-s1.ts +107 -0
- package/scripts/e2e-s2.ts +178 -0
- package/scripts/e2e-s3.ts +110 -0
- package/scripts/e2e-s4.ts +191 -0
- package/scripts/e2e-s5.ts +139 -0
- package/scripts/e2e-s6.ts +127 -0
- package/scripts/e2e-tenant-model.ts +119 -0
- package/scripts/e2e-tenant-worker.ts +199 -0
- package/scripts/gen-sql-migrations.js +46 -0
- package/scripts/phase8-codemod.js +219 -0
- package/scripts/phase9a-env-getters-codemod.js +82 -0
- package/scripts/scan-core-env.js +109 -0
- package/scripts/scan-tenant-queries.js +235 -0
- package/scripts/schema-drift-guard.ts +210 -0
- package/scripts/tenant-scan-whitelist.json +1 -0
- package/src/app.tsx +2 -1
- package/src/env.d.ts +13 -1
- package/src/libs/service-host.ts +13 -0
- package/tsconfig.json +1 -1
- package/vite.arc.config.ts +159 -0
- package/api/src/libs/did-space.ts +0 -235
- package/api/src/libs/middleware.ts +0 -50
- package/api/src/libs/security.ts +0 -192
- package/api/src/queues/space.ts +0 -662
- package/api/src/routes/credit-tokens.ts +0 -38
- package/api/src/routes/exchange-rates.ts +0 -87
- package/api/src/routes/index.ts +0 -142
- package/api/src/routes/integrations/stripe.ts +0 -61
- package/api/src/routes/meters.ts +0 -274
- package/api/src/routes/passports.ts +0 -68
- package/api/src/routes/redirect.ts +0 -20
- package/api/src/routes/tool.ts +0 -65
- package/api/src/routes/webhook-endpoints.ts +0 -126
- package/api/tests/routes/credit-grants.spec.ts +0 -1261
- package/cloudflare/did-connect-auth.ts +0 -527
- package/cloudflare/shims/did-space-js.ts +0 -17
- package/cloudflare/shims/did-space.ts +0 -11
- package/cloudflare/shims/express-compat/index.ts +0 -80
- package/cloudflare/shims/express-compat/types.ts +0 -41
- package/cloudflare/shims/lock.ts +0 -115
- package/cloudflare/shims/queue.ts +0 -611
- package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
|
@@ -11,6 +11,9 @@ const REPRESENTATIVE_TOKENS = ['ABT', 'ETH'];
|
|
|
11
11
|
interface HealthCheckJob {
|
|
12
12
|
type: 'health_check';
|
|
13
13
|
timestamp: number;
|
|
14
|
+
/** D6: the tenant this check belongs to — carried in the payload so the
|
|
15
|
+
* re-schedule survives outside any ALS context (multi mode). */
|
|
16
|
+
instance_did?: string;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
interface HealthCheckResult {
|
|
@@ -201,43 +204,59 @@ export const exchangeRateHealthQueue = createQueue<HealthCheckJob>({
|
|
|
201
204
|
// Schedule health checks every 6 hours
|
|
202
205
|
const HEALTH_CHECK_INTERVAL = 6 * 60 * 60; // 6 hours in seconds
|
|
203
206
|
|
|
207
|
+
// D6: every scheduled health-check job carries its tenant in the PAYLOAD so the
|
|
208
|
+
// re-schedule (on 'finished') preserves it WITHOUT relying on the ALS context —
|
|
209
|
+
// the 'finished' listener fires outside any withTenant() scope. In multi mode a
|
|
210
|
+
// tenant-less push would throw TENANT_CONTEXT_MISSING; here `instance_did` is set
|
|
211
|
+
// on the job, so injectJobTenant honors it instead of reading the (absent) ALS.
|
|
212
|
+
let scheduleListenerBound = false;
|
|
213
|
+
|
|
214
|
+
function scheduleNextHealthCheck(instanceDid?: string): void {
|
|
215
|
+
const now = Math.floor(Date.now() / 1000);
|
|
216
|
+
const nextRun = now + HEALTH_CHECK_INTERVAL;
|
|
217
|
+
|
|
218
|
+
exchangeRateHealthQueue.push({
|
|
219
|
+
job: {
|
|
220
|
+
type: 'health_check',
|
|
221
|
+
timestamp: Date.now(),
|
|
222
|
+
// carry the tenant in the payload (multi mode has no ALS context here)
|
|
223
|
+
...(instanceDid ? { instance_did: instanceDid } : {}),
|
|
224
|
+
} as HealthCheckJob,
|
|
225
|
+
runAt: nextRun,
|
|
226
|
+
persist: true,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
logger.info('Scheduled next exchange rate health check', {
|
|
230
|
+
nextRunAt: new Date(nextRun * 1000).toISOString(),
|
|
231
|
+
intervalHours: HEALTH_CHECK_INTERVAL / 3600,
|
|
232
|
+
instanceDid,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
204
236
|
/**
|
|
205
|
-
*
|
|
237
|
+
* Schedule periodic health checks for a tenant. The initial push AND every
|
|
238
|
+
* re-schedule carry `instance_did` in the job payload, so the cancel-then-replay
|
|
239
|
+
* recovery (D2) and the post-restart redispatch stay under the correct tenant.
|
|
206
240
|
*/
|
|
207
|
-
export function scheduleHealthChecks(): void {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
},
|
|
217
|
-
runAt: nextRun,
|
|
218
|
-
persist: true,
|
|
241
|
+
export function scheduleHealthChecks(instanceDid?: string): void {
|
|
242
|
+
scheduleNextHealthCheck(instanceDid);
|
|
243
|
+
|
|
244
|
+
// bind the re-schedule listener ONCE (idempotent across bootstrapTenant calls);
|
|
245
|
+
// it reads the finished job's own instance_did to re-schedule under that tenant.
|
|
246
|
+
if (!scheduleListenerBound) {
|
|
247
|
+
scheduleListenerBound = true;
|
|
248
|
+
exchangeRateHealthQueue.on('finished', (data: { job?: HealthCheckJob }) => {
|
|
249
|
+
scheduleNextHealthCheck(data?.job?.instance_did);
|
|
219
250
|
});
|
|
220
|
-
|
|
221
|
-
logger.info('Scheduled next exchange rate health check', {
|
|
222
|
-
nextRunAt: new Date(nextRun * 1000).toISOString(),
|
|
223
|
-
intervalHours: HEALTH_CHECK_INTERVAL / 3600,
|
|
224
|
-
});
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
// Schedule initial check
|
|
228
|
-
scheduleNext();
|
|
229
|
-
|
|
230
|
-
// Listen for completed checks and schedule next one
|
|
231
|
-
exchangeRateHealthQueue.on('finished', () => {
|
|
232
|
-
scheduleNext();
|
|
233
|
-
});
|
|
251
|
+
}
|
|
234
252
|
|
|
235
253
|
logger.info('Exchange rate health check scheduling initialized', {
|
|
236
254
|
intervalHours: HEALTH_CHECK_INTERVAL / 3600,
|
|
255
|
+
instanceDid,
|
|
237
256
|
});
|
|
238
257
|
}
|
|
239
258
|
|
|
240
|
-
// Export for explicit initialization
|
|
241
|
-
export function startExchangeRateHealthQueue(): void {
|
|
242
|
-
scheduleHealthChecks();
|
|
259
|
+
// Export for explicit initialization (single mode / per-tenant bootstrap).
|
|
260
|
+
export function startExchangeRateHealthQueue(instanceDid?: string): void {
|
|
261
|
+
scheduleHealthChecks(instanceDid);
|
|
243
262
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Op } from 'sequelize';
|
|
2
|
+
import { systemFindAll, systemFindByPk } from '../store/scoped';
|
|
2
3
|
|
|
3
4
|
import { getInvoiceShouldPayTotal, handleOverdraftProtectionInvoiceAfterPayment } from '../libs/invoice';
|
|
4
|
-
import { createEvent } from '../libs/audit';
|
|
5
|
+
import { createEvent, reportAuditFailure } from '../libs/audit';
|
|
5
6
|
import dayjs from '../libs/dayjs';
|
|
6
7
|
import logger from '../libs/logger';
|
|
7
|
-
import createQueue from '../libs/queue';
|
|
8
|
+
import createQueue, { assertJobObjectTenant } from '../libs/queue';
|
|
8
9
|
import { PaymentMethod, Invoice, InvoiceItem } from '../store/models';
|
|
9
10
|
import { CheckoutSession } from '../store/models/checkout-session';
|
|
10
11
|
import { PaymentIntent } from '../store/models/payment-intent';
|
|
@@ -29,11 +30,12 @@ type InvoiceJob = {
|
|
|
29
30
|
export const handleInvoice = async (job: InvoiceJob) => {
|
|
30
31
|
logger.info('handle invoice', job);
|
|
31
32
|
|
|
32
|
-
const invoice = await Invoice
|
|
33
|
+
const invoice = await systemFindByPk(Invoice, job.invoiceId);
|
|
33
34
|
if (!invoice) {
|
|
34
35
|
logger.warn('invoice not found', { invoiceId: job.invoiceId });
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
38
|
+
assertJobObjectTenant(invoice);
|
|
37
39
|
if (invoice.status !== 'open') {
|
|
38
40
|
logger.warn('invoice not open', { invoiceId: job.invoiceId });
|
|
39
41
|
return;
|
|
@@ -63,7 +65,7 @@ export const handleInvoice = async (job: InvoiceJob) => {
|
|
|
63
65
|
logger.info('Invoice updated to paid status', { invoiceId: invoice.id });
|
|
64
66
|
|
|
65
67
|
if (invoice.subscription_id) {
|
|
66
|
-
const subscription = await Subscription
|
|
68
|
+
const subscription = await systemFindByPk(Subscription, invoice.subscription_id);
|
|
67
69
|
if (subscription) {
|
|
68
70
|
if (subscription.status === 'incomplete') {
|
|
69
71
|
await subscription.start();
|
|
@@ -78,17 +80,17 @@ export const handleInvoice = async (job: InvoiceJob) => {
|
|
|
78
80
|
}
|
|
79
81
|
} else {
|
|
80
82
|
if (invoice.billing_reason === 'subscription_cycle') {
|
|
81
|
-
createEvent('Subscription', 'customer.subscription.renewed', subscription).catch(
|
|
83
|
+
createEvent('Subscription', 'customer.subscription.renewed', subscription).catch(reportAuditFailure);
|
|
82
84
|
}
|
|
83
85
|
if (invoice.billing_reason === 'subscription_update') {
|
|
84
|
-
createEvent('Subscription', 'customer.subscription.upgraded', subscription).catch(
|
|
86
|
+
createEvent('Subscription', 'customer.subscription.upgraded', subscription).catch(reportAuditFailure);
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
if (invoice.checkout_session_id) {
|
|
91
|
-
const checkoutSession = await CheckoutSession
|
|
93
|
+
const checkoutSession = await systemFindByPk(CheckoutSession, invoice.checkout_session_id);
|
|
92
94
|
if (checkoutSession && checkoutSession.status === 'open') {
|
|
93
95
|
await checkoutSession.update({ status: 'complete', payment_status: 'no_payment_required' });
|
|
94
96
|
logger.info('invoice checkout session updated', { invoice: invoice.id, checkoutSession: checkoutSession.id });
|
|
@@ -96,7 +98,7 @@ export const handleInvoice = async (job: InvoiceJob) => {
|
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
if (invoice.payment_intent_id) {
|
|
99
|
-
const paymentIntent = await PaymentIntent
|
|
101
|
+
const paymentIntent = await systemFindByPk(PaymentIntent, invoice.payment_intent_id);
|
|
100
102
|
if (
|
|
101
103
|
paymentIntent &&
|
|
102
104
|
['requires_action', 'requires_capture', 'requires_payment_method'].includes(paymentIntent.status)
|
|
@@ -119,7 +121,7 @@ export const handleInvoice = async (job: InvoiceJob) => {
|
|
|
119
121
|
invoiceId: job.invoiceId,
|
|
120
122
|
paymentIntent: invoice.payment_intent_id,
|
|
121
123
|
});
|
|
122
|
-
paymentIntent = await PaymentIntent
|
|
124
|
+
paymentIntent = await systemFindByPk(PaymentIntent, invoice.payment_intent_id);
|
|
123
125
|
if (paymentIntent && paymentIntent.isImmutable() === false) {
|
|
124
126
|
await paymentIntent.update({ status: 'requires_capture', customer_id: invoice.customer_id });
|
|
125
127
|
logger.info('PaymentIntent updated for invoice', {
|
|
@@ -166,7 +168,7 @@ export const handleInvoice = async (job: InvoiceJob) => {
|
|
|
166
168
|
});
|
|
167
169
|
|
|
168
170
|
if (invoice.checkout_session_id) {
|
|
169
|
-
const checkoutSession = await CheckoutSession
|
|
171
|
+
const checkoutSession = await systemFindByPk(CheckoutSession, invoice.checkout_session_id);
|
|
170
172
|
if (checkoutSession && checkoutSession.status === 'open') {
|
|
171
173
|
await checkoutSession.update({ payment_intent_id: paymentIntent.id });
|
|
172
174
|
logger.info('PaymentIntent attached to invoice checkout session', {
|
|
@@ -212,14 +214,14 @@ export const invoiceQueue = createQueue<InvoiceJob>({
|
|
|
212
214
|
});
|
|
213
215
|
|
|
214
216
|
export const startInvoiceQueue = async () => {
|
|
215
|
-
const lock = getLock('startInvoiceQueue');
|
|
217
|
+
const lock = getLock('startInvoiceQueue', { scope: 'global' });
|
|
216
218
|
if (lock.locked) {
|
|
217
219
|
return;
|
|
218
220
|
}
|
|
219
221
|
|
|
220
222
|
try {
|
|
221
223
|
await lock.acquire();
|
|
222
|
-
const invoices = await Invoice
|
|
224
|
+
const invoices = await systemFindAll(Invoice, {
|
|
223
225
|
where: {
|
|
224
226
|
status: 'open',
|
|
225
227
|
collection_method: 'charge_automatically',
|
|
@@ -259,11 +261,12 @@ invoiceQueue.on('failed', ({ id, job, error }) => {
|
|
|
259
261
|
});
|
|
260
262
|
|
|
261
263
|
events.on('invoice.paid', async ({ id: invoiceId }) => {
|
|
262
|
-
const invoice = await Invoice
|
|
264
|
+
const invoice = await systemFindByPk(Invoice, invoiceId);
|
|
263
265
|
if (!invoice) {
|
|
264
266
|
logger.error('Invoice not found for paid event', { invoiceId });
|
|
265
267
|
return;
|
|
266
268
|
}
|
|
269
|
+
assertJobObjectTenant(invoice);
|
|
267
270
|
logger.info('Processing paid invoice', { invoiceId, billingReason: invoice.billing_reason });
|
|
268
271
|
|
|
269
272
|
const checkBillingReason = ['subscription_cycle', 'subscription_cancel'];
|
|
@@ -286,7 +289,7 @@ events.on('invoice.paid', async ({ id: invoiceId }) => {
|
|
|
286
289
|
// Separate listener to mark quotes as paid - independent of other invoice.paid handlers
|
|
287
290
|
events.on('invoice.paid', async ({ id: invoiceId }) => {
|
|
288
291
|
try {
|
|
289
|
-
const invoiceItems = await InvoiceItem
|
|
292
|
+
const invoiceItems = await systemFindAll(InvoiceItem, {
|
|
290
293
|
where: { invoice_id: invoiceId },
|
|
291
294
|
});
|
|
292
295
|
|
|
@@ -304,7 +307,7 @@ events.on('invoice.paid', async ({ id: invoiceId }) => {
|
|
|
304
307
|
await Promise.all(
|
|
305
308
|
quoteIds.map(async (quoteId) => {
|
|
306
309
|
try {
|
|
307
|
-
const quote = await PriceQuote
|
|
310
|
+
const quote = await systemFindByPk(PriceQuote, quoteId);
|
|
308
311
|
if (quote && quote.status !== 'paid') {
|
|
309
312
|
await quote.update({ invoice_id: invoiceId });
|
|
310
313
|
await quoteService.markAsPaid(quoteId);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Op } from 'sequelize';
|
|
2
2
|
import debounce from 'lodash/debounce';
|
|
3
|
+
import { systemFindByPk } from '../store/scoped';
|
|
3
4
|
/* eslint-disable @typescript-eslint/indent */
|
|
4
5
|
import { events } from '../libs/event';
|
|
5
6
|
import logger from '../libs/logger';
|
|
@@ -62,7 +63,7 @@ import {
|
|
|
62
63
|
SubscriptionStakeSlashSucceededEmailTemplate,
|
|
63
64
|
SubscriptionStakeSlashSucceededEmailTemplateOptions,
|
|
64
65
|
} from '../libs/notification/template/subscription-stake-slash-succeeded';
|
|
65
|
-
import createQueue from '../libs/queue';
|
|
66
|
+
import createQueue, { assertJobObjectTenant } from '../libs/queue';
|
|
66
67
|
import {
|
|
67
68
|
CheckoutSession,
|
|
68
69
|
EventType,
|
|
@@ -263,7 +264,8 @@ async function getNotificationTemplate(job: NotificationQueueJob): Promise<BaseE
|
|
|
263
264
|
}
|
|
264
265
|
if (job.type === 'refund.succeeded') {
|
|
265
266
|
const { refundId } = job.options as { refundId: string };
|
|
266
|
-
const refund = await Refund
|
|
267
|
+
const refund = await systemFindByPk(Refund, refundId);
|
|
268
|
+
if (refund) assertJobObjectTenant(refund);
|
|
267
269
|
if (refund?.subscription_id) {
|
|
268
270
|
return new SubscriptionRefundSucceededEmailTemplate(
|
|
269
271
|
job.options as SubscriptionRefundSucceededEmailTemplateOptions
|
|
@@ -570,7 +572,8 @@ export async function startNotificationQueue() {
|
|
|
570
572
|
// 一次性购买成功
|
|
571
573
|
events.on('checkout.session.completed', async (checkoutSession: CheckoutSession) => {
|
|
572
574
|
if (checkoutSession.mode === 'payment') {
|
|
573
|
-
const paymentLink = await PaymentLink
|
|
575
|
+
const paymentLink = await systemFindByPk(PaymentLink, checkoutSession.payment_link_id);
|
|
576
|
+
if (paymentLink) assertJobObjectTenant(paymentLink);
|
|
574
577
|
if (paymentLink?.submit_type === 'donate') {
|
|
575
578
|
addNotificationJob('customer.reward.succeeded', { checkoutSessionId: checkoutSession.id }, [
|
|
576
579
|
checkoutSession.id,
|
|
@@ -583,7 +586,8 @@ export async function startNotificationQueue() {
|
|
|
583
586
|
});
|
|
584
587
|
|
|
585
588
|
events.on('customer.subscription.renewed', async (subscription: Subscription) => {
|
|
586
|
-
const customer = await Customer
|
|
589
|
+
const customer = await systemFindByPk(Customer, subscription.customer_id);
|
|
590
|
+
if (customer) assertJobObjectTenant(customer);
|
|
587
591
|
const preference = customer?.preference?.notification;
|
|
588
592
|
if (!subscription.latest_invoice_id) {
|
|
589
593
|
logger.warn('Missing latest_invoice_id for subscription', { subscriptionId: subscription.id });
|
|
@@ -619,7 +623,8 @@ export async function startNotificationQueue() {
|
|
|
619
623
|
});
|
|
620
624
|
|
|
621
625
|
events.on('customer.subscription.renew_failed', async (subscription: Subscription, { invoiceId }) => {
|
|
622
|
-
const invoice = await Invoice
|
|
626
|
+
const invoice = await systemFindByPk(Invoice, invoiceId || subscription.latest_invoice_id);
|
|
627
|
+
if (invoice) assertJobObjectTenant(invoice);
|
|
623
628
|
|
|
624
629
|
logger.info('events.on', 'customer.subscription.renew_failed', {
|
|
625
630
|
subscriptionId: subscription.id,
|
|
@@ -731,7 +736,8 @@ export async function startNotificationQueue() {
|
|
|
731
736
|
events.on(
|
|
732
737
|
'customer.auto_recharge.failed',
|
|
733
738
|
async (customer: Customer, { autoRechargeConfigId, invoiceId, paymentIntentId }) => {
|
|
734
|
-
const invoice = await Invoice
|
|
739
|
+
const invoice = await systemFindByPk(Invoice, invoiceId);
|
|
740
|
+
if (invoice) assertJobObjectTenant(invoice);
|
|
735
741
|
logger.info('addNotificationJob:customer.auto_recharge.failed', autoRechargeConfigId);
|
|
736
742
|
if (invoice && autoRechargeConfigId && invoice.metadata?.auto_recharge_failed_reason) {
|
|
737
743
|
addNotificationJob(
|
|
@@ -819,7 +825,8 @@ export async function startNotificationQueue() {
|
|
|
819
825
|
|
|
820
826
|
// Reuse customer.auto_recharge.failed notification with skipped reason
|
|
821
827
|
// This ensures users are notified when auto-recharge cannot proceed
|
|
822
|
-
const customer = await Customer
|
|
828
|
+
const customer = await systemFindByPk(Customer, data.customer_id);
|
|
829
|
+
if (customer) assertJobObjectTenant(customer);
|
|
823
830
|
if (customer) {
|
|
824
831
|
addNotificationJob(
|
|
825
832
|
'customer.auto_recharge.failed',
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import isEmpty from 'lodash/isEmpty';
|
|
2
|
-
|
|
3
2
|
import { BN } from '@ocap/util';
|
|
3
|
+
import { isCfWorker } from '../libs/env';
|
|
4
|
+
import { systemFindAll, systemFindByPk, systemFindOne } from '../store/scoped';
|
|
5
|
+
|
|
4
6
|
import { ensureStakedForGas } from '../integrations/arcblock/stake';
|
|
5
7
|
import { transferErc20FromUser } from '../integrations/ethereum/token';
|
|
6
|
-
import { createEvent } from '../libs/audit';
|
|
8
|
+
import { createEvent, reportAuditFailure } from '../libs/audit';
|
|
7
9
|
import { blocklet, wallet } from '../libs/auth';
|
|
10
|
+
import { withTenant } from '../libs/context';
|
|
8
11
|
import dayjs from '../libs/dayjs';
|
|
9
12
|
import CustomError from '../libs/error';
|
|
10
13
|
import { events } from '../libs/event';
|
|
@@ -41,7 +44,7 @@ import { notificationQueue } from './notification';
|
|
|
41
44
|
import { ensureOverdraftProtectionInvoiceAndItems } from '../libs/invoice';
|
|
42
45
|
import { AutoRechargeConfig, Lock, MeterEvent } from '../store/models';
|
|
43
46
|
import { ensureOverdraftProtectionPrice } from '../libs/overdraft-protection';
|
|
44
|
-
import createQueue from '../libs/queue';
|
|
47
|
+
import createQueue, { assertJobObjectTenant } from '../libs/queue';
|
|
45
48
|
import { CHARGE_SUPPORTED_CHAIN_TYPES, EVM_CHAIN_TYPES } from '../libs/constants';
|
|
46
49
|
import { getCheckoutSessionSubscriptionIds, getSubscriptionCreateSetup, SlippageOptions } from '../libs/session';
|
|
47
50
|
import { syncStripeSubscriptionAfterRecovery } from '../integrations/stripe/handlers/subscription';
|
|
@@ -170,11 +173,11 @@ export async function updateSubscriptionOnPaymentSuccess(
|
|
|
170
173
|
// Trigger renewal and upgrade events
|
|
171
174
|
if (triggerRenew && (!invoice || invoice.billing_reason !== 'subscription_update')) {
|
|
172
175
|
if (!invoice || invoice.billing_reason === 'subscription_cycle' || paymentIntent?.capture_method === 'manual') {
|
|
173
|
-
createEvent('Subscription', 'customer.subscription.renewed', subscription).catch(
|
|
176
|
+
createEvent('Subscription', 'customer.subscription.renewed', subscription).catch(reportAuditFailure);
|
|
174
177
|
}
|
|
175
178
|
}
|
|
176
179
|
if (invoice?.billing_reason === 'subscription_update') {
|
|
177
|
-
createEvent('Subscription', 'customer.subscription.upgraded', subscription).catch(
|
|
180
|
+
createEvent('Subscription', 'customer.subscription.upgraded', subscription).catch(reportAuditFailure);
|
|
178
181
|
}
|
|
179
182
|
}
|
|
180
183
|
|
|
@@ -232,11 +235,11 @@ export async function handlePastDueSubscriptionRecovery(
|
|
|
232
235
|
}
|
|
233
236
|
|
|
234
237
|
// Reset billing cycle
|
|
235
|
-
const subscriptionItems = await SubscriptionItem
|
|
238
|
+
const subscriptionItems = await systemFindAll(SubscriptionItem, { where: { subscription_id: subscription.id } });
|
|
236
239
|
const lineItems = await Price.expand(subscriptionItems.map((x) => x.toJSON()));
|
|
237
240
|
// Use the subscription's slippage config if available, otherwise use default 0.5%
|
|
238
241
|
const slippageConfig = subscription.slippage_config;
|
|
239
|
-
const currency = await PaymentCurrency
|
|
242
|
+
const currency = await systemFindByPk(PaymentCurrency, subscription.currency_id);
|
|
240
243
|
const slippageOptions: SlippageOptions = {
|
|
241
244
|
percent: slippageConfig?.percent ?? 0.5,
|
|
242
245
|
minAcceptableRate: slippageConfig?.min_acceptable_rate,
|
|
@@ -255,7 +258,7 @@ export async function handlePastDueSubscriptionRecovery(
|
|
|
255
258
|
cancelation_details: null,
|
|
256
259
|
});
|
|
257
260
|
|
|
258
|
-
createEvent('Subscription', 'customer.subscription.recovered', subscription).catch(
|
|
261
|
+
createEvent('Subscription', 'customer.subscription.recovered', subscription).catch(reportAuditFailure);
|
|
259
262
|
const recoveryReason =
|
|
260
263
|
subscription.cancelation_details?.reason === 'insufficient_credit' ? 'credit replenished' : 'payment done';
|
|
261
264
|
logger.info(
|
|
@@ -405,20 +408,20 @@ export const handlePaymentSucceed = async (
|
|
|
405
408
|
|
|
406
409
|
let invoice;
|
|
407
410
|
if (paymentIntent.invoice_id) {
|
|
408
|
-
invoice = await Invoice
|
|
411
|
+
invoice = await systemFindByPk(Invoice, paymentIntent.invoice_id);
|
|
409
412
|
}
|
|
410
413
|
|
|
411
414
|
// Handle checkout session when no invoice exists
|
|
412
415
|
if (!invoice && !slashStake) {
|
|
413
|
-
const checkoutSession = await CheckoutSession
|
|
416
|
+
const checkoutSession = await systemFindOne(CheckoutSession, { where: { payment_intent_id: paymentIntent.id } });
|
|
414
417
|
if (checkoutSession) {
|
|
415
418
|
if (
|
|
416
|
-
(
|
|
419
|
+
isCfWorker() &&
|
|
417
420
|
checkoutSession.mode === 'payment' &&
|
|
418
421
|
checkoutSession.invoice_creation?.enabled &&
|
|
419
422
|
!checkoutSession.invoice_id
|
|
420
423
|
) {
|
|
421
|
-
const customer = await Customer
|
|
424
|
+
const customer = await systemFindByPk(Customer, checkoutSession.customer_id);
|
|
422
425
|
if (customer) {
|
|
423
426
|
try {
|
|
424
427
|
const result = await ensureInvoiceForCheckout({
|
|
@@ -480,7 +483,7 @@ export const handlePaymentSucceed = async (
|
|
|
480
483
|
}
|
|
481
484
|
// Update subscription status
|
|
482
485
|
if (invoice && invoice.subscription_id && !slashStake) {
|
|
483
|
-
const subscription = await Subscription
|
|
486
|
+
const subscription = await systemFindByPk(Subscription, invoice.subscription_id);
|
|
484
487
|
if (subscription) {
|
|
485
488
|
await updateSubscriptionOnPaymentSuccess(paymentIntent, subscription, invoice, triggerRenew);
|
|
486
489
|
}
|
|
@@ -488,7 +491,7 @@ export const handlePaymentSucceed = async (
|
|
|
488
491
|
|
|
489
492
|
// Update checkout session
|
|
490
493
|
if (invoice && invoice.checkout_session_id && !slashStake) {
|
|
491
|
-
const checkoutSession = await CheckoutSession
|
|
494
|
+
const checkoutSession = await systemFindByPk(CheckoutSession, invoice.checkout_session_id);
|
|
492
495
|
if (checkoutSession) {
|
|
493
496
|
await updateCheckoutSessionOnPaymentSuccess(paymentIntent, checkoutSession, invoice);
|
|
494
497
|
}
|
|
@@ -508,7 +511,7 @@ export const doOverdraftProtection = async (
|
|
|
508
511
|
});
|
|
509
512
|
return;
|
|
510
513
|
}
|
|
511
|
-
const existProtectedInvoice = await Invoice
|
|
514
|
+
const existProtectedInvoice = await systemFindOne(Invoice, {
|
|
512
515
|
where: {
|
|
513
516
|
subscription_id: subscription.id,
|
|
514
517
|
billing_reason: 'overdraft_protection',
|
|
@@ -593,10 +596,11 @@ export const handlePaymentFailed = async (
|
|
|
593
596
|
if (!invoice.subscription_id) {
|
|
594
597
|
return updates.retry;
|
|
595
598
|
}
|
|
596
|
-
const subscription = await Subscription
|
|
599
|
+
const subscription = await systemFindByPk(Subscription, invoice.subscription_id);
|
|
597
600
|
if (!subscription) {
|
|
598
601
|
return updates.retry;
|
|
599
602
|
}
|
|
603
|
+
assertJobObjectTenant(subscription);
|
|
600
604
|
|
|
601
605
|
const { interval } = subscription.pending_invoice_item_interval;
|
|
602
606
|
updates.retry.minRetryMail = getMinRetryMail(interval);
|
|
@@ -636,7 +640,7 @@ export const handlePaymentFailed = async (
|
|
|
636
640
|
return updates.terminate;
|
|
637
641
|
}
|
|
638
642
|
// check overdraft protection, if protected, no need to check due
|
|
639
|
-
const customer = await Customer
|
|
643
|
+
const customer = await systemFindByPk(Customer, invoice.customer_id);
|
|
640
644
|
|
|
641
645
|
const { enabled: enableOverdraftProtection, unused: unusedAmount } =
|
|
642
646
|
await isSubscriptionOverdraftProtectionEnabled(subscription);
|
|
@@ -649,7 +653,9 @@ export const handlePaymentFailed = async (
|
|
|
649
653
|
const isLock = await Lock.isLocked(lockKey);
|
|
650
654
|
if (!isLock) {
|
|
651
655
|
await Lock.acquire(lockKey, subscription.current_period_end);
|
|
652
|
-
createEvent('Subscription', 'subscription.overdraft_protection.exhausted', subscription).catch(
|
|
656
|
+
createEvent('Subscription', 'subscription.overdraft_protection.exhausted', subscription).catch(
|
|
657
|
+
reportAuditFailure
|
|
658
|
+
);
|
|
653
659
|
}
|
|
654
660
|
}
|
|
655
661
|
|
|
@@ -735,7 +741,10 @@ export const handlePaymentFailed = async (
|
|
|
735
741
|
});
|
|
736
742
|
|
|
737
743
|
if (invoice.billing_reason === 'auto_recharge' && invoice.metadata?.recharge_config?.recharge_id) {
|
|
738
|
-
const autoRechargeConfig = await
|
|
744
|
+
const autoRechargeConfig = await systemFindByPk(
|
|
745
|
+
AutoRechargeConfig,
|
|
746
|
+
invoice.metadata?.recharge_config?.recharge_id
|
|
747
|
+
);
|
|
739
748
|
if (autoRechargeConfig && autoRechargeConfig.enabled) {
|
|
740
749
|
autoRechargeConfig.update({
|
|
741
750
|
enabled: false,
|
|
@@ -760,7 +769,7 @@ const handleStakeSlash = async (
|
|
|
760
769
|
customer: Customer,
|
|
761
770
|
paymentCurrency: PaymentCurrency
|
|
762
771
|
) => {
|
|
763
|
-
const subscription = await Subscription
|
|
772
|
+
const subscription = await systemFindByPk(Subscription, invoice.subscription_id);
|
|
764
773
|
if (!subscription) {
|
|
765
774
|
logger.warn('Stake slashing skipped because Subscription not found', {
|
|
766
775
|
subscription: invoice.subscription_id,
|
|
@@ -769,6 +778,7 @@ const handleStakeSlash = async (
|
|
|
769
778
|
});
|
|
770
779
|
return;
|
|
771
780
|
}
|
|
781
|
+
assertJobObjectTenant(subscription);
|
|
772
782
|
if (!subscription.cancelation_details?.slash_stake || subscription.status !== 'canceled') {
|
|
773
783
|
logger.warn('Stake slashing skipped because subscription not canceled or slash_stake is false', {
|
|
774
784
|
subscription: invoice.subscription_id,
|
|
@@ -871,22 +881,24 @@ const handleStakeSlash = async (
|
|
|
871
881
|
export const handlePayment = async (job: PaymentJob) => {
|
|
872
882
|
logger.info('handle payment', job);
|
|
873
883
|
|
|
874
|
-
const paymentIntent = await PaymentIntent
|
|
884
|
+
const paymentIntent = await systemFindByPk(PaymentIntent, job.paymentIntentId);
|
|
875
885
|
if (!paymentIntent) {
|
|
876
886
|
logger.warn('PaymentIntent not found', { id: job.paymentIntentId });
|
|
877
887
|
return;
|
|
878
888
|
}
|
|
889
|
+
assertJobObjectTenant(paymentIntent);
|
|
879
890
|
|
|
880
891
|
if (['requires_capture', 'processing'].includes(paymentIntent.status) === false) {
|
|
881
892
|
logger.warn('PaymentIntent status not expected', { id: paymentIntent.id, status: paymentIntent.status });
|
|
882
893
|
return;
|
|
883
894
|
}
|
|
884
895
|
|
|
885
|
-
const paymentMethod = await PaymentMethod
|
|
896
|
+
const paymentMethod = await systemFindByPk(PaymentMethod, paymentIntent.payment_method_id);
|
|
886
897
|
if (!paymentMethod) {
|
|
887
898
|
logger.warn('PaymentMethod not found', { id: paymentIntent.payment_method_id });
|
|
888
899
|
return;
|
|
889
900
|
}
|
|
901
|
+
assertJobObjectTenant(paymentMethod);
|
|
890
902
|
|
|
891
903
|
const supportAutoCharge = await PaymentMethod.supportAutoCharge(paymentIntent.payment_method_id);
|
|
892
904
|
if (supportAutoCharge === false) {
|
|
@@ -894,26 +906,28 @@ export const handlePayment = async (job: PaymentJob) => {
|
|
|
894
906
|
return;
|
|
895
907
|
}
|
|
896
908
|
|
|
897
|
-
const paymentCurrency = await PaymentCurrency
|
|
909
|
+
const paymentCurrency = await systemFindByPk(PaymentCurrency, paymentIntent.currency_id);
|
|
898
910
|
if (!paymentCurrency) {
|
|
899
911
|
logger.warn('PaymentCurrency not found', { id: paymentIntent.currency_id });
|
|
900
912
|
return;
|
|
901
913
|
}
|
|
914
|
+
assertJobObjectTenant(paymentCurrency);
|
|
902
915
|
|
|
903
916
|
if (paymentCurrency.isCredit()) {
|
|
904
917
|
logger.info('PaymentIntent capture skipped because paymentCurrency is credit', { id: paymentIntent.id });
|
|
905
918
|
return;
|
|
906
919
|
}
|
|
907
920
|
|
|
908
|
-
const customer = await Customer
|
|
921
|
+
const customer = await systemFindByPk(Customer, paymentIntent.customer_id);
|
|
909
922
|
if (!customer) {
|
|
910
923
|
logger.warn('Customer not found', { id: paymentIntent.customer_id });
|
|
911
924
|
return;
|
|
912
925
|
}
|
|
926
|
+
assertJobObjectTenant(customer);
|
|
913
927
|
|
|
914
|
-
const invoice = await Invoice
|
|
928
|
+
const invoice = await systemFindByPk(Invoice, paymentIntent.invoice_id);
|
|
915
929
|
// Fetch subscription early for migration fallback support and overdraft protection check
|
|
916
|
-
const subscription = invoice?.subscription_id ? await Subscription
|
|
930
|
+
const subscription = invoice?.subscription_id ? await systemFindByPk(Subscription, invoice.subscription_id) : null;
|
|
917
931
|
|
|
918
932
|
if (invoice?.status === 'void') {
|
|
919
933
|
await paymentIntent.update({
|
|
@@ -1359,23 +1373,36 @@ export const paymentQueue = createQueue<PaymentJob>({
|
|
|
1359
1373
|
});
|
|
1360
1374
|
|
|
1361
1375
|
export const startPaymentQueue = async () => {
|
|
1362
|
-
const payments = await PaymentIntent
|
|
1376
|
+
const payments = await systemFindAll(PaymentIntent, {
|
|
1363
1377
|
where: {
|
|
1364
1378
|
status: ['requires_capture', 'processing'],
|
|
1365
1379
|
capture_method: 'automatic',
|
|
1366
1380
|
},
|
|
1367
1381
|
});
|
|
1368
1382
|
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1383
|
+
// Each pending intent belongs to a tenant: the scoped supportAutoCharge query
|
|
1384
|
+
// and the queue push (push stamps the job tenant via getInstanceDid) must run in
|
|
1385
|
+
// that tenant's context — multi mode fails closed otherwise. for-of + await,
|
|
1386
|
+
// NOT forEach(async): a fire-and-forget rejection here becomes an
|
|
1387
|
+
// unhandledRejection → FATAL on boot. Per-record try/catch isolates one bad row.
|
|
1388
|
+
for (const x of payments) {
|
|
1389
|
+
const dispatch = async () => {
|
|
1390
|
+
const supportAutoCharge = await PaymentMethod.supportAutoCharge(x.payment_method_id);
|
|
1391
|
+
if (supportAutoCharge === false) {
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
const exist = await paymentQueue.get(x.id);
|
|
1395
|
+
if (!exist) {
|
|
1396
|
+
paymentQueue.push({ id: x.id, job: { paymentIntentId: x.id } });
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
try {
|
|
1400
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1401
|
+
await (x.instance_did ? withTenant(x.instance_did, dispatch) : dispatch());
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
logger.error('startPaymentQueue: re-queue failed', { id: x.id, error });
|
|
1377
1404
|
}
|
|
1378
|
-
}
|
|
1405
|
+
}
|
|
1379
1406
|
};
|
|
1380
1407
|
|
|
1381
1408
|
paymentQueue.on('failed', ({ id, job, error }) => {
|
|
@@ -1384,7 +1411,7 @@ paymentQueue.on('failed', ({ id, job, error }) => {
|
|
|
1384
1411
|
|
|
1385
1412
|
// Do gas stake as soon as possible after payment succeed
|
|
1386
1413
|
events.on('payment_intent.succeeded', async (paymentIntent: PaymentIntent) => {
|
|
1387
|
-
const paymentMethod = await PaymentMethod
|
|
1414
|
+
const paymentMethod = await systemFindByPk(PaymentMethod, paymentIntent.payment_method_id);
|
|
1388
1415
|
if (paymentMethod && paymentMethod.type === 'arcblock') {
|
|
1389
1416
|
ensureStakedForGas();
|
|
1390
1417
|
}
|