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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { BN, fromTokenToUnit, fromUnitToToken } from '@ocap/util';
|
|
2
|
+
import { systemFindByPk, systemFindOne } from '../store/scoped';
|
|
2
3
|
|
|
3
4
|
import dayjs from '../libs/dayjs';
|
|
4
5
|
import { getLock } from '../libs/lock';
|
|
5
6
|
import logger from '../libs/logger';
|
|
6
|
-
import createQueue from '../libs/queue';
|
|
7
|
+
import createQueue, { assertJobObjectTenant } from '../libs/queue';
|
|
7
8
|
import { getPriceUintAmountByCurrency } from '../libs/price';
|
|
8
9
|
import { Invoice, PaymentCurrency, Price, SubscriptionItem, TLineItemExpanded, UsageRecord } from '../store/models';
|
|
9
10
|
import { Subscription } from '../store/models/subscription';
|
|
@@ -30,11 +31,12 @@ export async function handleUsageRecord(job: UsageRecordJob) {
|
|
|
30
31
|
export const doHandleUsageRecord = async (job: UsageRecordJob) => {
|
|
31
32
|
logger.info('handle usage record', job);
|
|
32
33
|
|
|
33
|
-
const subscription = await Subscription
|
|
34
|
+
const subscription = await systemFindByPk(Subscription, job.subscriptionId);
|
|
34
35
|
if (!subscription) {
|
|
35
36
|
logger.warn('Subscription not found', job);
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
39
|
+
assertJobObjectTenant(subscription);
|
|
38
40
|
if (subscription.isActive() === false) {
|
|
39
41
|
logger.warn('Subscription not active, so usage check is skipped', job);
|
|
40
42
|
return;
|
|
@@ -43,17 +45,18 @@ export const doHandleUsageRecord = async (job: UsageRecordJob) => {
|
|
|
43
45
|
logger.warn('Subscription billing_threshold not set', job);
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
46
|
-
const currency = await PaymentCurrency
|
|
48
|
+
const currency = await systemFindByPk(PaymentCurrency, subscription.currency_id);
|
|
47
49
|
if (!currency) {
|
|
48
50
|
logger.warn('Subscription currency not found', job);
|
|
49
51
|
return;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
const item = await SubscriptionItem
|
|
54
|
+
const item = await systemFindByPk(SubscriptionItem, job.subscriptionItemId);
|
|
53
55
|
if (!item) {
|
|
54
56
|
logger.warn('SubscriptionItem not found', job);
|
|
55
57
|
return;
|
|
56
58
|
}
|
|
59
|
+
assertJobObjectTenant(item);
|
|
57
60
|
// @ts-ignore
|
|
58
61
|
const lines = await Price.expand([{ id: item.id, price_id: item.price_id, quantity: item.quantity }], {
|
|
59
62
|
product: true,
|
|
@@ -69,7 +72,7 @@ export const doHandleUsageRecord = async (job: UsageRecordJob) => {
|
|
|
69
72
|
|
|
70
73
|
const start = subscription.current_period_start as number;
|
|
71
74
|
const end = subscription.current_period_end as number;
|
|
72
|
-
const latestThresholdInvoice = await Invoice
|
|
75
|
+
const latestThresholdInvoice = await systemFindOne(Invoice, {
|
|
73
76
|
where: {
|
|
74
77
|
subscription_id: subscription.id,
|
|
75
78
|
billing_reason: 'subscription_threshold',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { events } from '../../libs/event';
|
|
2
2
|
import logger from '../../libs/logger';
|
|
3
|
-
import createQueue from '../../libs/queue';
|
|
3
|
+
import createQueue, { assertJobObjectTenant } from '../../libs/queue';
|
|
4
4
|
import { Invoice } from '../../store/models';
|
|
5
5
|
import { CheckoutSession } from '../../store/models/checkout-session';
|
|
6
6
|
import { PaymentIntent } from '../../store/models/payment-intent';
|
|
@@ -8,6 +8,7 @@ import { Price } from '../../store/models/price';
|
|
|
8
8
|
import { Product } from '../../store/models/product';
|
|
9
9
|
import { depositVaultQueue } from '../payment';
|
|
10
10
|
import { startVendorFulfillment, triggerCommissionProcess, triggerCoordinatorCheck } from './fulfillment-coordinator';
|
|
11
|
+
import { systemFindAll, systemFindByPk } from '../../store/scoped';
|
|
11
12
|
|
|
12
13
|
type VendorCommissionJob = {
|
|
13
14
|
invoiceId: string;
|
|
@@ -27,7 +28,7 @@ async function checkIfPaymentIntentHasVendors(checkoutSession: CheckoutSession):
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
// Find corresponding product_ids through price_ids
|
|
30
|
-
const prices = await Price
|
|
31
|
+
const prices = await systemFindAll(Price, {
|
|
31
32
|
where: { id: priceIds },
|
|
32
33
|
attributes: ['id', 'product_id'],
|
|
33
34
|
});
|
|
@@ -43,7 +44,7 @@ async function checkIfPaymentIntentHasVendors(checkoutSession: CheckoutSession):
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
// Get product information
|
|
46
|
-
const products = await Product
|
|
47
|
+
const products = await systemFindAll(Product, {
|
|
47
48
|
where: { id: productIds },
|
|
48
49
|
attributes: ['id', 'vendor_config'],
|
|
49
50
|
});
|
|
@@ -84,11 +85,12 @@ export const handleVendorCommission = async (job: VendorCommissionJob) => {
|
|
|
84
85
|
|
|
85
86
|
let checkoutSession: CheckoutSession | null = null;
|
|
86
87
|
try {
|
|
87
|
-
const invoice = await Invoice
|
|
88
|
+
const invoice = await systemFindByPk(Invoice, job.invoiceId);
|
|
88
89
|
if (!invoice) {
|
|
89
90
|
logger.warn('invoice not found', { id: job.invoiceId });
|
|
90
91
|
return;
|
|
91
92
|
}
|
|
93
|
+
assertJobObjectTenant(invoice);
|
|
92
94
|
|
|
93
95
|
// Find CheckoutSession through PaymentIntent
|
|
94
96
|
checkoutSession = await CheckoutSession.findByInvoiceId(invoice.id);
|
|
@@ -148,7 +150,7 @@ export const vendorCommissionQueue = createQueue<VendorCommissionJob>({
|
|
|
148
150
|
});
|
|
149
151
|
|
|
150
152
|
export const startVendorCommissionQueue = async () => {
|
|
151
|
-
const payments = await PaymentIntent
|
|
153
|
+
const payments = await systemFindAll(PaymentIntent, {
|
|
152
154
|
where: {
|
|
153
155
|
status: ['requires_capture', 'processing'],
|
|
154
156
|
capture_method: 'automatic',
|
|
@@ -3,7 +3,7 @@ import { Op } from 'sequelize';
|
|
|
3
3
|
import { events } from '../../libs/event';
|
|
4
4
|
import { getLock } from '../../libs/lock';
|
|
5
5
|
import logger from '../../libs/logger';
|
|
6
|
-
import createQueue from '../../libs/queue';
|
|
6
|
+
import createQueue, { assertJobObjectTenant } from '../../libs/queue';
|
|
7
7
|
import dayjs from '../../libs/dayjs';
|
|
8
8
|
import { VendorFulfillmentService } from '../../libs/vendor-util/fulfillment';
|
|
9
9
|
import { CheckoutSession } from '../../store/models/checkout-session';
|
|
@@ -17,6 +17,7 @@ import { depositVaultQueue } from '../payment';
|
|
|
17
17
|
import { addSubscriptionJob } from '../subscription';
|
|
18
18
|
import { SubscriptionWillCanceledSchedule } from '../../crons/subscription-will-canceled';
|
|
19
19
|
import { Invoice } from '../../store/models';
|
|
20
|
+
import { systemFindAll, systemFindByPk, systemFindOne } from '../../store/scoped';
|
|
20
21
|
|
|
21
22
|
export type VendorInfo = NonNullable<CheckoutSession['vendor_info']>[number];
|
|
22
23
|
|
|
@@ -30,7 +31,7 @@ const MAX_FULFILLMENT_TIMEOUT = 300000;
|
|
|
30
31
|
|
|
31
32
|
// Helper function to get order info for coordinated fulfillment
|
|
32
33
|
async function getCoordinatedOrderInfo(checkoutSessionId: string) {
|
|
33
|
-
const checkoutSession = await CheckoutSession
|
|
34
|
+
const checkoutSession = await systemFindByPk(CheckoutSession, checkoutSessionId);
|
|
34
35
|
|
|
35
36
|
if (!checkoutSession) {
|
|
36
37
|
throw new Error('CheckoutSession or Invoice not found');
|
|
@@ -370,7 +371,7 @@ export async function handleFulfillmentCoordination(job: CoordinatorJob) {
|
|
|
370
371
|
}
|
|
371
372
|
|
|
372
373
|
async function getVendorConfigurations(checkoutSessionId: string): Promise<any[]> {
|
|
373
|
-
const checkoutSession = await CheckoutSession
|
|
374
|
+
const checkoutSession = await systemFindByPk(CheckoutSession, checkoutSessionId);
|
|
374
375
|
if (!checkoutSession?.line_items) {
|
|
375
376
|
return [];
|
|
376
377
|
}
|
|
@@ -380,7 +381,7 @@ async function getVendorConfigurations(checkoutSessionId: string): Promise<any[]
|
|
|
380
381
|
return [];
|
|
381
382
|
}
|
|
382
383
|
|
|
383
|
-
const prices = await Price
|
|
384
|
+
const prices = await systemFindAll(Price, {
|
|
384
385
|
where: { id: priceIds },
|
|
385
386
|
attributes: ['id', 'product_id'],
|
|
386
387
|
});
|
|
@@ -390,12 +391,12 @@ async function getVendorConfigurations(checkoutSessionId: string): Promise<any[]
|
|
|
390
391
|
return [];
|
|
391
392
|
}
|
|
392
393
|
|
|
393
|
-
const product = await Product
|
|
394
|
+
const product = await systemFindByPk(Product, productIds[0]);
|
|
394
395
|
return product?.vendor_config || [];
|
|
395
396
|
}
|
|
396
397
|
|
|
397
398
|
async function getVendorInfo(checkoutSessionId: string): Promise<VendorInfo[]> {
|
|
398
|
-
const checkoutSession = await CheckoutSession
|
|
399
|
+
const checkoutSession = await systemFindByPk(CheckoutSession, checkoutSessionId);
|
|
399
400
|
return (checkoutSession?.vendor_info as VendorInfo[]) || [];
|
|
400
401
|
}
|
|
401
402
|
|
|
@@ -421,7 +422,7 @@ async function updateSingleVendorInfo(
|
|
|
421
422
|
|
|
422
423
|
try {
|
|
423
424
|
await sequelize.transaction(async (transaction: any) => {
|
|
424
|
-
const checkoutSession = await CheckoutSession
|
|
425
|
+
const checkoutSession = await systemFindByPk(CheckoutSession, checkoutSessionId, {
|
|
425
426
|
transaction,
|
|
426
427
|
lock: transaction.LOCK.UPDATE,
|
|
427
428
|
});
|
|
@@ -566,22 +567,24 @@ export function triggerCoordinatorCheck(checkoutSessionId: string, invoiceId: st
|
|
|
566
567
|
export async function triggerCommissionProcess(checkoutSessionId: string, invoiceId: string): Promise<void> {
|
|
567
568
|
logger.info('Triggering commission process', { checkoutSessionId, invoiceId });
|
|
568
569
|
|
|
569
|
-
const checkoutSession = await CheckoutSession
|
|
570
|
+
const checkoutSession = await systemFindByPk(CheckoutSession, checkoutSessionId);
|
|
570
571
|
if (!checkoutSession) {
|
|
571
572
|
logger.error('Checkout session not found[triggerCommissionProcess]', { checkoutSessionId });
|
|
572
573
|
return;
|
|
573
574
|
}
|
|
575
|
+
assertJobObjectTenant(checkoutSession);
|
|
574
576
|
|
|
575
577
|
if (!invoiceId) {
|
|
576
578
|
logger.warn('Invoice ID not found[triggerCommissionProcess]', { checkoutSessionId, invoiceId });
|
|
577
579
|
}
|
|
578
580
|
|
|
579
|
-
const invoice = await Invoice
|
|
581
|
+
const invoice = await systemFindByPk(Invoice, invoiceId || checkoutSession.invoice_id);
|
|
580
582
|
if (!invoice) {
|
|
581
583
|
logger.error('Invoice not found[triggerCommissionProcess]', { invoiceId });
|
|
582
584
|
return;
|
|
583
585
|
}
|
|
584
|
-
|
|
586
|
+
assertJobObjectTenant(invoice);
|
|
587
|
+
const paymentIntent = await systemFindOne(PaymentIntent, {
|
|
585
588
|
where: {
|
|
586
589
|
[Op.or]: [{ id: invoice.payment_intent_id || checkoutSession.payment_intent_id }, { invoice_id: invoiceId }],
|
|
587
590
|
},
|
|
@@ -621,7 +624,7 @@ export async function initiateFullRefund(invoiceId: string, reason: string): Pro
|
|
|
621
624
|
});
|
|
622
625
|
|
|
623
626
|
try {
|
|
624
|
-
const paymentIntent = await PaymentIntent
|
|
627
|
+
const paymentIntent = await systemFindOne(PaymentIntent, { where: { invoice_id: invoiceId } });
|
|
625
628
|
const checkoutSession = await CheckoutSession.findByInvoiceId(invoiceId);
|
|
626
629
|
|
|
627
630
|
if (!checkoutSession || !paymentIntent) {
|
|
@@ -644,7 +647,7 @@ export async function initiateFullRefund(invoiceId: string, reason: string): Pro
|
|
|
644
647
|
}
|
|
645
648
|
|
|
646
649
|
// Calculate remaining amount using the same logic as subscription createProration
|
|
647
|
-
const refunds = await Refund
|
|
650
|
+
const refunds = await systemFindAll(Refund, {
|
|
648
651
|
where: {
|
|
649
652
|
status: { [Op.not]: 'canceled' },
|
|
650
653
|
payment_intent_id: paymentIntent.id,
|
|
@@ -721,11 +724,12 @@ export async function initiateFullRefund(invoiceId: string, reason: string): Pro
|
|
|
721
724
|
*/
|
|
722
725
|
async function cancelSubscriptionForRefund(subscriptionId: string, reason: string): Promise<void> {
|
|
723
726
|
try {
|
|
724
|
-
const subscription = await Subscription
|
|
727
|
+
const subscription = await systemFindByPk(Subscription, subscriptionId);
|
|
725
728
|
if (!subscription) {
|
|
726
729
|
logger.warn('Subscription not found for cancellation', { subscriptionId });
|
|
727
730
|
return;
|
|
728
731
|
}
|
|
732
|
+
assertJobObjectTenant(subscription);
|
|
729
733
|
|
|
730
734
|
// Check if subscription is already canceled
|
|
731
735
|
if (subscription.status === 'canceled') {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { events } from '../../libs/event';
|
|
2
2
|
import logger from '../../libs/logger';
|
|
3
|
-
import createQueue from '../../libs/queue';
|
|
3
|
+
import createQueue, { assertJobObjectTenant } from '../../libs/queue';
|
|
4
4
|
import { VendorFulfillmentService } from '../../libs/vendor-util/fulfillment';
|
|
5
5
|
import { CheckoutSession } from '../../store/models/checkout-session';
|
|
6
6
|
import { updateVendorFulfillmentStatus } from './fulfillment-coordinator';
|
|
7
|
+
import { systemFindByPk } from '../../store/scoped';
|
|
7
8
|
|
|
8
9
|
type VendorFulfillmentJob = {
|
|
9
10
|
checkoutSessionId: string;
|
|
@@ -22,10 +23,11 @@ export const handleVendorFulfillment = async (job: VendorFulfillmentJob) => {
|
|
|
22
23
|
const { checkoutSessionId, invoiceId, vendorId, vendorConfig } = job;
|
|
23
24
|
|
|
24
25
|
try {
|
|
25
|
-
const checkoutSession = await CheckoutSession
|
|
26
|
+
const checkoutSession = await systemFindByPk(CheckoutSession, checkoutSessionId);
|
|
26
27
|
if (!checkoutSession) {
|
|
27
28
|
throw new Error(`CheckoutSession not found: ${checkoutSessionId}`);
|
|
28
29
|
}
|
|
30
|
+
assertJobObjectTenant(checkoutSession);
|
|
29
31
|
|
|
30
32
|
const orderInfo = {
|
|
31
33
|
checkoutSessionId,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import logger from '../../libs/logger';
|
|
2
|
-
import createQueue from '../../libs/queue';
|
|
2
|
+
import createQueue, { assertJobObjectTenant } from '../../libs/queue';
|
|
3
3
|
import { VendorFulfillmentService } from '../../libs/vendor-util/fulfillment';
|
|
4
4
|
import { buildRefundInfoFromPayout } from '../../libs/vendor-util/tool';
|
|
5
5
|
import { CheckoutSession, Payout } from '../../store/models';
|
|
6
6
|
import { payoutQueue } from '../payout';
|
|
7
7
|
import { VendorInfo } from './fulfillment-coordinator';
|
|
8
|
+
import { systemFindByPk, systemFindOne } from '../../store/scoped';
|
|
8
9
|
|
|
9
10
|
export const MAX_RETURN_RETRY = 3;
|
|
10
11
|
|
|
@@ -30,7 +31,8 @@ async function handleReturnProcessorJob(job: ReturnProcessorJob): Promise<void>
|
|
|
30
31
|
});
|
|
31
32
|
|
|
32
33
|
try {
|
|
33
|
-
const checkoutSession = await CheckoutSession
|
|
34
|
+
const checkoutSession = await systemFindByPk(CheckoutSession, checkoutSessionId);
|
|
35
|
+
assertJobObjectTenant(checkoutSession);
|
|
34
36
|
|
|
35
37
|
if (!checkoutSession) {
|
|
36
38
|
logger.warn('CheckoutSession not found', { checkoutSessionId });
|
|
@@ -156,7 +158,7 @@ async function callVendorReturn(vendor: VendorInfo, checkoutSession: CheckoutSes
|
|
|
156
158
|
throw new Error(`No adapter found for vendor: ${vendor.vendor_id}`);
|
|
157
159
|
}
|
|
158
160
|
|
|
159
|
-
const payoutInfo = await Payout
|
|
161
|
+
const payoutInfo = await systemFindOne(Payout, {
|
|
160
162
|
where: { vendor_info: { order_id: vendor.order_id, vendor_id: vendor.vendor_id } },
|
|
161
163
|
});
|
|
162
164
|
let refundInfo;
|
|
@@ -6,6 +6,7 @@ import { CheckoutSession, Subscription } from '../../store/models';
|
|
|
6
6
|
import { vendorReturnProcessorQueue } from './return-processor';
|
|
7
7
|
import { VendorInfo } from './fulfillment-coordinator';
|
|
8
8
|
import { events } from '../../libs/event';
|
|
9
|
+
import { systemFindAll, systemFindOne } from '../../store/scoped';
|
|
9
10
|
|
|
10
11
|
export const vendorReturnScannerQueue = createQueue({
|
|
11
12
|
name: 'vendor-return-scanner',
|
|
@@ -56,7 +57,7 @@ async function findSessionsNeedingVendorReturn(): Promise<CheckoutSession[]> {
|
|
|
56
57
|
// First, find canceled subscriptions
|
|
57
58
|
const oneWeekAgo = dayjs().subtract(7, 'day').unix();
|
|
58
59
|
|
|
59
|
-
const canceledSubscriptions = await Subscription
|
|
60
|
+
const canceledSubscriptions = await systemFindAll(Subscription, {
|
|
60
61
|
where: { status: 'canceled', canceled_at: { [Op.gt]: oneWeekAgo } },
|
|
61
62
|
attributes: ['id'],
|
|
62
63
|
});
|
|
@@ -64,7 +65,7 @@ async function findSessionsNeedingVendorReturn(): Promise<CheckoutSession[]> {
|
|
|
64
65
|
const canceledSubscriptionIds = canceledSubscriptions.map((sub) => sub.id);
|
|
65
66
|
|
|
66
67
|
// Find checkout sessions with completed fulfillment and canceled subscriptions
|
|
67
|
-
const readyToReturnSessions = await CheckoutSession
|
|
68
|
+
const readyToReturnSessions = await systemFindAll(CheckoutSession, {
|
|
68
69
|
where: {
|
|
69
70
|
fulfillment_status: { [Op.notIn]: ['returning', 'returned', 'failed'] },
|
|
70
71
|
subscription_id: { [Op.in]: canceledSubscriptionIds },
|
|
@@ -75,7 +76,7 @@ async function findSessionsNeedingVendorReturn(): Promise<CheckoutSession[]> {
|
|
|
75
76
|
});
|
|
76
77
|
|
|
77
78
|
// Find checkout sessions already in returning status
|
|
78
|
-
const returningSessions = await CheckoutSession
|
|
79
|
+
const returningSessions = await systemFindAll(CheckoutSession, {
|
|
79
80
|
where: {
|
|
80
81
|
fulfillment_status: 'returning',
|
|
81
82
|
subscription_id: { [Op.ne]: null as any },
|
|
@@ -135,7 +136,7 @@ events.on('customer.subscription.deleted', async (subscription: Subscription) =>
|
|
|
135
136
|
return;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
const session = await CheckoutSession
|
|
139
|
+
const session = await systemFindOne(CheckoutSession, {
|
|
139
140
|
where: { subscription_id: subscription.id },
|
|
140
141
|
attributes: ['id', 'vendor_info'],
|
|
141
142
|
});
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { joinURL } from 'ufo';
|
|
2
2
|
import { Auth as VendorAuth } from '@blocklet/payment-vendor';
|
|
3
|
-
import createQueue from '../../libs/queue';
|
|
3
|
+
import createQueue, { assertJobObjectTenant } from '../../libs/queue';
|
|
4
4
|
import { CheckoutSession } from '../../store/models/checkout-session';
|
|
5
5
|
import { ProductVendor } from '../../store/models';
|
|
6
6
|
import { fulfillmentCoordinatorQueue } from './fulfillment-coordinator';
|
|
7
7
|
import logger from '../../libs/logger';
|
|
8
8
|
import { vendorTimeoutMinutes } from '../../libs/env';
|
|
9
9
|
import { VendorFulfillmentService } from '../../libs/vendor-util/fulfillment';
|
|
10
|
+
import { systemFindAll, systemFindByPk } from '../../store/scoped';
|
|
10
11
|
|
|
11
12
|
export const startVendorStatusCheckSchedule = async () => {
|
|
12
|
-
const checkoutSessions = await CheckoutSession
|
|
13
|
+
const checkoutSessions = await systemFindAll(CheckoutSession, {
|
|
13
14
|
where: { fulfillment_status: 'sent' },
|
|
14
15
|
});
|
|
15
16
|
|
|
@@ -41,7 +42,8 @@ export const handleVendorStatusCheck = async (job: VendorStatusCheckJob) => {
|
|
|
41
42
|
const { checkoutSessionId, vendorId } = job;
|
|
42
43
|
logger.info('handleVendorStatusCheck', { checkoutSessionId, vendorId });
|
|
43
44
|
|
|
44
|
-
const checkoutSession = await CheckoutSession
|
|
45
|
+
const checkoutSession = await systemFindByPk(CheckoutSession, checkoutSessionId);
|
|
46
|
+
assertJobObjectTenant(checkoutSession);
|
|
45
47
|
|
|
46
48
|
const vendor = checkoutSession?.vendor_info?.find((v) => v.vendor_id === vendorId);
|
|
47
49
|
if (!vendor) {
|
|
@@ -49,7 +51,7 @@ export const handleVendorStatusCheck = async (job: VendorStatusCheckJob) => {
|
|
|
49
51
|
return { status: 'fail' };
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
const TIMEOUT_THRESHOLD = vendorTimeoutMinutes * 60 * 1000;
|
|
54
|
+
const TIMEOUT_THRESHOLD = vendorTimeoutMinutes() * 60 * 1000;
|
|
53
55
|
const now = Date.now();
|
|
54
56
|
|
|
55
57
|
if (vendor.lastAttemptAt) {
|
|
@@ -61,11 +63,11 @@ export const handleVendorStatusCheck = async (job: VendorStatusCheckJob) => {
|
|
|
61
63
|
vendorId,
|
|
62
64
|
status: vendor.status,
|
|
63
65
|
timeSinceLastAttempt: `${Math.floor(timeSinceLastAttempt / 1000)}s`,
|
|
64
|
-
timeoutThreshold: `${vendorTimeoutMinutes}min (${Math.floor(TIMEOUT_THRESHOLD / 1000)}s)`,
|
|
66
|
+
timeoutThreshold: `${vendorTimeoutMinutes()}min (${Math.floor(TIMEOUT_THRESHOLD / 1000)}s)`,
|
|
65
67
|
});
|
|
66
68
|
|
|
67
69
|
vendor.status = 'failed';
|
|
68
|
-
vendor.error_message = `Timeout: ${vendor.status} status exceeded ${vendorTimeoutMinutes} minutes`;
|
|
70
|
+
vendor.error_message = `Timeout: ${vendor.status} status exceeded ${vendorTimeoutMinutes()} minutes`;
|
|
69
71
|
vendor.lastAttemptAt = new Date().toISOString();
|
|
70
72
|
|
|
71
73
|
const updatedVendorInfo = checkoutSession?.vendor_info?.map((v) => {
|
|
@@ -92,7 +94,8 @@ export const handleVendorStatusCheck = async (job: VendorStatusCheckJob) => {
|
|
|
92
94
|
|
|
93
95
|
try {
|
|
94
96
|
if (!vendor.app_url) {
|
|
95
|
-
const productVendor = await ProductVendor
|
|
97
|
+
const productVendor = await systemFindByPk(ProductVendor, vendorId);
|
|
98
|
+
if (productVendor) assertJobObjectTenant(productVendor);
|
|
96
99
|
const url = productVendor?.app_url;
|
|
97
100
|
logger.info('found vendor url', { url, productVendor });
|
|
98
101
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import componentApi from '@blocklet/sdk/lib/util/component-api';
|
|
2
2
|
import { AxiosError } from 'axios';
|
|
3
3
|
import { Op } from 'sequelize';
|
|
4
|
+
import { systemFindAll, systemFindByPk } from '../store/scoped';
|
|
4
5
|
|
|
5
6
|
import { wallet } from '../libs/auth';
|
|
7
|
+
import { context } from '../libs/context';
|
|
6
8
|
import logger from '../libs/logger';
|
|
9
|
+
import { TENANT_MISMATCH, resolveRowTenant } from '../libs/tenant';
|
|
7
10
|
import createQueue from '../libs/queue';
|
|
8
11
|
import { MAX_RETRY_COUNT, getNextRetry, getWebhookJobId } from '../libs/util';
|
|
9
12
|
import { webhookAlertWindowMinutes, webhookAlertMinFailures } from '../libs/env';
|
|
@@ -46,13 +49,13 @@ type WebhookJob = {
|
|
|
46
49
|
export const handleWebhook = async (job: WebhookJob) => {
|
|
47
50
|
logger.info('handle webhook', job);
|
|
48
51
|
|
|
49
|
-
const event = await Event
|
|
52
|
+
const event = await systemFindByPk(Event, job.eventId);
|
|
50
53
|
if (!event) {
|
|
51
54
|
logger.warn('event not found when attempt webhook', job);
|
|
52
55
|
return;
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
const webhook = await WebhookEndpoint
|
|
58
|
+
const webhook = await systemFindByPk(WebhookEndpoint, job.webhookId);
|
|
56
59
|
if (!webhook) {
|
|
57
60
|
logger.warn('webhook not found on attempt', job);
|
|
58
61
|
return;
|
|
@@ -62,9 +65,28 @@ export const handleWebhook = async (job: WebhookJob) => {
|
|
|
62
65
|
return;
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
// Phase 4 (W1-3) handler invariant: a forged/mixed job payload must never
|
|
69
|
+
// deliver one tenant's event to another tenant's endpoint. No attempt row
|
|
70
|
+
// is written for rejected pairs (alert only).
|
|
71
|
+
const eventTenant = resolveRowTenant(event);
|
|
72
|
+
if (resolveRowTenant(webhook) !== eventTenant) {
|
|
73
|
+
logger.error('[webhook] tenant mismatch between event and endpoint, delivery refused', {
|
|
74
|
+
code: TENANT_MISMATCH,
|
|
75
|
+
eventId: event.id,
|
|
76
|
+
webhookId: webhook.id,
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// The webhook job is tenant-agnostic at dispatch (it only carries ids); the
|
|
82
|
+
// handler derives the authoritative tenant from the loaded event. Run the
|
|
83
|
+
// tenant-scoped reads/writes under that tenant so TenantModel scopes/stamps
|
|
84
|
+
// consistently (the cross-tenant loads above used system* on purpose).
|
|
85
|
+
const lastRetryCount = await context.withTenant(eventTenant, () =>
|
|
86
|
+
WebhookAttempt.max('retry_count', {
|
|
87
|
+
where: { event_id: event.id, webhook_endpoint_id: webhook.id },
|
|
88
|
+
})
|
|
89
|
+
);
|
|
68
90
|
|
|
69
91
|
const retryCount = lastRetryCount ? +lastRetryCount + 1 : 1;
|
|
70
92
|
|
|
@@ -74,10 +96,10 @@ export const handleWebhook = async (job: WebhookJob) => {
|
|
|
74
96
|
// expand basic fields
|
|
75
97
|
const { object } = json.data;
|
|
76
98
|
if (object.customer_id && !object.customer) {
|
|
77
|
-
object.customer = await Customer
|
|
99
|
+
object.customer = await systemFindByPk(Customer, object.customer_id);
|
|
78
100
|
}
|
|
79
101
|
if (object.currency_id && !object.currency) {
|
|
80
|
-
object.currency = await PaymentCurrency
|
|
102
|
+
object.currency = await systemFindByPk(PaymentCurrency, object.currency_id);
|
|
81
103
|
}
|
|
82
104
|
|
|
83
105
|
// verify similar to component call, but supports external urls
|
|
@@ -92,33 +114,39 @@ export const handleWebhook = async (job: WebhookJob) => {
|
|
|
92
114
|
},
|
|
93
115
|
});
|
|
94
116
|
|
|
95
|
-
await
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
await context.withTenant(eventTenant, () =>
|
|
118
|
+
WebhookAttempt.create({
|
|
119
|
+
livemode: event.livemode,
|
|
120
|
+
instance_did: eventTenant,
|
|
121
|
+
event_id: event.id,
|
|
122
|
+
webhook_endpoint_id: webhook.id,
|
|
123
|
+
status: 'succeeded',
|
|
124
|
+
response_status: result.status,
|
|
125
|
+
response_body: result.data || {},
|
|
126
|
+
retry_count: retryCount,
|
|
127
|
+
})
|
|
128
|
+
);
|
|
104
129
|
logger.info('WebhookAttempt created successfully', { eventId: event.id, webhookId: webhook.id });
|
|
105
130
|
|
|
106
|
-
await decrementPendingWebhooks(event, webhook.id);
|
|
131
|
+
await context.withTenant(eventTenant, () => decrementPendingWebhooks(event, webhook.id));
|
|
107
132
|
|
|
108
133
|
logger.info('webhook attempt success', { ...job, retryCount });
|
|
109
134
|
} catch (err: any) {
|
|
110
135
|
logger.warn('webhook attempt error', { ...job, retryCount, message: err.message });
|
|
111
136
|
const errorStatus = (err as AxiosError).response?.status || 500;
|
|
112
137
|
|
|
113
|
-
await
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
138
|
+
await context.withTenant(eventTenant, () =>
|
|
139
|
+
WebhookAttempt.create({
|
|
140
|
+
livemode: event.livemode,
|
|
141
|
+
instance_did: eventTenant,
|
|
142
|
+
event_id: event.id,
|
|
143
|
+
webhook_endpoint_id: webhook.id,
|
|
144
|
+
status: 'failed',
|
|
145
|
+
response_status: errorStatus,
|
|
146
|
+
response_body: (err as AxiosError).response?.data || {},
|
|
147
|
+
retry_count: retryCount,
|
|
148
|
+
})
|
|
149
|
+
);
|
|
122
150
|
logger.info('Failed WebhookAttempt created', { eventId: event.id, webhookId: webhook.id });
|
|
123
151
|
|
|
124
152
|
try {
|
|
@@ -143,16 +171,16 @@ export const handleWebhook = async (job: WebhookJob) => {
|
|
|
143
171
|
});
|
|
144
172
|
});
|
|
145
173
|
} else {
|
|
146
|
-
await decrementPendingWebhooks(event, webhook.id);
|
|
174
|
+
await context.withTenant(eventTenant, () => decrementPendingWebhooks(event, webhook.id));
|
|
147
175
|
}
|
|
148
176
|
}
|
|
149
177
|
};
|
|
150
178
|
|
|
151
179
|
// Alert if webhook continuously fails within configured time window
|
|
152
180
|
async function checkAndNotifyWebhookFailures(webhookId: string) {
|
|
153
|
-
const alertWindowStart = new Date(Date.now() - webhookAlertWindowMinutes * 60 * 1000);
|
|
181
|
+
const alertWindowStart = new Date(Date.now() - webhookAlertWindowMinutes() * 60 * 1000);
|
|
154
182
|
|
|
155
|
-
const recentAttempts = await WebhookAttempt
|
|
183
|
+
const recentAttempts = await systemFindAll(WebhookAttempt, {
|
|
156
184
|
where: {
|
|
157
185
|
webhook_endpoint_id: webhookId,
|
|
158
186
|
created_at: { [Op.gte]: alertWindowStart },
|
|
@@ -167,7 +195,7 @@ async function checkAndNotifyWebhookFailures(webhookId: string) {
|
|
|
167
195
|
}
|
|
168
196
|
|
|
169
197
|
const failedCountInWindow = recentAttempts.filter((attempt) => attempt.status === 'failed').length;
|
|
170
|
-
if (failedCountInWindow < webhookAlertMinFailures) {
|
|
198
|
+
if (failedCountInWindow < webhookAlertMinFailures()) {
|
|
171
199
|
return;
|
|
172
200
|
}
|
|
173
201
|
|
|
@@ -185,8 +213,8 @@ async function checkAndNotifyWebhookFailures(webhookId: string) {
|
|
|
185
213
|
logger.info('Notification job added for webhook consecutive failures', {
|
|
186
214
|
webhookId,
|
|
187
215
|
failedCountInWindow,
|
|
188
|
-
alertWindowMinutes: webhookAlertWindowMinutes,
|
|
189
|
-
minFailures: webhookAlertMinFailures,
|
|
216
|
+
alertWindowMinutes: webhookAlertWindowMinutes(),
|
|
217
|
+
minFailures: webhookAlertMinFailures(),
|
|
190
218
|
});
|
|
191
219
|
}
|
|
192
220
|
|
|
@@ -5,7 +5,6 @@ import { toDelegateAddress } from '@arcblock/did-util/cbor';
|
|
|
5
5
|
import type { DelegateState as DelegateStateType, Transaction } from '@ocap/client';
|
|
6
6
|
import { BN, fromTokenToUnit, toBase58 } from '@ocap/util';
|
|
7
7
|
import { fromPublicKey } from '@ocap/wallet';
|
|
8
|
-
import type { Request } from 'express';
|
|
9
8
|
import isEmpty from 'lodash/isEmpty';
|
|
10
9
|
import dayjs from '../../libs/dayjs';
|
|
11
10
|
import { estimateMaxGasForTx, hasStakedForGas } from '../../integrations/arcblock/stake';
|
|
@@ -1583,7 +1582,7 @@ export async function executeOcapTransactions(
|
|
|
1583
1582
|
userPk: string,
|
|
1584
1583
|
claims: any[],
|
|
1585
1584
|
paymentMethod: PaymentMethod,
|
|
1586
|
-
requestSource:
|
|
1585
|
+
requestSource: any | any[],
|
|
1587
1586
|
subscriptionId?: string,
|
|
1588
1587
|
paymentCurrencyContract?: string,
|
|
1589
1588
|
nonce?: string
|
|
@@ -29,7 +29,7 @@ const updateInvoices = async (invoices: Invoice[], update: Partial<Invoice>) =>
|
|
|
29
29
|
invoices.map((invoice) => async () => {
|
|
30
30
|
await invoice.update(update);
|
|
31
31
|
}),
|
|
32
|
-
{ concurrency: updateDataConcurrency }
|
|
32
|
+
{ concurrency: updateDataConcurrency() }
|
|
33
33
|
);
|
|
34
34
|
};
|
|
35
35
|
|
|
@@ -38,7 +38,7 @@ const updateSubscriptions = async (subscriptions: Subscription[], update: Partia
|
|
|
38
38
|
subscriptions.map((subscription) => async () => {
|
|
39
39
|
await subscription.update(update);
|
|
40
40
|
}),
|
|
41
|
-
{ concurrency: updateDataConcurrency }
|
|
41
|
+
{ concurrency: updateDataConcurrency() }
|
|
42
42
|
);
|
|
43
43
|
};
|
|
44
44
|
|
|
@@ -254,7 +254,7 @@ export default {
|
|
|
254
254
|
subscriptions.map((subscription) => async () => {
|
|
255
255
|
await addSubscriptionJob(subscription, 'cycle', false, subscription.trial_end);
|
|
256
256
|
}),
|
|
257
|
-
{ concurrency: updateDataConcurrency }
|
|
257
|
+
{ concurrency: updateDataConcurrency() }
|
|
258
258
|
);
|
|
259
259
|
|
|
260
260
|
logger.info('CheckoutSession updated with multiple subscriptions', {
|