payment-kit 1.29.0 → 1.29.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +36 -0
- package/api/src/crons/base.ts +3 -3
- package/api/src/crons/currency.ts +1 -1
- package/api/src/crons/index.ts +27 -24
- 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/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 +99 -38
- package/api/src/libs/context.ts +78 -1
- package/api/src/libs/currency.ts +2 -2
- package/api/src/libs/dayjs.ts +8 -2
- 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 +81 -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 +50 -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 +259 -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 +73 -0
- package/api/src/middlewares/hono/csrf.ts +72 -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 +214 -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 +17 -12
- 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 +12 -4
- 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 +41 -28
- package/api/src/queues/payout.ts +9 -5
- package/api/src/queues/refund.ts +18 -12
- 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} +193 -223
- 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} +96 -80
- 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 +667 -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 +66 -22
- 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/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/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/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 +236 -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/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/README.md +26 -6
- package/cloudflare/build.ts +28 -13
- package/cloudflare/did-connect-auth.ts +0 -217
- package/cloudflare/docs/2026-06-10-bundle-size-analysis.md +288 -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 +31 -56
- 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/util-csrf.ts +13 -0
- package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
- 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/node-fetch.ts +35 -0
- package/cloudflare/shims/xss.ts +8 -0
- package/cloudflare/tenant-middleware.ts +36 -0
- package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
- package/cloudflare/worker.ts +204 -433
- package/cloudflare/wrangler.local-e2e.jsonc +26 -0
- package/jest.config.js +3 -1
- package/package.json +33 -38
- 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/env.d.ts +13 -1
- package/tsconfig.json +1 -1
- 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/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
|
@@ -4,7 +4,8 @@ import { Op } from 'sequelize';
|
|
|
4
4
|
import { BN, fromTokenToUnit, fromUnitToToken } from '@ocap/util';
|
|
5
5
|
import pAll from 'p-all';
|
|
6
6
|
import dayjs from '../libs/dayjs';
|
|
7
|
-
import createQueue from '../libs/queue';
|
|
7
|
+
import createQueue, { assertJobObjectTenant } from '../libs/queue';
|
|
8
|
+
import { systemFindAll, systemFindByPk } from '../store/scoped';
|
|
8
9
|
import {
|
|
9
10
|
AutoRechargeConfig,
|
|
10
11
|
CreditGrant,
|
|
@@ -48,7 +49,7 @@ type CreditGrantJob =
|
|
|
48
49
|
* Non on-chain credits finish immediately; on-chain credits wait for mint success
|
|
49
50
|
*/
|
|
50
51
|
async function activateGrant(creditGrant: CreditGrant) {
|
|
51
|
-
const paymentCurrency = await PaymentCurrency
|
|
52
|
+
const paymentCurrency = await systemFindByPk(PaymentCurrency, creditGrant.currency_id);
|
|
52
53
|
const isOnChainCredit = paymentCurrency && PaymentCurrency.isOnChainCredit(paymentCurrency);
|
|
53
54
|
|
|
54
55
|
logger.info('Activating credit grant', {
|
|
@@ -76,10 +77,11 @@ async function activateGrant(creditGrant: CreditGrant) {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
// Verify customer
|
|
79
|
-
const customer = await Customer
|
|
80
|
+
const customer = await systemFindByPk(Customer, creditGrant.customer_id);
|
|
80
81
|
if (!customer) {
|
|
81
82
|
throw new Error(`Customer DID not found for credit grant ${creditGrant.id}`);
|
|
82
83
|
}
|
|
84
|
+
assertJobObjectTenant(customer);
|
|
83
85
|
|
|
84
86
|
const customerState = await getAccountState(paymentCurrency, customer.did);
|
|
85
87
|
|
|
@@ -161,7 +163,7 @@ export async function expireGrant(creditGrant: CreditGrant) {
|
|
|
161
163
|
return;
|
|
162
164
|
}
|
|
163
165
|
|
|
164
|
-
const paymentCurrency = await PaymentCurrency
|
|
166
|
+
const paymentCurrency = await systemFindByPk(PaymentCurrency, creditGrant.currency_id);
|
|
165
167
|
if (!paymentCurrency) {
|
|
166
168
|
logger.error('Payment currency not found for credit grant transfer', {
|
|
167
169
|
creditGrantId: creditGrant.id,
|
|
@@ -171,9 +173,10 @@ export async function expireGrant(creditGrant: CreditGrant) {
|
|
|
171
173
|
await creditGrant.update({ status: 'expired' });
|
|
172
174
|
return;
|
|
173
175
|
}
|
|
176
|
+
assertJobObjectTenant(paymentCurrency);
|
|
174
177
|
|
|
175
178
|
// Get customer DID
|
|
176
|
-
const customer = await Customer
|
|
179
|
+
const customer = await systemFindByPk(Customer, creditGrant.customer_id);
|
|
177
180
|
if (!customer?.did) {
|
|
178
181
|
logger.error('Customer DID not found for credit grant transfer', {
|
|
179
182
|
creditGrantId: creditGrant.id,
|
|
@@ -280,11 +283,12 @@ const handleCreditGrantJob = async (job: CreditGrantJob) => {
|
|
|
280
283
|
|
|
281
284
|
const { creditGrantId, action } = job as { creditGrantId: string; action: 'activate' | 'expire' };
|
|
282
285
|
|
|
283
|
-
const creditGrant = await CreditGrant
|
|
286
|
+
const creditGrant = await systemFindByPk(CreditGrant, creditGrantId);
|
|
284
287
|
if (!creditGrant) {
|
|
285
288
|
logger.error('Credit grant not found', { creditGrantId });
|
|
286
289
|
return;
|
|
287
290
|
}
|
|
291
|
+
assertJobObjectTenant(creditGrant);
|
|
288
292
|
|
|
289
293
|
const now = dayjs().unix();
|
|
290
294
|
|
|
@@ -411,7 +415,7 @@ export async function scheduleCreditGrantJobs(creditGrant: CreditGrant) {
|
|
|
411
415
|
export const startCreditGrantQueue = async () => {
|
|
412
416
|
logger.info('Starting credit grant queue...');
|
|
413
417
|
|
|
414
|
-
const grantsToSchedule = await CreditGrant
|
|
418
|
+
const grantsToSchedule = await systemFindAll(CreditGrant, {
|
|
415
419
|
where: {
|
|
416
420
|
[Op.or]: [
|
|
417
421
|
{ status: 'pending' },
|
|
@@ -424,7 +428,7 @@ export const startCreditGrantQueue = async () => {
|
|
|
424
428
|
order: [['created_at', 'ASC']],
|
|
425
429
|
});
|
|
426
430
|
|
|
427
|
-
const invoicesToSchedule = await Invoice
|
|
431
|
+
const invoicesToSchedule = await systemFindAll(Invoice, {
|
|
428
432
|
where: {
|
|
429
433
|
status: 'paid',
|
|
430
434
|
metadata: {
|
|
@@ -482,7 +486,7 @@ async function recoverCreditScheduleJobs(): Promise<void> {
|
|
|
482
486
|
try {
|
|
483
487
|
// Find subscriptions with active credit schedules
|
|
484
488
|
// We filter for non-null credit_schedule_state in application code
|
|
485
|
-
const allActiveSubscriptions = await Subscription
|
|
489
|
+
const allActiveSubscriptions = await systemFindAll(Subscription, {
|
|
486
490
|
where: {
|
|
487
491
|
status: ['active', 'trialing'],
|
|
488
492
|
},
|
|
@@ -589,7 +593,7 @@ creditGrantQueue.on('finished', ({ id }) => {
|
|
|
589
593
|
|
|
590
594
|
async function handleInvoiceCredit(invoiceId: string) {
|
|
591
595
|
try {
|
|
592
|
-
const invoice = (await Invoice
|
|
596
|
+
const invoice = (await systemFindByPk(Invoice, invoiceId, {
|
|
593
597
|
include: [
|
|
594
598
|
{
|
|
595
599
|
model: Customer,
|
|
@@ -619,7 +623,7 @@ async function handleInvoiceCredit(invoiceId: string) {
|
|
|
619
623
|
customerId: invoice.customer.id,
|
|
620
624
|
});
|
|
621
625
|
if (invoice.metadata?.auto_recharge?.config_id) {
|
|
622
|
-
const autoRechargeConfig = await AutoRechargeConfig
|
|
626
|
+
const autoRechargeConfig = await systemFindByPk(AutoRechargeConfig, invoice.metadata?.auto_recharge?.config_id);
|
|
623
627
|
if (autoRechargeConfig) {
|
|
624
628
|
await autoRechargeConfig.updateDailyStats(invoice.total);
|
|
625
629
|
}
|
|
@@ -635,7 +639,7 @@ async function handleInvoiceCredit(invoiceId: string) {
|
|
|
635
639
|
|
|
636
640
|
const items = await Promise.all(
|
|
637
641
|
lineItems.map(async (lineItem) => {
|
|
638
|
-
const price = (await Price
|
|
642
|
+
const price = (await systemFindByPk(Price, lineItem.price_id, {
|
|
639
643
|
include: [{ model: Product, as: 'product' }],
|
|
640
644
|
})) as TPriceExpanded | null;
|
|
641
645
|
|
|
@@ -664,7 +668,7 @@ async function handleInvoiceCredit(invoiceId: string) {
|
|
|
664
668
|
}
|
|
665
669
|
|
|
666
670
|
const quantity = lineItem.quantity || 1;
|
|
667
|
-
const currency = await PaymentCurrency
|
|
671
|
+
const currency = await systemFindByPk(PaymentCurrency, creditConfig.currency_id);
|
|
668
672
|
|
|
669
673
|
if (!currency) {
|
|
670
674
|
logger.error('Currency not found', { currencyId: creditConfig.currency_id, invoiceId });
|
|
@@ -726,7 +730,7 @@ async function handleInvoiceCredit(invoiceId: string) {
|
|
|
726
730
|
return;
|
|
727
731
|
}
|
|
728
732
|
|
|
729
|
-
const existingGrants = await CreditGrant
|
|
733
|
+
const existingGrants = await systemFindAll(CreditGrant, {
|
|
730
734
|
where: {
|
|
731
735
|
customer_id: invoice.customer.id,
|
|
732
736
|
metadata: {
|
|
@@ -972,7 +976,7 @@ events.on('invoice.paid', async (invoice: Invoice) => {
|
|
|
972
976
|
try {
|
|
973
977
|
await addInvoiceCreditJob(invoice.id);
|
|
974
978
|
if (invoice.subscription_id) {
|
|
975
|
-
const subscription = await Subscription
|
|
979
|
+
const subscription = await systemFindByPk(Subscription, invoice.subscription_id);
|
|
976
980
|
if (subscription) {
|
|
977
981
|
const jobs = await activateAfterFirstPaymentSchedules(subscription, invoice.status_transitions?.paid_at);
|
|
978
982
|
await Promise.all(jobs.map((job) => addScheduledCreditJob(job.jobId, job.job, job.runAt, true)));
|
|
@@ -989,11 +993,12 @@ events.on('invoice.paid', async (invoice: Invoice) => {
|
|
|
989
993
|
events.on('customer.credit_grant.created', async (data: { id: string }) => {
|
|
990
994
|
try {
|
|
991
995
|
// Fetch instance since event emits dataValues (plain object)
|
|
992
|
-
const creditGrant = await CreditGrant
|
|
996
|
+
const creditGrant = await systemFindByPk(CreditGrant, data.id);
|
|
993
997
|
if (!creditGrant) {
|
|
994
998
|
logger.error('Credit grant not found for scheduling', { creditGrantId: data.id });
|
|
995
999
|
return;
|
|
996
1000
|
}
|
|
1001
|
+
assertJobObjectTenant(creditGrant);
|
|
997
1002
|
await scheduleCreditGrantJobs(creditGrant);
|
|
998
1003
|
logger.info('Credit grant jobs scheduled', {
|
|
999
1004
|
creditGrantId: creditGrant.id,
|
|
@@ -1053,7 +1058,7 @@ events.on('credit-grant.queued', async (id, job, args = {}) => {
|
|
|
1053
1058
|
* Used by both subscription.created and subscription.started events
|
|
1054
1059
|
*/
|
|
1055
1060
|
async function initializeAndScheduleCreditJobs(subscriptionId: string, eventName: string): Promise<void> {
|
|
1056
|
-
const sub = await Subscription
|
|
1061
|
+
const sub = await systemFindByPk(Subscription, subscriptionId);
|
|
1057
1062
|
if (!sub) {
|
|
1058
1063
|
logger.warn('Subscription not found for credit schedule initialization', {
|
|
1059
1064
|
subscriptionId,
|
|
@@ -1061,6 +1066,7 @@ async function initializeAndScheduleCreditJobs(subscriptionId: string, eventName
|
|
|
1061
1066
|
});
|
|
1062
1067
|
return;
|
|
1063
1068
|
}
|
|
1069
|
+
assertJobObjectTenant(sub);
|
|
1064
1070
|
|
|
1065
1071
|
// initializeCreditSchedule only works for active/trialing subscriptions
|
|
1066
1072
|
// and skips if already initialized
|
|
@@ -1147,10 +1153,11 @@ events.on('customer.subscription.trial_start', async (subscription: Subscription
|
|
|
1147
1153
|
*/
|
|
1148
1154
|
events.on('customer.subscription.deleted', async (subscription: Subscription) => {
|
|
1149
1155
|
try {
|
|
1150
|
-
const sub = await Subscription
|
|
1156
|
+
const sub = await systemFindByPk(Subscription, subscription.id);
|
|
1151
1157
|
if (!sub) {
|
|
1152
1158
|
return;
|
|
1153
1159
|
}
|
|
1160
|
+
assertJobObjectTenant(sub);
|
|
1154
1161
|
|
|
1155
1162
|
const jobIds = await stopCreditSchedule(sub);
|
|
1156
1163
|
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import { BN } from '@ocap/util';
|
|
9
9
|
import logger from '../libs/logger';
|
|
10
|
-
import createQueue from '../libs/queue';
|
|
10
|
+
import createQueue, { assertJobObjectTenant } from '../libs/queue';
|
|
11
11
|
import { getAccountState, getCustomerTokenBalance } from '../integrations/arcblock/token';
|
|
12
12
|
import { CreditGrant, CreditTransaction, Customer, PaymentCurrency } from '../store/models';
|
|
13
13
|
import { events } from '../libs/event';
|
|
14
|
+
import { systemFindAll, systemFindByPk } from '../store/scoped';
|
|
14
15
|
|
|
15
16
|
type ReconciliationJob = {
|
|
16
17
|
customerId: string;
|
|
@@ -39,7 +40,7 @@ type BalanceInfo = {
|
|
|
39
40
|
*/
|
|
40
41
|
async function getBalanceInfo(customerId: string, currencyId: string): Promise<BalanceInfo> {
|
|
41
42
|
// Get all granted credit grants that have been minted on-chain
|
|
42
|
-
const grants = await CreditGrant
|
|
43
|
+
const grants = await systemFindAll(CreditGrant, {
|
|
43
44
|
where: {
|
|
44
45
|
customer_id: customerId,
|
|
45
46
|
currency_id: currencyId,
|
|
@@ -53,7 +54,7 @@ async function getBalanceInfo(customerId: string, currencyId: string): Promise<B
|
|
|
53
54
|
.toString();
|
|
54
55
|
|
|
55
56
|
// Get pending transfers (consumed but token not yet transferred on-chain)
|
|
56
|
-
const pendingTransactions = await CreditTransaction
|
|
57
|
+
const pendingTransactions = await systemFindAll(CreditTransaction, {
|
|
57
58
|
where: {
|
|
58
59
|
customer_id: customerId,
|
|
59
60
|
transfer_status: 'pending',
|
|
@@ -98,13 +99,14 @@ async function handleReconciliation(job: ReconciliationJob) {
|
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
try {
|
|
101
|
-
const currency = await PaymentCurrency
|
|
102
|
+
const currency = await systemFindByPk(PaymentCurrency, currencyId);
|
|
102
103
|
if (!currency || !currency.isOnChainCredit()) {
|
|
103
104
|
logger.warn('Currency not found or not on-chain credit', { currencyId });
|
|
104
105
|
return;
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
const customer = await Customer
|
|
108
|
+
const customer = await systemFindByPk(Customer, customerId);
|
|
109
|
+
assertJobObjectTenant(customer);
|
|
108
110
|
if (!customer?.did) {
|
|
109
111
|
logger.warn('Customer not found for reconciliation', { customerId });
|
|
110
112
|
return;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import pAll from 'p-all';
|
|
2
|
-
import createQueue from '../libs/queue';
|
|
2
|
+
import createQueue, { assertJobObjectTenant } from '../libs/queue';
|
|
3
3
|
import { Coupon, PromotionCode } from '../store/models';
|
|
4
4
|
import logger from '../libs/logger';
|
|
5
5
|
import { events } from '../libs/event';
|
|
6
6
|
import { validCoupon, validPromotionCode } from '../libs/discount/coupon';
|
|
7
|
+
import { systemFindAll, systemFindByPk } from '../store/scoped';
|
|
7
8
|
|
|
8
9
|
export type DiscountType = 'coupon' | 'promotion-code';
|
|
9
10
|
|
|
@@ -30,10 +31,11 @@ export async function processDiscountStatus(job: DiscountStatusJobData) {
|
|
|
30
31
|
* Process coupon status check
|
|
31
32
|
*/
|
|
32
33
|
async function processCouponStatus(couponId: string) {
|
|
33
|
-
const coupon = await Coupon
|
|
34
|
+
const coupon = await systemFindByPk(Coupon, couponId);
|
|
34
35
|
if (!coupon) {
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
38
|
+
assertJobObjectTenant(coupon);
|
|
37
39
|
|
|
38
40
|
// Skip if already invalid
|
|
39
41
|
if (!coupon.valid) {
|
|
@@ -54,7 +56,7 @@ async function processCouponStatus(couponId: string) {
|
|
|
54
56
|
max_redemptions: coupon.max_redemptions,
|
|
55
57
|
times_redeemed: coupon.times_redeemed,
|
|
56
58
|
});
|
|
57
|
-
const promotionCodes = await PromotionCode
|
|
59
|
+
const promotionCodes = await systemFindAll(PromotionCode, { where: { coupon_id: couponId, active: true } });
|
|
58
60
|
await pAll(
|
|
59
61
|
promotionCodes.map(
|
|
60
62
|
(promotionCode) => () =>
|
|
@@ -76,11 +78,12 @@ async function processCouponStatus(couponId: string) {
|
|
|
76
78
|
* Process promotion code status check
|
|
77
79
|
*/
|
|
78
80
|
async function processPromotionCodeStatus(promotionCodeId: string) {
|
|
79
|
-
const promotionCode = await PromotionCode
|
|
81
|
+
const promotionCode = await systemFindByPk(PromotionCode, promotionCodeId);
|
|
80
82
|
if (!promotionCode) {
|
|
81
83
|
logger.warn('Promotion code not found for status check', { promotionCodeId });
|
|
82
84
|
return;
|
|
83
85
|
}
|
|
86
|
+
assertJobObjectTenant(promotionCode);
|
|
84
87
|
|
|
85
88
|
// Skip if already inactive
|
|
86
89
|
if (!promotionCode.active) {
|
|
@@ -146,8 +149,8 @@ export const discountStatusQueue = createQueue<DiscountStatusJobData>({
|
|
|
146
149
|
export const startDiscountStatusQueue = async () => {
|
|
147
150
|
logger.info('Starting discount status queue...');
|
|
148
151
|
|
|
149
|
-
const coupons = await Coupon
|
|
150
|
-
const promotionCodes = await PromotionCode
|
|
152
|
+
const coupons = await systemFindAll(Coupon, { where: { valid: true } });
|
|
153
|
+
const promotionCodes = await systemFindAll(PromotionCode, { where: { active: true } });
|
|
151
154
|
await pAll(
|
|
152
155
|
coupons.map(
|
|
153
156
|
(coupon) => () =>
|
package/api/src/queues/event.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Op } from 'sequelize';
|
|
2
|
+
import { isCfWorker } from '../libs/env';
|
|
3
|
+
import { systemFindAll, systemFindByPk } from '../store/scoped';
|
|
2
4
|
|
|
3
5
|
import { events } from '../libs/event';
|
|
4
6
|
import logger from '../libs/logger';
|
|
5
7
|
import createQueue from '../libs/queue';
|
|
8
|
+
import { resolveRowTenant } from '../libs/tenant';
|
|
6
9
|
import { Event } from '../store/models/event';
|
|
7
10
|
import { WebhookAttempt } from '../store/models/webhook-attempt';
|
|
8
11
|
import { WebhookEndpoint } from '../store/models/webhook-endpoint';
|
|
@@ -15,7 +18,7 @@ type EventJob = {
|
|
|
15
18
|
export const handleEvent = async (job: EventJob) => {
|
|
16
19
|
logger.info('Starting to handle event', job);
|
|
17
20
|
|
|
18
|
-
const event = await Event
|
|
21
|
+
const event = await systemFindByPk(Event, job.eventId);
|
|
19
22
|
if (!event) {
|
|
20
23
|
logger.warn('Event not found', job);
|
|
21
24
|
return;
|
|
@@ -26,7 +29,12 @@ export const handleEvent = async (job: EventJob) => {
|
|
|
26
29
|
return;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
// Phase 4 (W1-3): fanout only to endpoints of the event's own tenant.
|
|
33
|
+
// resolveRowTenant fails closed for NULL-tenant events in multi mode.
|
|
34
|
+
const eventTenant = resolveRowTenant(event);
|
|
35
|
+
const webhooks = await systemFindAll(WebhookEndpoint, {
|
|
36
|
+
where: { status: 'enabled', livemode: event.livemode, instance_did: eventTenant },
|
|
37
|
+
});
|
|
30
38
|
const eventWebhooks = webhooks.filter((webhook) => webhook.enabled_events.includes(event.type));
|
|
31
39
|
if (eventWebhooks.length === 0) {
|
|
32
40
|
logger.info('no webhook endpoint for event', job);
|
|
@@ -75,7 +83,7 @@ export const eventQueue = createQueue<EventJob>({
|
|
|
75
83
|
});
|
|
76
84
|
|
|
77
85
|
export const startEventQueue = async () => {
|
|
78
|
-
const docs = await Event
|
|
86
|
+
const docs = await systemFindAll(Event, {
|
|
79
87
|
where: {
|
|
80
88
|
pending_webhooks: { [Op.gt]: 0 },
|
|
81
89
|
},
|
|
@@ -100,7 +108,7 @@ eventQueue.on('failed', ({ id, job, error }) => {
|
|
|
100
108
|
});
|
|
101
109
|
|
|
102
110
|
events.on('event.created', async (event) => {
|
|
103
|
-
if ((
|
|
111
|
+
if (isCfWorker()) {
|
|
104
112
|
// CF Workers: execute inline to save 2 CF Queue ops per event.
|
|
105
113
|
// eventQueue only dispatches webhooks — lightweight DB lookup + webhookQueue.push.
|
|
106
114
|
// Webhook delivery still goes through webhookQueue with full retry guarantees.
|
|
@@ -11,6 +11,9 @@ const REPRESENTATIVE_TOKENS = ['ABT', 'ETH'];
|
|
|
11
11
|
interface HealthCheckJob {
|
|
12
12
|
type: 'health_check';
|
|
13
13
|
timestamp: number;
|
|
14
|
+
/** D6: the tenant this check belongs to — carried in the payload so the
|
|
15
|
+
* re-schedule survives outside any ALS context (multi mode). */
|
|
16
|
+
instance_did?: string;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
interface HealthCheckResult {
|
|
@@ -201,43 +204,59 @@ export const exchangeRateHealthQueue = createQueue<HealthCheckJob>({
|
|
|
201
204
|
// Schedule health checks every 6 hours
|
|
202
205
|
const HEALTH_CHECK_INTERVAL = 6 * 60 * 60; // 6 hours in seconds
|
|
203
206
|
|
|
207
|
+
// D6: every scheduled health-check job carries its tenant in the PAYLOAD so the
|
|
208
|
+
// re-schedule (on 'finished') preserves it WITHOUT relying on the ALS context —
|
|
209
|
+
// the 'finished' listener fires outside any withTenant() scope. In multi mode a
|
|
210
|
+
// tenant-less push would throw TENANT_CONTEXT_MISSING; here `instance_did` is set
|
|
211
|
+
// on the job, so injectJobTenant honors it instead of reading the (absent) ALS.
|
|
212
|
+
let scheduleListenerBound = false;
|
|
213
|
+
|
|
214
|
+
function scheduleNextHealthCheck(instanceDid?: string): void {
|
|
215
|
+
const now = Math.floor(Date.now() / 1000);
|
|
216
|
+
const nextRun = now + HEALTH_CHECK_INTERVAL;
|
|
217
|
+
|
|
218
|
+
exchangeRateHealthQueue.push({
|
|
219
|
+
job: {
|
|
220
|
+
type: 'health_check',
|
|
221
|
+
timestamp: Date.now(),
|
|
222
|
+
// carry the tenant in the payload (multi mode has no ALS context here)
|
|
223
|
+
...(instanceDid ? { instance_did: instanceDid } : {}),
|
|
224
|
+
} as HealthCheckJob,
|
|
225
|
+
runAt: nextRun,
|
|
226
|
+
persist: true,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
logger.info('Scheduled next exchange rate health check', {
|
|
230
|
+
nextRunAt: new Date(nextRun * 1000).toISOString(),
|
|
231
|
+
intervalHours: HEALTH_CHECK_INTERVAL / 3600,
|
|
232
|
+
instanceDid,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
204
236
|
/**
|
|
205
|
-
*
|
|
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',
|