@open-mercato/core 0.4.7-develop-0a657b411f → 0.4.7-develop-e249d3e7d0
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/dist/generated/entities/carrier_shipment/index.js +37 -0
- package/dist/generated/entities/carrier_shipment/index.js.map +7 -0
- package/dist/generated/entities/gateway_transaction/index.js +47 -0
- package/dist/generated/entities/gateway_transaction/index.js.map +7 -0
- package/dist/generated/entities/webhook_processed_event/index.js +17 -0
- package/dist/generated/entities/webhook_processed_event/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +10 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +6 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +14 -5
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.meta.js +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.meta.js.map +1 -1
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +37 -12
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
- package/dist/modules/directory/api/get/tenants/lookup.js +1 -0
- package/dist/modules/directory/api/get/tenants/lookup.js.map +2 -2
- package/dist/modules/integrations/api/[id]/route.js +38 -11
- package/dist/modules/integrations/api/[id]/route.js.map +2 -2
- package/dist/modules/integrations/api/logs/route.js +52 -26
- package/dist/modules/integrations/api/logs/route.js.map +2 -2
- package/dist/modules/integrations/api/route.js +37 -7
- package/dist/modules/integrations/api/route.js.map +2 -2
- package/dist/modules/integrations/api/umes-read.js +121 -0
- package/dist/modules/integrations/api/umes-read.js.map +7 -0
- package/dist/modules/integrations/backend/integrations/[id]/page.js +715 -183
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +30 -9
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/detail-page-widgets.js +46 -0
- package/dist/modules/integrations/backend/integrations/detail-page-widgets.js.map +7 -0
- package/dist/modules/integrations/backend/integrations/page.js +78 -62
- package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/page.meta.js +2 -2
- package/dist/modules/integrations/backend/integrations/page.meta.js.map +1 -1
- package/dist/modules/integrations/setup.js +2 -2
- package/dist/modules/integrations/setup.js.map +2 -2
- package/dist/modules/payment_gateways/acl.js +12 -0
- package/dist/modules/payment_gateways/acl.js.map +7 -0
- package/dist/modules/payment_gateways/api/cancel/route.js +55 -0
- package/dist/modules/payment_gateways/api/cancel/route.js.map +7 -0
- package/dist/modules/payment_gateways/api/capture/route.js +55 -0
- package/dist/modules/payment_gateways/api/capture/route.js.map +7 -0
- package/dist/modules/payment_gateways/api/interceptors.js +24 -0
- package/dist/modules/payment_gateways/api/interceptors.js.map +7 -0
- package/dist/modules/payment_gateways/api/openapi.js +5 -0
- package/dist/modules/payment_gateways/api/openapi.js.map +7 -0
- package/dist/modules/payment_gateways/api/refund/route.js +56 -0
- package/dist/modules/payment_gateways/api/refund/route.js.map +7 -0
- package/dist/modules/payment_gateways/api/sessions/route.js +74 -0
- package/dist/modules/payment_gateways/api/sessions/route.js.map +7 -0
- package/dist/modules/payment_gateways/api/status/route.js +66 -0
- package/dist/modules/payment_gateways/api/status/route.js.map +7 -0
- package/dist/modules/payment_gateways/api/transactions/[id]/route.js +118 -0
- package/dist/modules/payment_gateways/api/transactions/[id]/route.js.map +7 -0
- package/dist/modules/payment_gateways/api/transactions/route.js +113 -0
- package/dist/modules/payment_gateways/api/transactions/route.js.map +7 -0
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +136 -0
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +7 -0
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js +496 -0
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +7 -0
- package/dist/modules/payment_gateways/backend/payment-gateways/page.meta.js +23 -0
- package/dist/modules/payment_gateways/backend/payment-gateways/page.meta.js.map +7 -0
- package/dist/modules/payment_gateways/data/enrichers.js +5 -0
- package/dist/modules/payment_gateways/data/enrichers.js.map +7 -0
- package/dist/modules/payment_gateways/data/entities.js +131 -0
- package/dist/modules/payment_gateways/data/entities.js.map +7 -0
- package/dist/modules/payment_gateways/data/validators.js +57 -0
- package/dist/modules/payment_gateways/data/validators.js.map +7 -0
- package/dist/modules/payment_gateways/di.js +16 -0
- package/dist/modules/payment_gateways/di.js.map +7 -0
- package/dist/modules/payment_gateways/events.js +21 -0
- package/dist/modules/payment_gateways/events.js.map +7 -0
- package/dist/modules/payment_gateways/i18n/en.js +6 -0
- package/dist/modules/payment_gateways/i18n/en.js.map +7 -0
- package/dist/modules/payment_gateways/i18n/pl.js +6 -0
- package/dist/modules/payment_gateways/i18n/pl.js.map +7 -0
- package/dist/modules/payment_gateways/index.js +9 -0
- package/dist/modules/payment_gateways/index.js.map +7 -0
- package/dist/modules/payment_gateways/lib/gateway-service.js +378 -0
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +7 -0
- package/dist/modules/payment_gateways/lib/queue.js +17 -0
- package/dist/modules/payment_gateways/lib/queue.js.map +7 -0
- package/dist/modules/payment_gateways/lib/status-machine.js +29 -0
- package/dist/modules/payment_gateways/lib/status-machine.js.map +7 -0
- package/dist/modules/payment_gateways/lib/webhook-processor.js +88 -0
- package/dist/modules/payment_gateways/lib/webhook-processor.js.map +7 -0
- package/dist/modules/payment_gateways/lib/webhook-utils.js +42 -0
- package/dist/modules/payment_gateways/lib/webhook-utils.js.map +7 -0
- package/dist/modules/payment_gateways/migrations/Migration20260305122155.js +19 -0
- package/dist/modules/payment_gateways/migrations/Migration20260305122155.js.map +7 -0
- package/dist/modules/payment_gateways/setup.js +13 -0
- package/dist/modules/payment_gateways/setup.js.map +7 -0
- package/dist/modules/payment_gateways/widgets/injection-table.js +7 -0
- package/dist/modules/payment_gateways/widgets/injection-table.js.map +7 -0
- package/dist/modules/payment_gateways/workers/status-poller.js +44 -0
- package/dist/modules/payment_gateways/workers/status-poller.js.map +7 -0
- package/dist/modules/payment_gateways/workers/webhook-processor.js +20 -0
- package/dist/modules/payment_gateways/workers/webhook-processor.js.map +7 -0
- package/dist/modules/sales/data/enrichers.js +72 -0
- package/dist/modules/sales/data/enrichers.js.map +7 -0
- package/dist/modules/sales/lib/makeSalesLineRoute.js +3 -0
- package/dist/modules/sales/lib/makeSalesLineRoute.js.map +2 -2
- package/dist/modules/sales/widgets/injection/payment-gateway-config-field/widget.js +29 -0
- package/dist/modules/sales/widgets/injection/payment-gateway-config-field/widget.js.map +7 -0
- package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js +23 -0
- package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js.map +7 -0
- package/dist/modules/sales/widgets/injection-table.js +13 -1
- package/dist/modules/sales/widgets/injection-table.js.map +2 -2
- package/dist/modules/shipping_carriers/acl.js +10 -0
- package/dist/modules/shipping_carriers/acl.js.map +7 -0
- package/dist/modules/shipping_carriers/api/cancel/route.js +55 -0
- package/dist/modules/shipping_carriers/api/cancel/route.js.map +7 -0
- package/dist/modules/shipping_carriers/api/interceptors.js +21 -0
- package/dist/modules/shipping_carriers/api/interceptors.js.map +7 -0
- package/dist/modules/shipping_carriers/api/openapi.js +5 -0
- package/dist/modules/shipping_carriers/api/openapi.js.map +7 -0
- package/dist/modules/shipping_carriers/api/rates/route.js +55 -0
- package/dist/modules/shipping_carriers/api/rates/route.js.map +7 -0
- package/dist/modules/shipping_carriers/api/shipments/route.js +61 -0
- package/dist/modules/shipping_carriers/api/shipments/route.js.map +7 -0
- package/dist/modules/shipping_carriers/api/tracking/route.js +58 -0
- package/dist/modules/shipping_carriers/api/tracking/route.js.map +7 -0
- package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js +119 -0
- package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js.map +7 -0
- package/dist/modules/shipping_carriers/data/enrichers.js +82 -0
- package/dist/modules/shipping_carriers/data/enrichers.js.map +7 -0
- package/dist/modules/shipping_carriers/data/entities.js +80 -0
- package/dist/modules/shipping_carriers/data/entities.js.map +7 -0
- package/dist/modules/shipping_carriers/data/validators.js +49 -0
- package/dist/modules/shipping_carriers/data/validators.js.map +7 -0
- package/dist/modules/shipping_carriers/di.js +15 -0
- package/dist/modules/shipping_carriers/di.js.map +7 -0
- package/dist/modules/shipping_carriers/events.js +19 -0
- package/dist/modules/shipping_carriers/events.js.map +7 -0
- package/dist/modules/shipping_carriers/i18n/en.js +11 -0
- package/dist/modules/shipping_carriers/i18n/en.js.map +7 -0
- package/dist/modules/shipping_carriers/i18n/pl.js +11 -0
- package/dist/modules/shipping_carriers/i18n/pl.js.map +7 -0
- package/dist/modules/shipping_carriers/index.js +9 -0
- package/dist/modules/shipping_carriers/index.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/adapter-registry.js +29 -0
- package/dist/modules/shipping_carriers/lib/adapter-registry.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/adapter.js +1 -0
- package/dist/modules/shipping_carriers/lib/adapter.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/queue.js +17 -0
- package/dist/modules/shipping_carriers/lib/queue.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/shipping-service.js +155 -0
- package/dist/modules/shipping_carriers/lib/shipping-service.js.map +7 -0
- package/dist/modules/shipping_carriers/lib/status-sync.js +37 -0
- package/dist/modules/shipping_carriers/lib/status-sync.js.map +7 -0
- package/dist/modules/shipping_carriers/migrations/Migration20260305170000.js +16 -0
- package/dist/modules/shipping_carriers/migrations/Migration20260305170000.js.map +7 -0
- package/dist/modules/shipping_carriers/setup.js +13 -0
- package/dist/modules/shipping_carriers/setup.js.map +7 -0
- package/dist/modules/shipping_carriers/widgets/injection/create-shipment-button/widget.js +25 -0
- package/dist/modules/shipping_carriers/widgets/injection/create-shipment-button/widget.js.map +7 -0
- package/dist/modules/shipping_carriers/widgets/injection/tracking-column/widget.js +23 -0
- package/dist/modules/shipping_carriers/widgets/injection/tracking-column/widget.js.map +7 -0
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js +40 -0
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js.map +7 -0
- package/dist/modules/shipping_carriers/widgets/injection-table.js +24 -0
- package/dist/modules/shipping_carriers/widgets/injection-table.js.map +7 -0
- package/dist/modules/shipping_carriers/workers/status-poller.js +21 -0
- package/dist/modules/shipping_carriers/workers/status-poller.js.map +7 -0
- package/dist/modules/shipping_carriers/workers/webhook-processor.js +54 -0
- package/dist/modules/shipping_carriers/workers/webhook-processor.js.map +7 -0
- package/dist/modules/translations/api/get/locales.js +1 -0
- package/dist/modules/translations/api/get/locales.js.map +2 -2
- package/dist/modules/translations/api/put/locales.js +1 -0
- package/dist/modules/translations/api/put/locales.js.map +2 -2
- package/generated/entities/carrier_shipment/index.ts +17 -0
- package/generated/entities/gateway_transaction/index.ts +22 -0
- package/generated/entities/webhook_processed_event/index.ts +7 -0
- package/generated/entities.ids.generated.ts +10 -1
- package/generated/entity-fields-registry.ts +6 -0
- package/jest.config.cjs +1 -0
- package/package.json +5 -2
- package/src/modules/auth/i18n/de.json +1 -0
- package/src/modules/auth/i18n/en.json +1 -0
- package/src/modules/auth/i18n/es.json +1 -0
- package/src/modules/auth/i18n/pl.json +1 -0
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +18 -5
- package/src/modules/data_sync/backend/data-sync/page.meta.ts +2 -2
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +50 -12
- package/src/modules/directory/api/get/tenants/lookup.ts +1 -0
- package/src/modules/integrations/AGENTS.md +31 -0
- package/src/modules/integrations/api/[id]/route.ts +38 -11
- package/src/modules/integrations/api/logs/route.ts +53 -27
- package/src/modules/integrations/api/route.ts +31 -1
- package/src/modules/integrations/api/umes-read.ts +177 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +902 -202
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +43 -9
- package/src/modules/integrations/backend/integrations/detail-page-widgets.ts +74 -0
- package/src/modules/integrations/backend/integrations/page.meta.ts +2 -2
- package/src/modules/integrations/backend/integrations/page.tsx +65 -54
- package/src/modules/integrations/i18n/de.json +15 -0
- package/src/modules/integrations/i18n/en.json +15 -0
- package/src/modules/integrations/i18n/es.json +15 -0
- package/src/modules/integrations/i18n/pl.json +15 -0
- package/src/modules/integrations/setup.ts +2 -2
- package/src/modules/payment_gateways/acl.ts +8 -0
- package/src/modules/payment_gateways/api/cancel/route.ts +56 -0
- package/src/modules/payment_gateways/api/capture/route.ts +56 -0
- package/src/modules/payment_gateways/api/interceptors.ts +22 -0
- package/src/modules/payment_gateways/api/openapi.ts +1 -0
- package/src/modules/payment_gateways/api/refund/route.ts +57 -0
- package/src/modules/payment_gateways/api/sessions/route.ts +76 -0
- package/src/modules/payment_gateways/api/status/route.ts +69 -0
- package/src/modules/payment_gateways/api/transactions/[id]/route.ts +123 -0
- package/src/modules/payment_gateways/api/transactions/route.ts +120 -0
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +161 -0
- package/src/modules/payment_gateways/backend/payment-gateways/page.meta.ts +19 -0
- package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +660 -0
- package/src/modules/payment_gateways/data/enrichers.ts +8 -0
- package/src/modules/payment_gateways/data/entities.ts +106 -0
- package/src/modules/payment_gateways/data/validators.ts +67 -0
- package/src/modules/payment_gateways/di.ts +26 -0
- package/src/modules/payment_gateways/events.ts +17 -0
- package/src/modules/payment_gateways/i18n/de.json +77 -0
- package/src/modules/payment_gateways/i18n/en.json +77 -0
- package/src/modules/payment_gateways/i18n/en.ts +4 -0
- package/src/modules/payment_gateways/i18n/es.json +77 -0
- package/src/modules/payment_gateways/i18n/pl.json +77 -0
- package/src/modules/payment_gateways/i18n/pl.ts +4 -0
- package/src/modules/payment_gateways/index.ts +5 -0
- package/src/modules/payment_gateways/lib/gateway-service.ts +486 -0
- package/src/modules/payment_gateways/lib/queue.ts +19 -0
- package/src/modules/payment_gateways/lib/status-machine.ts +28 -0
- package/src/modules/payment_gateways/lib/webhook-processor.ts +133 -0
- package/src/modules/payment_gateways/lib/webhook-utils.ts +52 -0
- package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +373 -0
- package/src/modules/payment_gateways/migrations/Migration20260305122155.ts +20 -0
- package/src/modules/payment_gateways/setup.ts +11 -0
- package/src/modules/payment_gateways/widgets/injection-table.ts +9 -0
- package/src/modules/payment_gateways/workers/status-poller.ts +58 -0
- package/src/modules/payment_gateways/workers/webhook-processor.ts +30 -0
- package/src/modules/sales/data/enrichers.ts +120 -0
- package/src/modules/sales/lib/makeSalesLineRoute.ts +3 -0
- package/src/modules/sales/widgets/injection/payment-gateway-config-field/widget.ts +28 -0
- package/src/modules/sales/widgets/injection/payment-gateway-status-column/widget.ts +22 -0
- package/src/modules/sales/widgets/injection-table.ts +12 -0
- package/src/modules/shipping_carriers/acl.ts +6 -0
- package/src/modules/shipping_carriers/api/cancel/route.ts +53 -0
- package/src/modules/shipping_carriers/api/interceptors.ts +19 -0
- package/src/modules/shipping_carriers/api/openapi.ts +1 -0
- package/src/modules/shipping_carriers/api/rates/route.ts +53 -0
- package/src/modules/shipping_carriers/api/shipments/route.ts +59 -0
- package/src/modules/shipping_carriers/api/tracking/route.ts +56 -0
- package/src/modules/shipping_carriers/api/webhook/[provider]/route.ts +134 -0
- package/src/modules/shipping_carriers/data/enrichers.ts +89 -0
- package/src/modules/shipping_carriers/data/entities.ts +60 -0
- package/src/modules/shipping_carriers/data/validators.ts +48 -0
- package/src/modules/shipping_carriers/di.ts +20 -0
- package/src/modules/shipping_carriers/events.ts +16 -0
- package/src/modules/shipping_carriers/i18n/de.json +7 -0
- package/src/modules/shipping_carriers/i18n/en.json +7 -0
- package/src/modules/shipping_carriers/i18n/en.ts +7 -0
- package/src/modules/shipping_carriers/i18n/es.json +7 -0
- package/src/modules/shipping_carriers/i18n/pl.json +7 -0
- package/src/modules/shipping_carriers/i18n/pl.ts +7 -0
- package/src/modules/shipping_carriers/index.ts +5 -0
- package/src/modules/shipping_carriers/lib/adapter-registry.ts +33 -0
- package/src/modules/shipping_carriers/lib/adapter.ts +93 -0
- package/src/modules/shipping_carriers/lib/queue.ts +19 -0
- package/src/modules/shipping_carriers/lib/shipping-service.ts +204 -0
- package/src/modules/shipping_carriers/lib/status-sync.ts +38 -0
- package/src/modules/shipping_carriers/migrations/Migration20260305170000.ts +14 -0
- package/src/modules/shipping_carriers/setup.ts +11 -0
- package/src/modules/shipping_carriers/widgets/injection/create-shipment-button/widget.ts +24 -0
- package/src/modules/shipping_carriers/widgets/injection/tracking-column/widget.ts +22 -0
- package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.tsx +44 -0
- package/src/modules/shipping_carriers/widgets/injection-table.ts +22 -0
- package/src/modules/shipping_carriers/workers/status-poller.ts +33 -0
- package/src/modules/shipping_carriers/workers/webhook-processor.ts +79 -0
- package/src/modules/translations/api/get/locales.ts +1 -0
- package/src/modules/translations/api/put/locales.ts +1 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
3
|
+
import {
|
|
4
|
+
getGatewayAdapter,
|
|
5
|
+
type CreateSessionInput,
|
|
6
|
+
type CreateSessionResult,
|
|
7
|
+
type CaptureResult,
|
|
8
|
+
type RefundResult,
|
|
9
|
+
type CancelResult,
|
|
10
|
+
type GatewayPaymentStatus,
|
|
11
|
+
type UnifiedPaymentStatus,
|
|
12
|
+
} from '@open-mercato/shared/modules/payment_gateways/types'
|
|
13
|
+
import type { CredentialsService } from '../../integrations/lib/credentials-service'
|
|
14
|
+
import type { IntegrationStateService } from '../../integrations/lib/state-service'
|
|
15
|
+
import type { IntegrationLogService } from '../../integrations/lib/log-service'
|
|
16
|
+
import { GatewayTransaction } from '../data/entities'
|
|
17
|
+
import { isValidTransition } from './status-machine'
|
|
18
|
+
import { emitPaymentGatewayEvent } from '../events'
|
|
19
|
+
|
|
20
|
+
export interface PaymentGatewayServiceDeps {
|
|
21
|
+
em: EntityManager
|
|
22
|
+
integrationCredentialsService: CredentialsService
|
|
23
|
+
integrationStateService?: IntegrationStateService
|
|
24
|
+
integrationLogService?: IntegrationLogService
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CreatePaymentSessionInput {
|
|
28
|
+
providerKey: string
|
|
29
|
+
paymentId: string
|
|
30
|
+
orderId?: string
|
|
31
|
+
amount: number
|
|
32
|
+
currencyCode: string
|
|
33
|
+
captureMethod?: 'automatic' | 'manual'
|
|
34
|
+
description?: string
|
|
35
|
+
successUrl?: string
|
|
36
|
+
cancelUrl?: string
|
|
37
|
+
metadata?: Record<string, unknown>
|
|
38
|
+
organizationId: string
|
|
39
|
+
tenantId: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createPaymentGatewayService(deps: PaymentGatewayServiceDeps) {
|
|
43
|
+
const { em, integrationCredentialsService, integrationLogService } = deps
|
|
44
|
+
|
|
45
|
+
async function findTransactionOrThrow(
|
|
46
|
+
transactionId: string,
|
|
47
|
+
scope: { organizationId: string; tenantId: string },
|
|
48
|
+
): Promise<GatewayTransaction> {
|
|
49
|
+
const transaction = await findOneWithDecryption(
|
|
50
|
+
em,
|
|
51
|
+
GatewayTransaction,
|
|
52
|
+
{
|
|
53
|
+
id: transactionId,
|
|
54
|
+
organizationId: scope.organizationId,
|
|
55
|
+
tenantId: scope.tenantId,
|
|
56
|
+
deletedAt: null,
|
|
57
|
+
},
|
|
58
|
+
undefined,
|
|
59
|
+
scope,
|
|
60
|
+
)
|
|
61
|
+
if (!transaction) {
|
|
62
|
+
throw new Error('Transaction not found')
|
|
63
|
+
}
|
|
64
|
+
return transaction
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readProviderSessionId(transaction: GatewayTransaction): string {
|
|
68
|
+
if (typeof transaction.providerSessionId === 'string' && transaction.providerSessionId.trim().length > 0) {
|
|
69
|
+
return transaction.providerSessionId
|
|
70
|
+
}
|
|
71
|
+
throw new Error('Transaction is missing provider session id')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function emitStatusEvent(status: UnifiedPaymentStatus, payload: Record<string, unknown>) {
|
|
75
|
+
type PaymentGatewayEventId = Parameters<typeof emitPaymentGatewayEvent>[0]
|
|
76
|
+
const eventMap: Partial<Record<UnifiedPaymentStatus, PaymentGatewayEventId>> = {
|
|
77
|
+
authorized: 'payment_gateways.payment.authorized',
|
|
78
|
+
captured: 'payment_gateways.payment.captured',
|
|
79
|
+
failed: 'payment_gateways.payment.failed',
|
|
80
|
+
refunded: 'payment_gateways.payment.refunded',
|
|
81
|
+
cancelled: 'payment_gateways.payment.cancelled',
|
|
82
|
+
}
|
|
83
|
+
const eventId = eventMap[status]
|
|
84
|
+
if (!eventId) return
|
|
85
|
+
await emitPaymentGatewayEvent(eventId, payload)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function writeTransactionLog(
|
|
89
|
+
providerKey: string,
|
|
90
|
+
scope: { organizationId: string; tenantId: string },
|
|
91
|
+
transactionId: string,
|
|
92
|
+
level: 'info' | 'warn' | 'error',
|
|
93
|
+
message: string,
|
|
94
|
+
payload?: Record<string, unknown> | null,
|
|
95
|
+
code?: string | null,
|
|
96
|
+
) {
|
|
97
|
+
if (!integrationLogService) return
|
|
98
|
+
await integrationLogService.write({
|
|
99
|
+
integrationId: `gateway_${providerKey}`,
|
|
100
|
+
scopeEntityType: 'payment_transaction',
|
|
101
|
+
scopeEntityId: transactionId,
|
|
102
|
+
level,
|
|
103
|
+
message,
|
|
104
|
+
code,
|
|
105
|
+
payload: payload ?? null,
|
|
106
|
+
}, scope)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function resolveAdapterAndCredentials(providerKey: string, scope: { organizationId: string; tenantId: string }) {
|
|
110
|
+
const integrationId = `gateway_${providerKey}`
|
|
111
|
+
const selectedVersion = deps.integrationStateService
|
|
112
|
+
? await deps.integrationStateService.resolveApiVersion(integrationId, scope)
|
|
113
|
+
: undefined
|
|
114
|
+
const adapter = getGatewayAdapter(providerKey, selectedVersion)
|
|
115
|
+
if (!adapter) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
selectedVersion
|
|
118
|
+
? `No gateway adapter registered for provider: ${providerKey} (version: ${selectedVersion})`
|
|
119
|
+
: `No gateway adapter registered for provider: ${providerKey}`,
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
const credentials = await integrationCredentialsService.resolve(integrationId, scope) ?? {}
|
|
123
|
+
|
|
124
|
+
return { adapter, credentials }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
async createPaymentSession(input: CreatePaymentSessionInput): Promise<{ transaction: GatewayTransaction; session: CreateSessionResult }> {
|
|
129
|
+
const scope = { organizationId: input.organizationId, tenantId: input.tenantId }
|
|
130
|
+
const { adapter, credentials } = await resolveAdapterAndCredentials(input.providerKey, scope)
|
|
131
|
+
|
|
132
|
+
const sessionInput: CreateSessionInput = {
|
|
133
|
+
paymentId: input.paymentId,
|
|
134
|
+
orderId: input.orderId,
|
|
135
|
+
tenantId: input.tenantId,
|
|
136
|
+
organizationId: input.organizationId,
|
|
137
|
+
amount: input.amount,
|
|
138
|
+
currencyCode: input.currencyCode,
|
|
139
|
+
captureMethod: input.captureMethod,
|
|
140
|
+
description: input.description,
|
|
141
|
+
successUrl: input.successUrl,
|
|
142
|
+
cancelUrl: input.cancelUrl,
|
|
143
|
+
metadata: input.metadata,
|
|
144
|
+
credentials,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const session = await adapter.createSession(sessionInput)
|
|
148
|
+
|
|
149
|
+
const transaction = em.create(GatewayTransaction, {
|
|
150
|
+
paymentId: input.paymentId,
|
|
151
|
+
providerKey: input.providerKey,
|
|
152
|
+
providerSessionId: session.sessionId,
|
|
153
|
+
unifiedStatus: session.status,
|
|
154
|
+
redirectUrl: session.redirectUrl ?? null,
|
|
155
|
+
clientSecret: null,
|
|
156
|
+
amount: String(input.amount),
|
|
157
|
+
currencyCode: input.currencyCode,
|
|
158
|
+
gatewayMetadata: session.providerData ?? null,
|
|
159
|
+
organizationId: input.organizationId,
|
|
160
|
+
tenantId: input.tenantId,
|
|
161
|
+
})
|
|
162
|
+
await em.persistAndFlush(transaction)
|
|
163
|
+
await emitPaymentGatewayEvent('payment_gateways.session.created', {
|
|
164
|
+
transactionId: transaction.id,
|
|
165
|
+
paymentId: transaction.paymentId,
|
|
166
|
+
providerKey: transaction.providerKey,
|
|
167
|
+
status: transaction.unifiedStatus,
|
|
168
|
+
organizationId: transaction.organizationId,
|
|
169
|
+
tenantId: transaction.tenantId,
|
|
170
|
+
})
|
|
171
|
+
await writeTransactionLog(
|
|
172
|
+
transaction.providerKey,
|
|
173
|
+
scope,
|
|
174
|
+
transaction.id,
|
|
175
|
+
'info',
|
|
176
|
+
'Payment session created',
|
|
177
|
+
{
|
|
178
|
+
paymentId: transaction.paymentId,
|
|
179
|
+
providerSessionId: transaction.providerSessionId,
|
|
180
|
+
status: transaction.unifiedStatus,
|
|
181
|
+
amount: input.amount,
|
|
182
|
+
currencyCode: input.currencyCode,
|
|
183
|
+
},
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return { transaction, session }
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
async capturePayment(transactionId: string, amount: number | undefined, scope: { organizationId: string; tenantId: string }): Promise<CaptureResult> {
|
|
190
|
+
const transaction = await findTransactionOrThrow(transactionId, scope)
|
|
191
|
+
const { adapter, credentials } = await resolveAdapterAndCredentials(
|
|
192
|
+
transaction.providerKey,
|
|
193
|
+
{ organizationId: transaction.organizationId, tenantId: transaction.tenantId },
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
const result = await adapter.capture({
|
|
197
|
+
sessionId: readProviderSessionId(transaction),
|
|
198
|
+
amount,
|
|
199
|
+
credentials,
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
transaction.unifiedStatus = result.status
|
|
203
|
+
transaction.gatewayMetadata = { ...transaction.gatewayMetadata, captureResult: result.providerData }
|
|
204
|
+
await em.flush()
|
|
205
|
+
await emitStatusEvent(result.status, {
|
|
206
|
+
transactionId: transaction.id,
|
|
207
|
+
paymentId: transaction.paymentId,
|
|
208
|
+
providerKey: transaction.providerKey,
|
|
209
|
+
organizationId: transaction.organizationId,
|
|
210
|
+
tenantId: transaction.tenantId,
|
|
211
|
+
})
|
|
212
|
+
await writeTransactionLog(
|
|
213
|
+
transaction.providerKey,
|
|
214
|
+
{ organizationId: transaction.organizationId, tenantId: transaction.tenantId },
|
|
215
|
+
transaction.id,
|
|
216
|
+
'info',
|
|
217
|
+
'Payment captured',
|
|
218
|
+
{
|
|
219
|
+
amount: amount ?? null,
|
|
220
|
+
status: result.status,
|
|
221
|
+
capturedAmount: result.capturedAmount,
|
|
222
|
+
},
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return result
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
async refundPayment(
|
|
229
|
+
transactionId: string,
|
|
230
|
+
amount: number | undefined,
|
|
231
|
+
reason: string | undefined,
|
|
232
|
+
scope: { organizationId: string; tenantId: string },
|
|
233
|
+
): Promise<RefundResult> {
|
|
234
|
+
const transaction = await findTransactionOrThrow(transactionId, scope)
|
|
235
|
+
const { adapter, credentials } = await resolveAdapterAndCredentials(
|
|
236
|
+
transaction.providerKey,
|
|
237
|
+
{ organizationId: transaction.organizationId, tenantId: transaction.tenantId },
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
const result = await adapter.refund({
|
|
241
|
+
sessionId: readProviderSessionId(transaction),
|
|
242
|
+
amount,
|
|
243
|
+
reason,
|
|
244
|
+
credentials,
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
transaction.unifiedStatus = result.status
|
|
248
|
+
transaction.gatewayRefundId = result.refundId
|
|
249
|
+
transaction.gatewayMetadata = { ...transaction.gatewayMetadata, refundResult: result.providerData }
|
|
250
|
+
await em.flush()
|
|
251
|
+
await emitStatusEvent(result.status, {
|
|
252
|
+
transactionId: transaction.id,
|
|
253
|
+
paymentId: transaction.paymentId,
|
|
254
|
+
providerKey: transaction.providerKey,
|
|
255
|
+
organizationId: transaction.organizationId,
|
|
256
|
+
tenantId: transaction.tenantId,
|
|
257
|
+
})
|
|
258
|
+
await writeTransactionLog(
|
|
259
|
+
transaction.providerKey,
|
|
260
|
+
{ organizationId: transaction.organizationId, tenantId: transaction.tenantId },
|
|
261
|
+
transaction.id,
|
|
262
|
+
'info',
|
|
263
|
+
'Payment refunded',
|
|
264
|
+
{
|
|
265
|
+
amount: amount ?? null,
|
|
266
|
+
reason: reason ?? null,
|
|
267
|
+
status: result.status,
|
|
268
|
+
refundId: result.refundId,
|
|
269
|
+
},
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
return result
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
async cancelPayment(
|
|
276
|
+
transactionId: string,
|
|
277
|
+
reason: string | undefined,
|
|
278
|
+
scope: { organizationId: string; tenantId: string },
|
|
279
|
+
): Promise<CancelResult> {
|
|
280
|
+
const transaction = await findTransactionOrThrow(transactionId, scope)
|
|
281
|
+
const { adapter, credentials } = await resolveAdapterAndCredentials(
|
|
282
|
+
transaction.providerKey,
|
|
283
|
+
{ organizationId: transaction.organizationId, tenantId: transaction.tenantId },
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
const result = await adapter.cancel({
|
|
287
|
+
sessionId: readProviderSessionId(transaction),
|
|
288
|
+
reason,
|
|
289
|
+
credentials,
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
transaction.unifiedStatus = result.status
|
|
293
|
+
await em.flush()
|
|
294
|
+
await emitStatusEvent(result.status, {
|
|
295
|
+
transactionId: transaction.id,
|
|
296
|
+
paymentId: transaction.paymentId,
|
|
297
|
+
providerKey: transaction.providerKey,
|
|
298
|
+
organizationId: transaction.organizationId,
|
|
299
|
+
tenantId: transaction.tenantId,
|
|
300
|
+
})
|
|
301
|
+
await writeTransactionLog(
|
|
302
|
+
transaction.providerKey,
|
|
303
|
+
{ organizationId: transaction.organizationId, tenantId: transaction.tenantId },
|
|
304
|
+
transaction.id,
|
|
305
|
+
'info',
|
|
306
|
+
'Payment cancelled',
|
|
307
|
+
{
|
|
308
|
+
reason: reason ?? null,
|
|
309
|
+
status: result.status,
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
return result
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
async getPaymentStatus(transactionId: string, scope: { organizationId: string; tenantId: string }): Promise<GatewayPaymentStatus> {
|
|
317
|
+
const transaction = await findTransactionOrThrow(transactionId, scope)
|
|
318
|
+
const { adapter, credentials } = await resolveAdapterAndCredentials(
|
|
319
|
+
transaction.providerKey,
|
|
320
|
+
{ organizationId: transaction.organizationId, tenantId: transaction.tenantId },
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
const status = await adapter.getStatus({
|
|
324
|
+
sessionId: readProviderSessionId(transaction),
|
|
325
|
+
credentials,
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
if (status.status !== transaction.unifiedStatus && isValidTransition(transaction.unifiedStatus as UnifiedPaymentStatus, status.status)) {
|
|
329
|
+
const previousStatus = transaction.unifiedStatus
|
|
330
|
+
transaction.unifiedStatus = status.status
|
|
331
|
+
transaction.gatewayStatus = status.status
|
|
332
|
+
transaction.gatewayMetadata = { ...transaction.gatewayMetadata, statusResult: status.providerData ?? null }
|
|
333
|
+
transaction.lastPolledAt = new Date()
|
|
334
|
+
await em.flush()
|
|
335
|
+
await emitStatusEvent(status.status, {
|
|
336
|
+
transactionId: transaction.id,
|
|
337
|
+
paymentId: transaction.paymentId,
|
|
338
|
+
providerKey: transaction.providerKey,
|
|
339
|
+
previousStatus,
|
|
340
|
+
organizationId: transaction.organizationId,
|
|
341
|
+
tenantId: transaction.tenantId,
|
|
342
|
+
})
|
|
343
|
+
await writeTransactionLog(
|
|
344
|
+
transaction.providerKey,
|
|
345
|
+
{ organizationId: transaction.organizationId, tenantId: transaction.tenantId },
|
|
346
|
+
transaction.id,
|
|
347
|
+
'info',
|
|
348
|
+
'Payment status updated by poller',
|
|
349
|
+
{
|
|
350
|
+
previousStatus,
|
|
351
|
+
nextStatus: status.status,
|
|
352
|
+
},
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return status
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
async syncTransactionStatus(transactionId: string, update: {
|
|
360
|
+
unifiedStatus: UnifiedPaymentStatus
|
|
361
|
+
providerStatus?: string
|
|
362
|
+
providerData?: Record<string, unknown>
|
|
363
|
+
webhookEvent?: {
|
|
364
|
+
eventType: string
|
|
365
|
+
idempotencyKey: string
|
|
366
|
+
processed: boolean
|
|
367
|
+
receivedAt?: string
|
|
368
|
+
}
|
|
369
|
+
}, scope: { organizationId: string; tenantId: string }): Promise<void> {
|
|
370
|
+
const transaction = await findTransactionOrThrow(transactionId, scope)
|
|
371
|
+
const currentStatus = transaction.unifiedStatus as UnifiedPaymentStatus
|
|
372
|
+
const canTransition = isValidTransition(currentStatus, update.unifiedStatus)
|
|
373
|
+
const shouldApplyStatus = canTransition && update.unifiedStatus !== currentStatus
|
|
374
|
+
const previousStatus = transaction.unifiedStatus
|
|
375
|
+
if (shouldApplyStatus) {
|
|
376
|
+
transaction.unifiedStatus = update.unifiedStatus
|
|
377
|
+
}
|
|
378
|
+
if (update.providerStatus) {
|
|
379
|
+
transaction.gatewayStatus = update.providerStatus
|
|
380
|
+
}
|
|
381
|
+
if (update.providerData) {
|
|
382
|
+
transaction.gatewayMetadata = { ...transaction.gatewayMetadata, ...update.providerData }
|
|
383
|
+
}
|
|
384
|
+
if (update.webhookEvent) {
|
|
385
|
+
const webhookLog = Array.isArray(transaction.webhookLog) ? transaction.webhookLog : []
|
|
386
|
+
webhookLog.push({
|
|
387
|
+
eventType: update.webhookEvent.eventType,
|
|
388
|
+
receivedAt: update.webhookEvent.receivedAt ?? new Date().toISOString(),
|
|
389
|
+
idempotencyKey: update.webhookEvent.idempotencyKey,
|
|
390
|
+
unifiedStatus: update.unifiedStatus,
|
|
391
|
+
processed: update.webhookEvent.processed,
|
|
392
|
+
})
|
|
393
|
+
transaction.webhookLog = webhookLog
|
|
394
|
+
}
|
|
395
|
+
transaction.lastWebhookAt = new Date()
|
|
396
|
+
await em.flush()
|
|
397
|
+
if (shouldApplyStatus) {
|
|
398
|
+
await emitStatusEvent(update.unifiedStatus, {
|
|
399
|
+
transactionId: transaction.id,
|
|
400
|
+
paymentId: transaction.paymentId,
|
|
401
|
+
providerKey: transaction.providerKey,
|
|
402
|
+
previousStatus,
|
|
403
|
+
organizationId: transaction.organizationId,
|
|
404
|
+
tenantId: transaction.tenantId,
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
await writeTransactionLog(
|
|
408
|
+
transaction.providerKey,
|
|
409
|
+
{ organizationId: transaction.organizationId, tenantId: transaction.tenantId },
|
|
410
|
+
transaction.id,
|
|
411
|
+
shouldApplyStatus ? 'info' : 'warn',
|
|
412
|
+
shouldApplyStatus ? 'Payment status synchronized from webhook' : 'Webhook received with no status transition',
|
|
413
|
+
{
|
|
414
|
+
previousStatus,
|
|
415
|
+
nextStatus: update.unifiedStatus,
|
|
416
|
+
providerStatus: update.providerStatus ?? null,
|
|
417
|
+
eventType: update.webhookEvent?.eventType ?? null,
|
|
418
|
+
idempotencyKey: update.webhookEvent?.idempotencyKey ?? null,
|
|
419
|
+
},
|
|
420
|
+
)
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
async findTransaction(id: string, scope: { organizationId: string; tenantId: string }): Promise<GatewayTransaction | null> {
|
|
424
|
+
return findOneWithDecryption(
|
|
425
|
+
em,
|
|
426
|
+
GatewayTransaction,
|
|
427
|
+
{
|
|
428
|
+
id,
|
|
429
|
+
organizationId: scope.organizationId,
|
|
430
|
+
tenantId: scope.tenantId,
|
|
431
|
+
deletedAt: null,
|
|
432
|
+
},
|
|
433
|
+
undefined,
|
|
434
|
+
scope,
|
|
435
|
+
)
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
async findTransactionBySessionId(
|
|
439
|
+
providerSessionId: string,
|
|
440
|
+
scope: { organizationId: string; tenantId: string },
|
|
441
|
+
providerKey?: string,
|
|
442
|
+
): Promise<GatewayTransaction | null> {
|
|
443
|
+
return findOneWithDecryption(
|
|
444
|
+
em,
|
|
445
|
+
GatewayTransaction,
|
|
446
|
+
{
|
|
447
|
+
providerSessionId,
|
|
448
|
+
organizationId: scope.organizationId,
|
|
449
|
+
tenantId: scope.tenantId,
|
|
450
|
+
deletedAt: null,
|
|
451
|
+
...(providerKey ? { providerKey } : {}),
|
|
452
|
+
},
|
|
453
|
+
undefined,
|
|
454
|
+
scope,
|
|
455
|
+
)
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
async listTransactionsForStatusPolling(scope?: {
|
|
459
|
+
organizationId?: string
|
|
460
|
+
tenantId?: string
|
|
461
|
+
providerKey?: string
|
|
462
|
+
limit?: number
|
|
463
|
+
}): Promise<GatewayTransaction[]> {
|
|
464
|
+
const where: Record<string, unknown> = {
|
|
465
|
+
unifiedStatus: { $in: ['pending', 'authorized', 'partially_captured'] },
|
|
466
|
+
deletedAt: null,
|
|
467
|
+
}
|
|
468
|
+
if (scope?.organizationId) where.organizationId = scope.organizationId
|
|
469
|
+
if (scope?.tenantId) where.tenantId = scope.tenantId
|
|
470
|
+
if (scope?.providerKey) where.providerKey = scope.providerKey
|
|
471
|
+
|
|
472
|
+
return findWithDecryption(
|
|
473
|
+
em,
|
|
474
|
+
GatewayTransaction,
|
|
475
|
+
where,
|
|
476
|
+
{
|
|
477
|
+
orderBy: { updatedAt: 'asc' },
|
|
478
|
+
limit: scope?.limit ?? 100,
|
|
479
|
+
},
|
|
480
|
+
scope,
|
|
481
|
+
)
|
|
482
|
+
},
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export type PaymentGatewayService = ReturnType<typeof createPaymentGatewayService>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createQueue, type Queue } from '@open-mercato/queue'
|
|
2
|
+
import { getRedisUrl } from '@open-mercato/shared/lib/redis/connection'
|
|
3
|
+
|
|
4
|
+
const queues = new Map<string, Queue<Record<string, unknown>>>()
|
|
5
|
+
|
|
6
|
+
export function getPaymentGatewayQueue(queueName: string): Queue<Record<string, unknown>> {
|
|
7
|
+
const existing = queues.get(queueName)
|
|
8
|
+
if (existing) return existing
|
|
9
|
+
|
|
10
|
+
const created = process.env.QUEUE_STRATEGY === 'async'
|
|
11
|
+
? createQueue<Record<string, unknown>>(queueName, 'async', {
|
|
12
|
+
connection: { url: getRedisUrl('QUEUE') },
|
|
13
|
+
concurrency: Math.max(1, Number.parseInt(process.env.PAYMENT_GATEWAY_QUEUE_CONCURRENCY ?? '5', 10) || 5),
|
|
14
|
+
})
|
|
15
|
+
: createQueue<Record<string, unknown>>(queueName, 'local')
|
|
16
|
+
|
|
17
|
+
queues.set(queueName, created)
|
|
18
|
+
return created
|
|
19
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { UnifiedPaymentStatus } from '@open-mercato/shared/modules/payment_gateways/types'
|
|
2
|
+
|
|
3
|
+
const VALID_TRANSITIONS: Record<string, UnifiedPaymentStatus[]> = {
|
|
4
|
+
pending: ['authorized', 'captured', 'failed', 'expired', 'cancelled'],
|
|
5
|
+
authorized: ['captured', 'partially_captured', 'cancelled', 'failed'],
|
|
6
|
+
captured: ['refunded', 'partially_refunded'],
|
|
7
|
+
partially_captured: ['captured', 'refunded', 'partially_refunded', 'cancelled'],
|
|
8
|
+
partially_refunded: ['refunded'],
|
|
9
|
+
// Terminal states: refunded, cancelled, failed, expired — no valid transitions out
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const TERMINAL_STATUSES: Set<UnifiedPaymentStatus> = new Set([
|
|
13
|
+
'refunded',
|
|
14
|
+
'cancelled',
|
|
15
|
+
'failed',
|
|
16
|
+
'expired',
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
export function isValidTransition(from: UnifiedPaymentStatus, to: UnifiedPaymentStatus): boolean {
|
|
20
|
+
if (from === to) return false
|
|
21
|
+
const allowed = VALID_TRANSITIONS[from]
|
|
22
|
+
if (!allowed) return false
|
|
23
|
+
return allowed.includes(to)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isTerminalStatus(status: UnifiedPaymentStatus): boolean {
|
|
27
|
+
return TERMINAL_STATUSES.has(status)
|
|
28
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { getGatewayAdapter, type WebhookEvent } from '@open-mercato/shared/modules/payment_gateways/types'
|
|
3
|
+
import type { IntegrationLogService } from '../../integrations/lib/log-service'
|
|
4
|
+
import type { PaymentGatewayService } from './gateway-service'
|
|
5
|
+
import { claimWebhookProcessing, releaseWebhookClaim } from './webhook-utils'
|
|
6
|
+
|
|
7
|
+
export type PaymentGatewayWebhookJobPayload = {
|
|
8
|
+
providerKey: string
|
|
9
|
+
event: WebhookEvent
|
|
10
|
+
transactionId?: string | null
|
|
11
|
+
scope?: {
|
|
12
|
+
organizationId: string
|
|
13
|
+
tenantId: string
|
|
14
|
+
} | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type PaymentGatewayWebhookProcessorDeps = {
|
|
18
|
+
em: EntityManager
|
|
19
|
+
paymentGatewayService: PaymentGatewayService
|
|
20
|
+
integrationLogService: IntegrationLogService
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readSessionIdFromEvent(event: WebhookEvent): string | null {
|
|
24
|
+
const id = event.data.id
|
|
25
|
+
if (typeof id === 'string' && id.trim().length > 0) return id.trim()
|
|
26
|
+
const paymentIntent = event.data.payment_intent
|
|
27
|
+
if (typeof paymentIntent === 'string' && paymentIntent.trim().length > 0) return paymentIntent.trim()
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readScopeFromEvent(event: WebhookEvent): { organizationId: string; tenantId: string } | null {
|
|
32
|
+
const metadata = event.data.metadata
|
|
33
|
+
if (!metadata || typeof metadata !== 'object') return null
|
|
34
|
+
|
|
35
|
+
const metadataRecord = metadata as Record<string, unknown>
|
|
36
|
+
const organizationId = typeof metadataRecord.organizationId === 'string'
|
|
37
|
+
? metadataRecord.organizationId.trim()
|
|
38
|
+
: ''
|
|
39
|
+
const tenantId = typeof metadataRecord.tenantId === 'string'
|
|
40
|
+
? metadataRecord.tenantId.trim()
|
|
41
|
+
: ''
|
|
42
|
+
|
|
43
|
+
if (!organizationId || !tenantId) return null
|
|
44
|
+
return { organizationId, tenantId }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function writeTransactionLog(
|
|
48
|
+
integrationLogService: IntegrationLogService,
|
|
49
|
+
providerKey: string,
|
|
50
|
+
scope: { organizationId: string; tenantId: string },
|
|
51
|
+
transactionId: string,
|
|
52
|
+
level: 'info' | 'warn' | 'error',
|
|
53
|
+
message: string,
|
|
54
|
+
payload?: Record<string, unknown>,
|
|
55
|
+
) {
|
|
56
|
+
await integrationLogService.write({
|
|
57
|
+
integrationId: `gateway_${providerKey}`,
|
|
58
|
+
scopeEntityType: 'payment_transaction',
|
|
59
|
+
scopeEntityId: transactionId,
|
|
60
|
+
level,
|
|
61
|
+
message,
|
|
62
|
+
payload: payload ?? null,
|
|
63
|
+
}, scope)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function processPaymentGatewayWebhookJob(
|
|
67
|
+
deps: PaymentGatewayWebhookProcessorDeps,
|
|
68
|
+
payload: PaymentGatewayWebhookJobPayload,
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
const { em, paymentGatewayService, integrationLogService } = deps
|
|
71
|
+
const { providerKey, event } = payload
|
|
72
|
+
const scopedPayload = payload.scope ?? readScopeFromEvent(event)
|
|
73
|
+
|
|
74
|
+
let transaction = payload.transactionId && scopedPayload
|
|
75
|
+
? await paymentGatewayService.findTransaction(payload.transactionId, scopedPayload)
|
|
76
|
+
: null
|
|
77
|
+
|
|
78
|
+
if (!transaction) {
|
|
79
|
+
const sessionId = readSessionIdFromEvent(event)
|
|
80
|
+
if (sessionId && scopedPayload) {
|
|
81
|
+
transaction = await paymentGatewayService.findTransactionBySessionId(sessionId, scopedPayload, providerKey)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!transaction) return
|
|
85
|
+
|
|
86
|
+
const scope = { organizationId: transaction.organizationId, tenantId: transaction.tenantId }
|
|
87
|
+
const claimed = await claimWebhookProcessing(em, event.idempotencyKey, providerKey, scope, event.eventType)
|
|
88
|
+
if (!claimed) {
|
|
89
|
+
await writeTransactionLog(integrationLogService, providerKey, scope, transaction.id, 'info', 'Duplicate payment gateway webhook skipped', {
|
|
90
|
+
eventType: event.eventType,
|
|
91
|
+
idempotencyKey: event.idempotencyKey,
|
|
92
|
+
})
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const adapter = getGatewayAdapter(providerKey)
|
|
98
|
+
if (!adapter) {
|
|
99
|
+
await writeTransactionLog(integrationLogService, providerKey, scope, transaction.id, 'warn', 'Missing payment gateway adapter for webhook event', {
|
|
100
|
+
providerKey,
|
|
101
|
+
eventType: event.eventType,
|
|
102
|
+
})
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const providerStatus = typeof event.data.status === 'string' ? event.data.status : ''
|
|
107
|
+
const unifiedStatus = adapter.mapStatus(providerStatus, event.eventType)
|
|
108
|
+
await writeTransactionLog(integrationLogService, providerKey, scope, transaction.id, 'info', 'Payment gateway webhook received', {
|
|
109
|
+
eventType: event.eventType,
|
|
110
|
+
providerStatus,
|
|
111
|
+
unifiedStatus,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
await paymentGatewayService.syncTransactionStatus(transaction.id, {
|
|
115
|
+
unifiedStatus,
|
|
116
|
+
providerStatus: event.eventType,
|
|
117
|
+
providerData: event.data,
|
|
118
|
+
webhookEvent: {
|
|
119
|
+
eventType: event.eventType,
|
|
120
|
+
idempotencyKey: event.idempotencyKey,
|
|
121
|
+
processed: true,
|
|
122
|
+
},
|
|
123
|
+
}, scope)
|
|
124
|
+
|
|
125
|
+
await writeTransactionLog(integrationLogService, providerKey, scope, transaction.id, 'info', 'Payment gateway webhook processed', {
|
|
126
|
+
eventType: event.eventType,
|
|
127
|
+
unifiedStatus,
|
|
128
|
+
})
|
|
129
|
+
} catch (error: unknown) {
|
|
130
|
+
await releaseWebhookClaim(em, event.idempotencyKey, providerKey, scope)
|
|
131
|
+
throw error
|
|
132
|
+
}
|
|
133
|
+
}
|