@open-mercato/core 0.6.7-develop.6572.1.3d8e83062f → 0.6.7-develop.6574.1.05dcf72ba8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/gateway_payment_operation/index.js +35 -0
  3. package/dist/generated/entities/gateway_payment_operation/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +1 -0
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +18 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/payment_gateways/api/cancel/route.js +2 -1
  9. package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
  10. package/dist/modules/payment_gateways/api/capture/route.js +2 -1
  11. package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
  12. package/dist/modules/payment_gateways/api/refund/route.js +2 -1
  13. package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
  14. package/dist/modules/payment_gateways/data/entities.js +67 -0
  15. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  16. package/dist/modules/payment_gateways/data/validators.js +6 -3
  17. package/dist/modules/payment_gateways/data/validators.js.map +2 -2
  18. package/dist/modules/payment_gateways/lib/gateway-service.js +145 -111
  19. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  20. package/dist/modules/payment_gateways/lib/payment-operation-idempotency.js +183 -0
  21. package/dist/modules/payment_gateways/lib/payment-operation-idempotency.js.map +7 -0
  22. package/dist/modules/payment_gateways/migrations/Migration20260709220735_payment_gateways.js +13 -0
  23. package/dist/modules/payment_gateways/migrations/Migration20260709220735_payment_gateways.js.map +7 -0
  24. package/generated/entities/gateway_payment_operation/index.ts +16 -0
  25. package/generated/entities.ids.generated.ts +1 -0
  26. package/generated/entity-fields-registry.ts +18 -0
  27. package/package.json +7 -7
  28. package/src/modules/payment_gateways/api/cancel/route.ts +1 -0
  29. package/src/modules/payment_gateways/api/capture/route.ts +1 -0
  30. package/src/modules/payment_gateways/api/refund/route.ts +1 -0
  31. package/src/modules/payment_gateways/data/entities.ts +59 -0
  32. package/src/modules/payment_gateways/data/validators.ts +3 -0
  33. package/src/modules/payment_gateways/lib/gateway-service.ts +168 -117
  34. package/src/modules/payment_gateways/lib/payment-operation-idempotency.ts +238 -0
  35. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +331 -1
  36. package/src/modules/payment_gateways/migrations/Migration20260709220735_payment_gateways.ts +12 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/payment_gateways/lib/gateway-service.ts"],
4
- "sourcesContent": ["import { randomUUID } from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n getGatewayAdapter,\n type CreateSessionInput,\n type CreateSessionResult,\n type CaptureResult,\n type RefundResult,\n type CancelResult,\n type GatewayPaymentStatus,\n type PaymentGatewayPresentationRequest,\n type UnifiedPaymentStatus,\n} from '@open-mercato/shared/modules/payment_gateways/types'\nimport type { CredentialsService } from '../../integrations/lib/credentials-service'\nimport type { IntegrationStateService } from '../../integrations/lib/state-service'\nimport type { IntegrationLogService } from '../../integrations/lib/log-service'\nimport { conflict } from '@open-mercato/shared/lib/crud/errors'\nimport { GatewaySessionInitialization, GatewayTransaction } from '../data/entities'\nimport { canApplyManualAction, isValidTransition, type ManualGatewayAction } from './status-machine'\nimport { emitPaymentGatewayEvent } from '../events'\nimport { readGatewayMetadata, readWebhookLog } from './transaction-fields'\nimport {\n buildPaymentSessionOperationKey,\n claimPaymentSessionInitialization,\n findPaymentSessionInitialization,\n reclaimPaymentSessionInitialization,\n refreshPaymentSessionInitialization,\n releasePaymentSessionInitialization,\n} from './session-idempotency'\n\nconst PAYMENT_SESSION_CLAIM_STALE_MS = 30_000\nconst PAYMENT_SESSION_WAIT_INTERVAL_MS = 25\n\nfunction assertManualActionAllowed(action: ManualGatewayAction, transaction: GatewayTransaction): void {\n const current = transaction.unifiedStatus as UnifiedPaymentStatus\n if (!canApplyManualAction(action, current)) {\n throw conflict(`Cannot ${action} a payment in status \"${current}\"`)\n }\n}\n\nfunction applyAdapterResultStatus(\n action: ManualGatewayAction,\n transaction: GatewayTransaction,\n resultStatus: UnifiedPaymentStatus,\n): boolean {\n const current = transaction.unifiedStatus as UnifiedPaymentStatus\n if (resultStatus === current) return false\n if (!isValidTransition(current, resultStatus)) {\n throw conflict(\n `Gateway returned status \"${resultStatus}\" which is not a valid transition from \"${current}\" for ${action}`,\n )\n }\n transaction.unifiedStatus = resultStatus\n return true\n}\n\nexport interface PaymentGatewayServiceDeps {\n em: EntityManager\n integrationCredentialsService: CredentialsService\n integrationStateService?: IntegrationStateService\n integrationLogService?: IntegrationLogService\n sessionClaimOptions?: {\n staleAfterMs?: number\n heartbeatIntervalMs?: number\n pollIntervalMs?: number\n }\n}\n\nexport interface CreatePaymentSessionInput {\n providerKey: string\n paymentId: string\n idempotencyKey?: string\n orderId?: string\n amount: number\n currencyCode: string\n captureMethod?: 'automatic' | 'manual'\n paymentTypes?: string[]\n description?: string\n successUrl?: string\n cancelUrl?: string\n metadata?: Record<string, unknown>\n presentation?: PaymentGatewayPresentationRequest\n organizationId: string\n tenantId: string\n}\n\nexport function createPaymentGatewayService(deps: PaymentGatewayServiceDeps) {\n const { em, integrationCredentialsService, integrationLogService } = deps\n const claimStaleAfterMs = Math.max(1, deps.sessionClaimOptions?.staleAfterMs ?? PAYMENT_SESSION_CLAIM_STALE_MS)\n const claimHeartbeatIntervalMs = Math.max(\n 1,\n deps.sessionClaimOptions?.heartbeatIntervalMs ?? Math.floor(claimStaleAfterMs / 3),\n )\n const claimPollIntervalMs = Math.max(1, deps.sessionClaimOptions?.pollIntervalMs ?? PAYMENT_SESSION_WAIT_INTERVAL_MS)\n\n async function findTransactionOrThrow(\n transactionId: string,\n scope: { organizationId: string; tenantId: string },\n ): Promise<GatewayTransaction> {\n const transaction = await findOneWithDecryption(\n em,\n GatewayTransaction,\n {\n id: transactionId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!transaction) {\n throw new Error('Transaction not found')\n }\n return transaction\n }\n\n function readProviderSessionId(transaction: GatewayTransaction): string {\n if (typeof transaction.providerSessionId === 'string' && transaction.providerSessionId.trim().length > 0) {\n return transaction.providerSessionId\n }\n throw new Error('Transaction is missing provider session id')\n }\n\n async function emitStatusEvent(status: UnifiedPaymentStatus, payload: Record<string, unknown>) {\n type PaymentGatewayEventId = Parameters<typeof emitPaymentGatewayEvent>[0]\n const eventMap: Partial<Record<UnifiedPaymentStatus, PaymentGatewayEventId>> = {\n authorized: 'payment_gateways.payment.authorized',\n captured: 'payment_gateways.payment.captured',\n failed: 'payment_gateways.payment.failed',\n refunded: 'payment_gateways.payment.refunded',\n cancelled: 'payment_gateways.payment.cancelled',\n }\n const eventId = eventMap[status]\n if (!eventId) return\n await emitPaymentGatewayEvent(eventId, payload)\n }\n\n async function writeTransactionLog(\n providerKey: string,\n scope: { organizationId: string; tenantId: string },\n transactionId: string,\n level: 'info' | 'warn' | 'error',\n message: string,\n payload?: Record<string, unknown> | null,\n code?: string | null,\n ) {\n if (!integrationLogService) return\n await integrationLogService.write({\n integrationId: `gateway_${providerKey}`,\n scopeEntityType: 'payment_transaction',\n scopeEntityId: transactionId,\n level,\n message,\n code,\n payload: payload ?? null,\n }, scope)\n }\n\n async function resolveAdapterAndCredentials(providerKey: string, scope: { organizationId: string; tenantId: string }) {\n const integrationId = `gateway_${providerKey}`\n const selectedVersion = deps.integrationStateService\n ? await deps.integrationStateService.resolveApiVersion(integrationId, scope)\n : undefined\n const adapter = getGatewayAdapter(providerKey, selectedVersion)\n if (!adapter) {\n throw new Error(\n selectedVersion\n ? `No gateway adapter registered for provider: ${providerKey} (version: ${selectedVersion})`\n : `No gateway adapter registered for provider: ${providerKey}`,\n )\n }\n const credentials = await integrationCredentialsService.resolve(integrationId, scope) ?? {}\n\n return { adapter, credentials }\n }\n\n function createGatewayTransaction(\n manager: EntityManager,\n input: CreatePaymentSessionInput,\n session: CreateSessionResult,\n id?: string,\n ): GatewayTransaction {\n const data = {\n paymentId: input.paymentId,\n providerKey: input.providerKey,\n providerSessionId: session.sessionId,\n unifiedStatus: session.status,\n redirectUrl: session.redirectUrl\n ?? (session.clientSession?.type === 'redirect' ? session.clientSession.redirectUrl : null),\n clientSecret: session.clientSecret ?? null,\n amount: String(input.amount),\n currencyCode: input.currencyCode,\n gatewayMetadata: {\n ...(session.providerData ?? {}),\n ...(session.clientSession ? { clientSession: session.clientSession } : {}),\n },\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n deletedAt: null,\n }\n return id\n ? manager.create(GatewayTransaction, { id, ...data })\n : manager.create(GatewayTransaction, data)\n }\n\n function restoreSession(transaction: GatewayTransaction): CreateSessionResult {\n const metadata = readGatewayMetadata(transaction.gatewayMetadata)\n const { clientSession, ...providerData } = metadata\n return {\n sessionId: readProviderSessionId(transaction),\n status: transaction.unifiedStatus as UnifiedPaymentStatus,\n ...(transaction.clientSecret ? { clientSecret: transaction.clientSecret } : {}),\n ...(transaction.redirectUrl ? { redirectUrl: transaction.redirectUrl } : {}),\n ...(Object.keys(providerData).length > 0 ? { providerData } : {}),\n ...(clientSession && typeof clientSession === 'object'\n ? { clientSession: clientSession as CreateSessionResult['clientSession'] }\n : {}),\n }\n }\n\n async function recordCreatedSession(\n transaction: GatewayTransaction,\n input: CreatePaymentSessionInput,\n scope: { organizationId: string; tenantId: string },\n ): Promise<void> {\n await emitPaymentGatewayEvent('payment_gateways.session.created', {\n transactionId: transaction.id,\n paymentId: transaction.paymentId,\n providerKey: transaction.providerKey,\n status: transaction.unifiedStatus,\n organizationId: transaction.organizationId,\n tenantId: transaction.tenantId,\n })\n await writeTransactionLog(\n transaction.providerKey,\n scope,\n transaction.id,\n 'info',\n 'Payment session created',\n {\n paymentId: transaction.paymentId,\n providerSessionId: transaction.providerSessionId,\n status: transaction.unifiedStatus,\n amount: input.amount,\n currencyCode: input.currencyCode,\n },\n )\n }\n\n function startClaimHeartbeat(\n ownership: { id: string; claimToken: string },\n scope: { organizationId: string; tenantId: string },\n ): () => Promise<void> {\n let stopped = false\n let timer: ReturnType<typeof setTimeout> | null = null\n let pendingRefresh = Promise.resolve()\n\n const schedule = () => {\n timer = setTimeout(() => {\n pendingRefresh = refreshPaymentSessionInitialization(em, ownership, scope, new Date())\n .then(\n (refreshed) => {\n if (refreshed && !stopped) schedule()\n },\n () => {\n if (!stopped) schedule()\n },\n )\n }, claimHeartbeatIntervalMs)\n }\n schedule()\n\n return async () => {\n stopped = true\n if (timer) clearTimeout(timer)\n await pendingRefresh\n }\n }\n\n return {\n async createPaymentSession(input: CreatePaymentSessionInput): Promise<{ transaction: GatewayTransaction; session: CreateSessionResult }> {\n const scope = { organizationId: input.organizationId, tenantId: input.tenantId }\n const { adapter, credentials } = await resolveAdapterAndCredentials(input.providerKey, scope)\n\n const sessionInput: CreateSessionInput = {\n paymentId: input.paymentId,\n orderId: input.orderId,\n idempotencyKey: input.idempotencyKey,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n amount: input.amount,\n currencyCode: input.currencyCode,\n captureMethod: input.captureMethod,\n paymentTypes: input.paymentTypes,\n description: input.description,\n successUrl: input.successUrl,\n cancelUrl: input.cancelUrl,\n metadata: input.metadata,\n presentation: input.presentation,\n credentials,\n }\n\n if (!input.idempotencyKey) {\n const session = await adapter.createSession(sessionInput)\n const transaction = createGatewayTransaction(em, input, session)\n await em.persist(transaction).flush()\n await recordCreatedSession(transaction, input, scope)\n return { transaction, session }\n }\n\n const operationKey = buildPaymentSessionOperationKey({\n idempotencyKey: input.idempotencyKey,\n paymentId: input.paymentId,\n providerKey: input.providerKey,\n scope,\n })\n while (true) {\n const existing = await findPaymentSessionInitialization(em, operationKey, input.providerKey, scope)\n if (existing?.gatewayTransactionId) {\n const transaction = await findOneWithDecryption(\n em.fork(),\n GatewayTransaction,\n {\n id: existing.gatewayTransactionId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!transaction) {\n throw new Error('Completed payment session is missing its gateway transaction')\n }\n return { transaction, session: restoreSession(transaction) }\n }\n\n const claimedAt = new Date()\n const staleBefore = new Date(claimedAt.getTime() - claimStaleAfterMs)\n const ownership = existing\n ? await reclaimPaymentSessionInitialization(em, existing, scope, claimedAt, staleBefore)\n : await claimPaymentSessionInitialization(em, operationKey, input.providerKey, scope, claimedAt)\n\n if (!ownership) {\n await new Promise((resolve) => setTimeout(resolve, claimPollIntervalMs))\n continue\n }\n\n let session: CreateSessionResult\n const stopHeartbeat = startClaimHeartbeat(ownership, scope)\n try {\n session = await adapter.createSession({ ...sessionInput, idempotencyKey: operationKey })\n } catch (error) {\n await releasePaymentSessionInitialization(em, ownership, scope).catch(() => undefined)\n throw error\n } finally {\n await stopHeartbeat()\n }\n\n const transactionId = randomUUID()\n const transaction = await em.fork().transactional(async (transactionEm) => {\n const finalizedRows = await transactionEm.nativeUpdate(\n GatewaySessionInitialization,\n {\n id: ownership.id,\n claimToken: ownership.claimToken,\n gatewayTransactionId: null,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n {\n gatewayTransactionId: transactionId,\n claimToken: null,\n claimedAt: null,\n updatedAt: new Date(),\n },\n )\n if (finalizedRows === 0) return null\n const created = createGatewayTransaction(transactionEm, input, session, transactionId)\n await transactionEm.persist(created).flush()\n return created\n })\n\n if (!transaction) {\n continue\n }\n await recordCreatedSession(transaction, input, scope)\n return { transaction, session }\n }\n },\n\n async capturePayment(transactionId: string, amount: number | undefined, scope: { organizationId: string; tenantId: string }): Promise<CaptureResult> {\n const transaction = await findTransactionOrThrow(transactionId, scope)\n assertManualActionAllowed('capture', transaction)\n const { adapter, credentials } = await resolveAdapterAndCredentials(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n )\n\n const result = await adapter.capture({\n sessionId: readProviderSessionId(transaction),\n amount,\n credentials,\n })\n\n const statusChanged = applyAdapterResultStatus('capture', transaction, result.status)\n transaction.gatewayMetadata = { ...readGatewayMetadata(transaction.gatewayMetadata), captureResult: result.providerData }\n await em.flush()\n if (statusChanged) {\n await emitStatusEvent(result.status, {\n transactionId: transaction.id,\n paymentId: transaction.paymentId,\n providerKey: transaction.providerKey,\n organizationId: transaction.organizationId,\n tenantId: transaction.tenantId,\n })\n }\n await writeTransactionLog(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n transaction.id,\n 'info',\n 'Payment captured',\n {\n amount: amount ?? null,\n status: result.status,\n capturedAmount: result.capturedAmount,\n },\n )\n\n return result\n },\n\n async refundPayment(\n transactionId: string,\n amount: number | undefined,\n reason: string | undefined,\n scope: { organizationId: string; tenantId: string },\n ): Promise<RefundResult> {\n const transaction = await findTransactionOrThrow(transactionId, scope)\n assertManualActionAllowed('refund', transaction)\n const { adapter, credentials } = await resolveAdapterAndCredentials(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n )\n\n const result = await adapter.refund({\n sessionId: readProviderSessionId(transaction),\n amount,\n reason,\n credentials,\n })\n\n const statusChanged = applyAdapterResultStatus('refund', transaction, result.status)\n transaction.gatewayRefundId = result.refundId\n transaction.gatewayMetadata = { ...readGatewayMetadata(transaction.gatewayMetadata), refundResult: result.providerData }\n await em.flush()\n if (statusChanged) {\n await emitStatusEvent(result.status, {\n transactionId: transaction.id,\n paymentId: transaction.paymentId,\n providerKey: transaction.providerKey,\n organizationId: transaction.organizationId,\n tenantId: transaction.tenantId,\n })\n }\n await writeTransactionLog(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n transaction.id,\n 'info',\n 'Payment refunded',\n {\n amount: amount ?? null,\n reason: reason ?? null,\n status: result.status,\n refundId: result.refundId,\n },\n )\n\n return result\n },\n\n async cancelPayment(\n transactionId: string,\n reason: string | undefined,\n scope: { organizationId: string; tenantId: string },\n ): Promise<CancelResult> {\n const transaction = await findTransactionOrThrow(transactionId, scope)\n assertManualActionAllowed('cancel', transaction)\n const { adapter, credentials } = await resolveAdapterAndCredentials(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n )\n\n const result = await adapter.cancel({\n sessionId: readProviderSessionId(transaction),\n reason,\n credentials,\n })\n\n const statusChanged = applyAdapterResultStatus('cancel', transaction, result.status)\n await em.flush()\n if (statusChanged) {\n await emitStatusEvent(result.status, {\n transactionId: transaction.id,\n paymentId: transaction.paymentId,\n providerKey: transaction.providerKey,\n organizationId: transaction.organizationId,\n tenantId: transaction.tenantId,\n })\n }\n await writeTransactionLog(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n transaction.id,\n 'info',\n 'Payment cancelled',\n {\n reason: reason ?? null,\n status: result.status,\n },\n )\n\n return result\n },\n\n async getPaymentStatus(transactionId: string, scope: { organizationId: string; tenantId: string }): Promise<GatewayPaymentStatus> {\n const transaction = await findTransactionOrThrow(transactionId, scope)\n const { adapter, credentials } = await resolveAdapterAndCredentials(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n )\n\n const status = await adapter.getStatus({\n sessionId: readProviderSessionId(transaction),\n credentials,\n })\n\n if (status.status !== transaction.unifiedStatus && isValidTransition(transaction.unifiedStatus as UnifiedPaymentStatus, status.status)) {\n const previousStatus = transaction.unifiedStatus\n transaction.unifiedStatus = status.status\n transaction.gatewayStatus = status.status\n transaction.gatewayMetadata = { ...readGatewayMetadata(transaction.gatewayMetadata), statusResult: status.providerData ?? null }\n transaction.lastPolledAt = new Date()\n await em.flush()\n await emitStatusEvent(status.status, {\n transactionId: transaction.id,\n paymentId: transaction.paymentId,\n providerKey: transaction.providerKey,\n previousStatus,\n organizationId: transaction.organizationId,\n tenantId: transaction.tenantId,\n })\n await writeTransactionLog(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n transaction.id,\n 'info',\n 'Payment status updated by poller',\n {\n previousStatus,\n nextStatus: status.status,\n },\n )\n }\n\n return status\n },\n\n async syncTransactionStatus(transactionId: string, update: {\n unifiedStatus: UnifiedPaymentStatus\n providerStatus?: string\n providerData?: Record<string, unknown>\n webhookEvent?: {\n eventType: string\n idempotencyKey: string\n processed: boolean\n receivedAt?: string\n }\n }, scope: { organizationId: string; tenantId: string }): Promise<void> {\n const transaction = await findTransactionOrThrow(transactionId, scope)\n const currentStatus = transaction.unifiedStatus as UnifiedPaymentStatus\n const canTransition = isValidTransition(currentStatus, update.unifiedStatus)\n const shouldApplyStatus = canTransition && update.unifiedStatus !== currentStatus\n const previousStatus = transaction.unifiedStatus\n if (shouldApplyStatus) {\n transaction.unifiedStatus = update.unifiedStatus\n }\n if (update.providerStatus) {\n transaction.gatewayStatus = update.providerStatus\n }\n if (update.providerData) {\n transaction.gatewayMetadata = { ...readGatewayMetadata(transaction.gatewayMetadata), ...update.providerData }\n }\n if (update.webhookEvent) {\n const webhookLog = readWebhookLog(transaction.webhookLog)\n webhookLog.push({\n eventType: update.webhookEvent.eventType,\n receivedAt: update.webhookEvent.receivedAt ?? new Date().toISOString(),\n idempotencyKey: update.webhookEvent.idempotencyKey,\n unifiedStatus: update.unifiedStatus,\n processed: update.webhookEvent.processed,\n })\n transaction.webhookLog = webhookLog\n }\n transaction.lastWebhookAt = new Date()\n await em.flush()\n if (shouldApplyStatus) {\n await emitStatusEvent(update.unifiedStatus, {\n transactionId: transaction.id,\n paymentId: transaction.paymentId,\n providerKey: transaction.providerKey,\n previousStatus,\n organizationId: transaction.organizationId,\n tenantId: transaction.tenantId,\n })\n }\n await writeTransactionLog(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n transaction.id,\n shouldApplyStatus ? 'info' : 'warn',\n shouldApplyStatus ? 'Payment status synchronized from webhook' : 'Webhook received with no status transition',\n {\n previousStatus,\n nextStatus: update.unifiedStatus,\n providerStatus: update.providerStatus ?? null,\n eventType: update.webhookEvent?.eventType ?? null,\n idempotencyKey: update.webhookEvent?.idempotencyKey ?? null,\n },\n )\n },\n\n async findTransaction(id: string, scope: { organizationId: string; tenantId: string }): Promise<GatewayTransaction | null> {\n return findOneWithDecryption(\n em,\n GatewayTransaction,\n {\n id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n },\n\n async findTransactionBySessionId(\n providerSessionId: string,\n scope: { organizationId: string; tenantId: string },\n providerKey?: string,\n ): Promise<GatewayTransaction | null> {\n return findOneWithDecryption(\n em,\n GatewayTransaction,\n {\n providerSessionId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n ...(providerKey ? { providerKey } : {}),\n },\n undefined,\n scope,\n )\n },\n\n async listTransactionsForStatusPolling(scope?: {\n organizationId?: string\n tenantId?: string\n providerKey?: string\n limit?: number\n }): Promise<GatewayTransaction[]> {\n const where: Record<string, unknown> = {\n unifiedStatus: { $in: ['pending', 'authorized', 'partially_captured'] },\n deletedAt: null,\n }\n if (scope?.organizationId) where.organizationId = scope.organizationId\n if (scope?.tenantId) where.tenantId = scope.tenantId\n if (scope?.providerKey) where.providerKey = scope.providerKey\n\n return findWithDecryption(\n em,\n GatewayTransaction,\n where,\n {\n orderBy: { updatedAt: 'asc' },\n limit: scope?.limit ?? 100,\n },\n scope,\n )\n },\n }\n}\n\nexport type PaymentGatewayService = ReturnType<typeof createPaymentGatewayService>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { randomUUID } from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n getGatewayAdapter,\n type CreateSessionInput,\n type CreateSessionResult,\n type CaptureResult,\n type RefundResult,\n type CancelResult,\n type GatewayAdapter,\n type GatewayPaymentStatus,\n type PaymentGatewayPresentationRequest,\n type UnifiedPaymentStatus,\n} from '@open-mercato/shared/modules/payment_gateways/types'\nimport type { CredentialsService } from '../../integrations/lib/credentials-service'\nimport type { IntegrationStateService } from '../../integrations/lib/state-service'\nimport type { IntegrationLogService } from '../../integrations/lib/log-service'\nimport { conflict } from '@open-mercato/shared/lib/crud/errors'\nimport { GatewaySessionInitialization, GatewayTransaction } from '../data/entities'\nimport { canApplyManualAction, isValidTransition, type ManualGatewayAction } from './status-machine'\nimport { emitPaymentGatewayEvent } from '../events'\nimport { readGatewayMetadata, readWebhookLog } from './transaction-fields'\nimport {\n completePaymentOperation,\n failPaymentOperation,\n preparePaymentOperation,\n} from './payment-operation-idempotency'\nimport {\n buildPaymentSessionOperationKey,\n claimPaymentSessionInitialization,\n findPaymentSessionInitialization,\n reclaimPaymentSessionInitialization,\n refreshPaymentSessionInitialization,\n releasePaymentSessionInitialization,\n} from './session-idempotency'\n\nconst PAYMENT_SESSION_CLAIM_STALE_MS = 30_000\nconst PAYMENT_SESSION_WAIT_INTERVAL_MS = 25\n\nfunction assertManualActionAllowed(action: ManualGatewayAction, transaction: GatewayTransaction): void {\n const current = transaction.unifiedStatus as UnifiedPaymentStatus\n if (!canApplyManualAction(action, current)) {\n throw conflict(`Cannot ${action} a payment in status \"${current}\"`)\n }\n}\n\nfunction applyAdapterResultStatus(\n action: ManualGatewayAction,\n transaction: GatewayTransaction,\n resultStatus: UnifiedPaymentStatus,\n): boolean {\n const current = transaction.unifiedStatus as UnifiedPaymentStatus\n if (resultStatus === current) return false\n if (isValidTransition(resultStatus, current)) return false\n if (!isValidTransition(current, resultStatus)) {\n throw conflict(\n `Gateway returned status \"${resultStatus}\" which is not a valid transition from \"${current}\" for ${action}`,\n )\n }\n transaction.unifiedStatus = resultStatus\n return true\n}\n\nexport interface PaymentGatewayServiceDeps {\n em: EntityManager\n integrationCredentialsService: CredentialsService\n integrationStateService?: IntegrationStateService\n integrationLogService?: IntegrationLogService\n sessionClaimOptions?: {\n staleAfterMs?: number\n heartbeatIntervalMs?: number\n pollIntervalMs?: number\n }\n}\n\nexport interface CreatePaymentSessionInput {\n providerKey: string\n paymentId: string\n idempotencyKey?: string\n orderId?: string\n amount: number\n currencyCode: string\n captureMethod?: 'automatic' | 'manual'\n paymentTypes?: string[]\n description?: string\n successUrl?: string\n cancelUrl?: string\n metadata?: Record<string, unknown>\n presentation?: PaymentGatewayPresentationRequest\n organizationId: string\n tenantId: string\n}\n\nexport function createPaymentGatewayService(deps: PaymentGatewayServiceDeps) {\n const { em, integrationCredentialsService, integrationLogService } = deps\n const claimStaleAfterMs = Math.max(1, deps.sessionClaimOptions?.staleAfterMs ?? PAYMENT_SESSION_CLAIM_STALE_MS)\n const claimHeartbeatIntervalMs = Math.max(\n 1,\n deps.sessionClaimOptions?.heartbeatIntervalMs ?? Math.floor(claimStaleAfterMs / 3),\n )\n const claimPollIntervalMs = Math.max(1, deps.sessionClaimOptions?.pollIntervalMs ?? PAYMENT_SESSION_WAIT_INTERVAL_MS)\n\n async function findTransactionOrThrow(\n transactionId: string,\n scope: { organizationId: string; tenantId: string },\n targetEm: EntityManager = em,\n ): Promise<GatewayTransaction> {\n const transaction = await findOneWithDecryption(\n targetEm,\n GatewayTransaction,\n {\n id: transactionId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!transaction) {\n throw new Error('Transaction not found')\n }\n return transaction\n }\n\n function readProviderSessionId(transaction: GatewayTransaction): string {\n if (typeof transaction.providerSessionId === 'string' && transaction.providerSessionId.trim().length > 0) {\n return transaction.providerSessionId\n }\n throw new Error('Transaction is missing provider session id')\n }\n\n async function emitStatusEvent(status: UnifiedPaymentStatus, payload: Record<string, unknown>) {\n type PaymentGatewayEventId = Parameters<typeof emitPaymentGatewayEvent>[0]\n const eventMap: Partial<Record<UnifiedPaymentStatus, PaymentGatewayEventId>> = {\n authorized: 'payment_gateways.payment.authorized',\n captured: 'payment_gateways.payment.captured',\n failed: 'payment_gateways.payment.failed',\n refunded: 'payment_gateways.payment.refunded',\n cancelled: 'payment_gateways.payment.cancelled',\n }\n const eventId = eventMap[status]\n if (!eventId) return\n await emitPaymentGatewayEvent(eventId, payload)\n }\n\n async function writeTransactionLog(\n providerKey: string,\n scope: { organizationId: string; tenantId: string },\n transactionId: string,\n level: 'info' | 'warn' | 'error',\n message: string,\n payload?: Record<string, unknown> | null,\n code?: string | null,\n ) {\n if (!integrationLogService) return\n await integrationLogService.write({\n integrationId: `gateway_${providerKey}`,\n scopeEntityType: 'payment_transaction',\n scopeEntityId: transactionId,\n level,\n message,\n code,\n payload: payload ?? null,\n }, scope)\n }\n\n async function resolveAdapterAndCredentials(providerKey: string, scope: { organizationId: string; tenantId: string }) {\n const integrationId = `gateway_${providerKey}`\n const selectedVersion = deps.integrationStateService\n ? await deps.integrationStateService.resolveApiVersion(integrationId, scope)\n : undefined\n const adapter = getGatewayAdapter(providerKey, selectedVersion)\n if (!adapter) {\n throw new Error(\n selectedVersion\n ? `No gateway adapter registered for provider: ${providerKey} (version: ${selectedVersion})`\n : `No gateway adapter registered for provider: ${providerKey}`,\n )\n }\n const credentials = await integrationCredentialsService.resolve(integrationId, scope) ?? {}\n\n return { adapter, credentials }\n }\n\n type ManualOperationResult = CaptureResult | RefundResult | CancelResult\n\n async function executeManualOperation<TResult extends ManualOperationResult>(input: {\n action: ManualGatewayAction\n transactionId: string\n operationId?: string\n payload: Record<string, unknown>\n scope: { organizationId: string; tenantId: string }\n invoke: (context: {\n adapter: GatewayAdapter\n credentials: Record<string, unknown>\n transaction: GatewayTransaction\n idempotencyKey: string\n }) => Promise<TResult>\n applyResult: (transaction: GatewayTransaction, result: TResult) => void\n afterCommit: (transaction: GatewayTransaction, result: TResult) => Promise<void>\n }): Promise<TResult> {\n const transaction = await findTransactionOrThrow(input.transactionId, input.scope)\n const prepared = await preparePaymentOperation({\n em,\n transactionId: transaction.id,\n providerKey: transaction.providerKey,\n action: input.action,\n operationId: input.operationId,\n payload: input.payload,\n scope: input.scope,\n assertInitialAllowed: () => assertManualActionAllowed(input.action, transaction),\n })\n if (prepared.kind === 'completed') {\n if (prepared.result.status !== transaction.unifiedStatus) {\n throw conflict(\n `Cannot replay ${input.action} because the payment status has changed since the operation completed`,\n )\n }\n return prepared.result as unknown as TResult\n }\n\n try {\n const { adapter, credentials } = await resolveAdapterAndCredentials(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n )\n const result = await input.invoke({\n adapter,\n credentials,\n transaction,\n idempotencyKey: prepared.claim.providerIdempotencyKey,\n })\n const statusChanged = await completePaymentOperation(\n em,\n prepared.claim,\n result as unknown as Record<string, unknown>,\n async (tx) => {\n const current = await findTransactionOrThrow(input.transactionId, input.scope, tx)\n const changed = applyAdapterResultStatus(input.action, current, result.status)\n input.applyResult(current, result)\n return changed\n },\n )\n if (statusChanged) {\n await emitStatusEvent(result.status, {\n transactionId: transaction.id,\n paymentId: transaction.paymentId,\n providerKey: transaction.providerKey,\n organizationId: transaction.organizationId,\n tenantId: transaction.tenantId,\n })\n }\n await input.afterCommit(transaction, result)\n return result\n } catch (error: unknown) {\n await Promise.allSettled([failPaymentOperation(em, prepared.claim)])\n throw error\n }\n }\n\n function createGatewayTransaction(\n manager: EntityManager,\n input: CreatePaymentSessionInput,\n session: CreateSessionResult,\n id?: string,\n ): GatewayTransaction {\n const data = {\n paymentId: input.paymentId,\n providerKey: input.providerKey,\n providerSessionId: session.sessionId,\n unifiedStatus: session.status,\n redirectUrl: session.redirectUrl\n ?? (session.clientSession?.type === 'redirect' ? session.clientSession.redirectUrl : null),\n clientSecret: session.clientSecret ?? null,\n amount: String(input.amount),\n currencyCode: input.currencyCode,\n gatewayMetadata: {\n ...(session.providerData ?? {}),\n ...(session.clientSession ? { clientSession: session.clientSession } : {}),\n },\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n deletedAt: null,\n }\n return id\n ? manager.create(GatewayTransaction, { id, ...data })\n : manager.create(GatewayTransaction, data)\n }\n\n function restoreSession(transaction: GatewayTransaction): CreateSessionResult {\n const metadata = readGatewayMetadata(transaction.gatewayMetadata)\n const { clientSession, ...providerData } = metadata\n return {\n sessionId: readProviderSessionId(transaction),\n status: transaction.unifiedStatus as UnifiedPaymentStatus,\n ...(transaction.clientSecret ? { clientSecret: transaction.clientSecret } : {}),\n ...(transaction.redirectUrl ? { redirectUrl: transaction.redirectUrl } : {}),\n ...(Object.keys(providerData).length > 0 ? { providerData } : {}),\n ...(clientSession && typeof clientSession === 'object'\n ? { clientSession: clientSession as CreateSessionResult['clientSession'] }\n : {}),\n }\n }\n\n async function recordCreatedSession(\n transaction: GatewayTransaction,\n input: CreatePaymentSessionInput,\n scope: { organizationId: string; tenantId: string },\n ): Promise<void> {\n await emitPaymentGatewayEvent('payment_gateways.session.created', {\n transactionId: transaction.id,\n paymentId: transaction.paymentId,\n providerKey: transaction.providerKey,\n status: transaction.unifiedStatus,\n organizationId: transaction.organizationId,\n tenantId: transaction.tenantId,\n })\n await writeTransactionLog(\n transaction.providerKey,\n scope,\n transaction.id,\n 'info',\n 'Payment session created',\n {\n paymentId: transaction.paymentId,\n providerSessionId: transaction.providerSessionId,\n status: transaction.unifiedStatus,\n amount: input.amount,\n currencyCode: input.currencyCode,\n },\n )\n }\n\n function startClaimHeartbeat(\n ownership: { id: string; claimToken: string },\n scope: { organizationId: string; tenantId: string },\n ): () => Promise<void> {\n let stopped = false\n let timer: ReturnType<typeof setTimeout> | null = null\n let pendingRefresh = Promise.resolve()\n\n const schedule = () => {\n timer = setTimeout(() => {\n pendingRefresh = refreshPaymentSessionInitialization(em, ownership, scope, new Date())\n .then(\n (refreshed) => {\n if (refreshed && !stopped) schedule()\n },\n () => {\n if (!stopped) schedule()\n },\n )\n }, claimHeartbeatIntervalMs)\n }\n schedule()\n\n return async () => {\n stopped = true\n if (timer) clearTimeout(timer)\n await pendingRefresh\n }\n }\n\n return {\n async createPaymentSession(input: CreatePaymentSessionInput): Promise<{ transaction: GatewayTransaction; session: CreateSessionResult }> {\n const scope = { organizationId: input.organizationId, tenantId: input.tenantId }\n const { adapter, credentials } = await resolveAdapterAndCredentials(input.providerKey, scope)\n\n const sessionInput: CreateSessionInput = {\n paymentId: input.paymentId,\n orderId: input.orderId,\n idempotencyKey: input.idempotencyKey,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n amount: input.amount,\n currencyCode: input.currencyCode,\n captureMethod: input.captureMethod,\n paymentTypes: input.paymentTypes,\n description: input.description,\n successUrl: input.successUrl,\n cancelUrl: input.cancelUrl,\n metadata: input.metadata,\n presentation: input.presentation,\n credentials,\n }\n\n if (!input.idempotencyKey) {\n const session = await adapter.createSession(sessionInput)\n const transaction = createGatewayTransaction(em, input, session)\n await em.persist(transaction).flush()\n await recordCreatedSession(transaction, input, scope)\n return { transaction, session }\n }\n\n const operationKey = buildPaymentSessionOperationKey({\n idempotencyKey: input.idempotencyKey,\n paymentId: input.paymentId,\n providerKey: input.providerKey,\n scope,\n })\n while (true) {\n const existing = await findPaymentSessionInitialization(em, operationKey, input.providerKey, scope)\n if (existing?.gatewayTransactionId) {\n const transaction = await findOneWithDecryption(\n em.fork(),\n GatewayTransaction,\n {\n id: existing.gatewayTransactionId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!transaction) {\n throw new Error('Completed payment session is missing its gateway transaction')\n }\n return { transaction, session: restoreSession(transaction) }\n }\n\n const claimedAt = new Date()\n const staleBefore = new Date(claimedAt.getTime() - claimStaleAfterMs)\n const ownership = existing\n ? await reclaimPaymentSessionInitialization(em, existing, scope, claimedAt, staleBefore)\n : await claimPaymentSessionInitialization(em, operationKey, input.providerKey, scope, claimedAt)\n\n if (!ownership) {\n await new Promise((resolve) => setTimeout(resolve, claimPollIntervalMs))\n continue\n }\n\n let session: CreateSessionResult\n const stopHeartbeat = startClaimHeartbeat(ownership, scope)\n try {\n session = await adapter.createSession({ ...sessionInput, idempotencyKey: operationKey })\n } catch (error) {\n await releasePaymentSessionInitialization(em, ownership, scope).catch(() => undefined)\n throw error\n } finally {\n await stopHeartbeat()\n }\n\n const transactionId = randomUUID()\n const transaction = await em.fork().transactional(async (transactionEm) => {\n const finalizedRows = await transactionEm.nativeUpdate(\n GatewaySessionInitialization,\n {\n id: ownership.id,\n claimToken: ownership.claimToken,\n gatewayTransactionId: null,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n {\n gatewayTransactionId: transactionId,\n claimToken: null,\n claimedAt: null,\n updatedAt: new Date(),\n },\n )\n if (finalizedRows === 0) return null\n const created = createGatewayTransaction(transactionEm, input, session, transactionId)\n await transactionEm.persist(created).flush()\n return created\n })\n\n if (!transaction) {\n continue\n }\n await recordCreatedSession(transaction, input, scope)\n return { transaction, session }\n }\n },\n\n async capturePayment(\n transactionId: string,\n amount: number | undefined,\n scope: { organizationId: string; tenantId: string },\n operationId?: string,\n ): Promise<CaptureResult> {\n return executeManualOperation({\n action: 'capture',\n transactionId,\n operationId,\n payload: { amount: amount ?? null },\n scope,\n invoke: ({ adapter, credentials, transaction, idempotencyKey }) => adapter.capture({\n sessionId: readProviderSessionId(transaction),\n amount,\n credentials,\n idempotencyKey,\n }),\n applyResult: (transaction, result) => {\n transaction.gatewayMetadata = {\n ...readGatewayMetadata(transaction.gatewayMetadata),\n captureResult: result.providerData,\n }\n },\n afterCommit: (transaction, result) => writeTransactionLog(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n transaction.id,\n 'info',\n 'Payment captured',\n { amount: amount ?? null, status: result.status, capturedAmount: result.capturedAmount },\n ),\n })\n },\n\n async refundPayment(\n transactionId: string,\n amount: number | undefined,\n reason: string | undefined,\n scope: { organizationId: string; tenantId: string },\n operationId?: string,\n ): Promise<RefundResult> {\n return executeManualOperation({\n action: 'refund',\n transactionId,\n operationId,\n payload: { amount: amount ?? null, reason: reason ?? null },\n scope,\n invoke: ({ adapter, credentials, transaction, idempotencyKey }) => adapter.refund({\n sessionId: readProviderSessionId(transaction),\n amount,\n reason,\n credentials,\n idempotencyKey,\n }),\n applyResult: (transaction, result) => {\n transaction.gatewayRefundId = result.refundId\n transaction.gatewayMetadata = {\n ...readGatewayMetadata(transaction.gatewayMetadata),\n refundResult: result.providerData,\n }\n },\n afterCommit: (transaction, result) => writeTransactionLog(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n transaction.id,\n 'info',\n 'Payment refunded',\n { amount: amount ?? null, reason: reason ?? null, status: result.status, refundId: result.refundId },\n ),\n })\n },\n\n async cancelPayment(\n transactionId: string,\n reason: string | undefined,\n scope: { organizationId: string; tenantId: string },\n operationId?: string,\n ): Promise<CancelResult> {\n return executeManualOperation({\n action: 'cancel',\n transactionId,\n operationId,\n payload: { reason: reason ?? null },\n scope,\n invoke: ({ adapter, credentials, transaction, idempotencyKey }) => adapter.cancel({\n sessionId: readProviderSessionId(transaction),\n reason,\n credentials,\n idempotencyKey,\n }),\n applyResult: () => {},\n afterCommit: (transaction, result) => writeTransactionLog(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n transaction.id,\n 'info',\n 'Payment cancelled',\n { reason: reason ?? null, status: result.status },\n ),\n })\n },\n\n async getPaymentStatus(transactionId: string, scope: { organizationId: string; tenantId: string }): Promise<GatewayPaymentStatus> {\n const transaction = await findTransactionOrThrow(transactionId, scope)\n const { adapter, credentials } = await resolveAdapterAndCredentials(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n )\n\n const status = await adapter.getStatus({\n sessionId: readProviderSessionId(transaction),\n credentials,\n })\n\n if (status.status !== transaction.unifiedStatus && isValidTransition(transaction.unifiedStatus as UnifiedPaymentStatus, status.status)) {\n const previousStatus = transaction.unifiedStatus\n transaction.unifiedStatus = status.status\n transaction.gatewayStatus = status.status\n transaction.gatewayMetadata = { ...readGatewayMetadata(transaction.gatewayMetadata), statusResult: status.providerData ?? null }\n transaction.lastPolledAt = new Date()\n await em.flush()\n await emitStatusEvent(status.status, {\n transactionId: transaction.id,\n paymentId: transaction.paymentId,\n providerKey: transaction.providerKey,\n previousStatus,\n organizationId: transaction.organizationId,\n tenantId: transaction.tenantId,\n })\n await writeTransactionLog(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n transaction.id,\n 'info',\n 'Payment status updated by poller',\n {\n previousStatus,\n nextStatus: status.status,\n },\n )\n }\n\n return status\n },\n\n async syncTransactionStatus(transactionId: string, update: {\n unifiedStatus: UnifiedPaymentStatus\n providerStatus?: string\n providerData?: Record<string, unknown>\n webhookEvent?: {\n eventType: string\n idempotencyKey: string\n processed: boolean\n receivedAt?: string\n }\n }, scope: { organizationId: string; tenantId: string }): Promise<void> {\n const transaction = await findTransactionOrThrow(transactionId, scope)\n const currentStatus = transaction.unifiedStatus as UnifiedPaymentStatus\n const canTransition = isValidTransition(currentStatus, update.unifiedStatus)\n const shouldApplyStatus = canTransition && update.unifiedStatus !== currentStatus\n const previousStatus = transaction.unifiedStatus\n if (shouldApplyStatus) {\n transaction.unifiedStatus = update.unifiedStatus\n }\n if (update.providerStatus) {\n transaction.gatewayStatus = update.providerStatus\n }\n if (update.providerData) {\n transaction.gatewayMetadata = { ...readGatewayMetadata(transaction.gatewayMetadata), ...update.providerData }\n }\n if (update.webhookEvent) {\n const webhookLog = readWebhookLog(transaction.webhookLog)\n webhookLog.push({\n eventType: update.webhookEvent.eventType,\n receivedAt: update.webhookEvent.receivedAt ?? new Date().toISOString(),\n idempotencyKey: update.webhookEvent.idempotencyKey,\n unifiedStatus: update.unifiedStatus,\n processed: update.webhookEvent.processed,\n })\n transaction.webhookLog = webhookLog\n }\n transaction.lastWebhookAt = new Date()\n await em.flush()\n if (shouldApplyStatus) {\n await emitStatusEvent(update.unifiedStatus, {\n transactionId: transaction.id,\n paymentId: transaction.paymentId,\n providerKey: transaction.providerKey,\n previousStatus,\n organizationId: transaction.organizationId,\n tenantId: transaction.tenantId,\n })\n }\n await writeTransactionLog(\n transaction.providerKey,\n { organizationId: transaction.organizationId, tenantId: transaction.tenantId },\n transaction.id,\n shouldApplyStatus ? 'info' : 'warn',\n shouldApplyStatus ? 'Payment status synchronized from webhook' : 'Webhook received with no status transition',\n {\n previousStatus,\n nextStatus: update.unifiedStatus,\n providerStatus: update.providerStatus ?? null,\n eventType: update.webhookEvent?.eventType ?? null,\n idempotencyKey: update.webhookEvent?.idempotencyKey ?? null,\n },\n )\n },\n\n async findTransaction(id: string, scope: { organizationId: string; tenantId: string }): Promise<GatewayTransaction | null> {\n return findOneWithDecryption(\n em,\n GatewayTransaction,\n {\n id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n },\n\n async findTransactionBySessionId(\n providerSessionId: string,\n scope: { organizationId: string; tenantId: string },\n providerKey?: string,\n ): Promise<GatewayTransaction | null> {\n return findOneWithDecryption(\n em,\n GatewayTransaction,\n {\n providerSessionId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n ...(providerKey ? { providerKey } : {}),\n },\n undefined,\n scope,\n )\n },\n\n async listTransactionsForStatusPolling(scope?: {\n organizationId?: string\n tenantId?: string\n providerKey?: string\n limit?: number\n }): Promise<GatewayTransaction[]> {\n const where: Record<string, unknown> = {\n unifiedStatus: { $in: ['pending', 'authorized', 'partially_captured'] },\n deletedAt: null,\n }\n if (scope?.organizationId) where.organizationId = scope.organizationId\n if (scope?.tenantId) where.tenantId = scope.tenantId\n if (scope?.providerKey) where.providerKey = scope.providerKey\n\n return findWithDecryption(\n em,\n GatewayTransaction,\n where,\n {\n orderBy: { updatedAt: 'asc' },\n limit: scope?.limit ?? 100,\n },\n scope,\n )\n },\n }\n}\n\nexport type PaymentGatewayService = ReturnType<typeof createPaymentGatewayService>\n"],
5
+ "mappings": "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",
6
6
  "names": ["transaction"]
7
7
  }
@@ -0,0 +1,183 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { CrudHttpError, isUniqueViolation } from "@open-mercato/shared/lib/crud/errors";
3
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
+ import { GatewayPaymentOperation } from "../data/entities.js";
5
+ const OPERATION_LEASE_MS = 10 * 60 * 1e3;
6
+ const OPERATION_UNIQUE_CONSTRAINT = "gateway_payment_operations_scope_operation_unique";
7
+ function digest(value) {
8
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex");
9
+ }
10
+ function operationConflict(code, operationId, message) {
11
+ return new CrudHttpError(409, { error: message, code, operationId });
12
+ }
13
+ async function findOperation(em, operationId, scope) {
14
+ return findOneWithDecryption(
15
+ em,
16
+ GatewayPaymentOperation,
17
+ {
18
+ operationId,
19
+ organizationId: scope.organizationId,
20
+ tenantId: scope.tenantId
21
+ },
22
+ void 0,
23
+ scope
24
+ );
25
+ }
26
+ function buildOperationIdentity(input) {
27
+ const requestHash = digest({
28
+ transactionId: input.transactionId,
29
+ providerKey: input.providerKey,
30
+ action: input.action,
31
+ payload: input.payload
32
+ });
33
+ const operationId = input.operationId ?? `auto_${requestHash}`;
34
+ const providerIdempotencyKey = `om-pg-${digest({
35
+ tenantId: input.scope.tenantId,
36
+ organizationId: input.scope.organizationId,
37
+ transactionId: input.transactionId,
38
+ action: input.action,
39
+ operationId
40
+ })}`;
41
+ return { operationId, providerIdempotencyKey, requestHash };
42
+ }
43
+ function claimFields(now) {
44
+ return {
45
+ status: "in_progress",
46
+ attemptToken: randomUUID(),
47
+ leaseExpiresAt: new Date(now.getTime() + OPERATION_LEASE_MS),
48
+ updatedAt: now
49
+ };
50
+ }
51
+ async function resolveExistingOperation(em, existing, identity) {
52
+ if (existing.requestHash !== identity.requestHash) {
53
+ throw operationConflict(
54
+ "payment_operation_conflict",
55
+ identity.operationId,
56
+ "Payment operation id was already used with a different request"
57
+ );
58
+ }
59
+ if (existing.providerIdempotencyKey !== identity.providerIdempotencyKey) {
60
+ throw operationConflict(
61
+ "payment_operation_conflict",
62
+ identity.operationId,
63
+ "Payment operation id resolved to a different provider request"
64
+ );
65
+ }
66
+ if (existing.status === "succeeded" && existing.result) {
67
+ return { kind: "completed", result: existing.result };
68
+ }
69
+ const now = /* @__PURE__ */ new Date();
70
+ const stale = existing.status === "in_progress" && existing.leaseExpiresAt instanceof Date && existing.leaseExpiresAt <= now;
71
+ if (existing.status !== "failed" && !stale) {
72
+ throw operationConflict(
73
+ "payment_operation_in_progress",
74
+ identity.operationId,
75
+ "Payment operation is already in progress"
76
+ );
77
+ }
78
+ const next = claimFields(now);
79
+ const where = existing.status === "failed" ? { id: existing.id, status: "failed", attemptToken: existing.attemptToken } : { id: existing.id, status: "in_progress", attemptToken: existing.attemptToken, leaseExpiresAt: { $lt: now } };
80
+ const claimed = await em.nativeUpdate(
81
+ GatewayPaymentOperation,
82
+ where,
83
+ { ...next, attemptCount: existing.attemptCount + 1 }
84
+ );
85
+ if (claimed !== 1) {
86
+ throw operationConflict(
87
+ "payment_operation_in_progress",
88
+ identity.operationId,
89
+ "Payment operation is already in progress"
90
+ );
91
+ }
92
+ Object.assign(existing, next, { attemptCount: existing.attemptCount + 1 });
93
+ return {
94
+ kind: "claimed",
95
+ claim: {
96
+ record: existing,
97
+ attemptToken: next.attemptToken,
98
+ providerIdempotencyKey: existing.providerIdempotencyKey
99
+ }
100
+ };
101
+ }
102
+ async function preparePaymentOperation(input) {
103
+ const identity = buildOperationIdentity(input);
104
+ const existing = await findOperation(input.em, identity.operationId, input.scope);
105
+ if (existing) {
106
+ return resolveExistingOperation(input.em, existing, identity);
107
+ }
108
+ input.assertInitialAllowed();
109
+ const now = /* @__PURE__ */ new Date();
110
+ const claim = claimFields(now);
111
+ const record = input.em.create(GatewayPaymentOperation, {
112
+ operationId: identity.operationId,
113
+ transactionId: input.transactionId,
114
+ operationType: input.action,
115
+ providerKey: input.providerKey,
116
+ requestHash: identity.requestHash,
117
+ providerIdempotencyKey: identity.providerIdempotencyKey,
118
+ status: claim.status,
119
+ attemptToken: claim.attemptToken,
120
+ attemptCount: 1,
121
+ result: null,
122
+ leaseExpiresAt: claim.leaseExpiresAt,
123
+ organizationId: input.scope.organizationId,
124
+ tenantId: input.scope.tenantId,
125
+ createdAt: now,
126
+ updatedAt: now
127
+ });
128
+ try {
129
+ await input.em.persist(record).flush();
130
+ return {
131
+ kind: "claimed",
132
+ claim: {
133
+ record,
134
+ attemptToken: claim.attemptToken,
135
+ providerIdempotencyKey: identity.providerIdempotencyKey
136
+ }
137
+ };
138
+ } catch (error) {
139
+ if (!isUniqueViolation(error, OPERATION_UNIQUE_CONSTRAINT) && !isUniqueViolation(error)) {
140
+ throw error;
141
+ }
142
+ const winner = await findOperation(input.em, identity.operationId, input.scope);
143
+ if (!winner) throw error;
144
+ return resolveExistingOperation(input.em, winner, identity);
145
+ }
146
+ }
147
+ async function completePaymentOperation(em, claim, result, applyResult) {
148
+ const statusChanged = await em.transactional(async (tx) => {
149
+ const completed = await tx.nativeUpdate(
150
+ GatewayPaymentOperation,
151
+ { id: claim.record.id, status: "in_progress", attemptToken: claim.attemptToken },
152
+ { status: "succeeded", result, leaseExpiresAt: null, updatedAt: /* @__PURE__ */ new Date() }
153
+ );
154
+ if (completed !== 1) {
155
+ throw operationConflict(
156
+ "payment_operation_claim_lost",
157
+ claim.record.operationId,
158
+ "Payment operation claim is no longer active"
159
+ );
160
+ }
161
+ const changed = await applyResult(tx);
162
+ await tx.flush();
163
+ return changed;
164
+ });
165
+ Object.assign(claim.record, { status: "succeeded", result, leaseExpiresAt: null });
166
+ return statusChanged;
167
+ }
168
+ async function failPaymentOperation(em, claim) {
169
+ const failed = await em.nativeUpdate(
170
+ GatewayPaymentOperation,
171
+ { id: claim.record.id, status: "in_progress", attemptToken: claim.attemptToken },
172
+ { status: "failed", leaseExpiresAt: null, updatedAt: /* @__PURE__ */ new Date() }
173
+ );
174
+ if (failed === 1) {
175
+ Object.assign(claim.record, { status: "failed", leaseExpiresAt: null });
176
+ }
177
+ }
178
+ export {
179
+ completePaymentOperation,
180
+ failPaymentOperation,
181
+ preparePaymentOperation
182
+ };
183
+ //# sourceMappingURL=payment-operation-idempotency.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/payment_gateways/lib/payment-operation-idempotency.ts"],
4
+ "sourcesContent": ["import { createHash, randomUUID } from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError, isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { GatewayPaymentOperation } from '../data/entities'\nimport type { ManualGatewayAction } from './status-machine'\n\ntype Scope = { organizationId: string; tenantId: string }\ntype OperationStatus = 'in_progress' | 'succeeded' | 'failed'\n\nconst OPERATION_LEASE_MS = 10 * 60 * 1000\nconst OPERATION_UNIQUE_CONSTRAINT = 'gateway_payment_operations_scope_operation_unique'\n\nexport type ClaimedPaymentOperation = {\n record: GatewayPaymentOperation\n attemptToken: string\n providerIdempotencyKey: string\n}\n\nexport type PreparedPaymentOperation =\n | { kind: 'completed'; result: Record<string, unknown> }\n | { kind: 'claimed'; claim: ClaimedPaymentOperation }\n\ntype PreparePaymentOperationInput = {\n em: EntityManager\n transactionId: string\n providerKey: string\n action: ManualGatewayAction\n operationId?: string\n payload: Record<string, unknown>\n scope: Scope\n assertInitialAllowed: () => void\n}\n\nfunction digest(value: unknown): string {\n return createHash('sha256').update(JSON.stringify(value)).digest('hex')\n}\n\nfunction operationConflict(code: string, operationId: string, message: string): CrudHttpError {\n return new CrudHttpError(409, { error: message, code, operationId })\n}\n\nasync function findOperation(\n em: EntityManager,\n operationId: string,\n scope: Scope,\n): Promise<GatewayPaymentOperation | null> {\n return findOneWithDecryption(\n em,\n GatewayPaymentOperation,\n {\n operationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n },\n undefined,\n scope,\n )\n}\n\nfunction buildOperationIdentity(input: Omit<PreparePaymentOperationInput, 'em' | 'assertInitialAllowed'>) {\n const requestHash = digest({\n transactionId: input.transactionId,\n providerKey: input.providerKey,\n action: input.action,\n payload: input.payload,\n })\n const operationId = input.operationId ?? `auto_${requestHash}`\n const providerIdempotencyKey = `om-pg-${digest({\n tenantId: input.scope.tenantId,\n organizationId: input.scope.organizationId,\n transactionId: input.transactionId,\n action: input.action,\n operationId,\n })}`\n return { operationId, providerIdempotencyKey, requestHash }\n}\n\nfunction claimFields(now: Date) {\n return {\n status: 'in_progress' as OperationStatus,\n attemptToken: randomUUID(),\n leaseExpiresAt: new Date(now.getTime() + OPERATION_LEASE_MS),\n updatedAt: now,\n }\n}\n\nasync function resolveExistingOperation(\n em: EntityManager,\n existing: GatewayPaymentOperation,\n identity: ReturnType<typeof buildOperationIdentity>,\n): Promise<PreparedPaymentOperation> {\n if (existing.requestHash !== identity.requestHash) {\n throw operationConflict(\n 'payment_operation_conflict',\n identity.operationId,\n 'Payment operation id was already used with a different request',\n )\n }\n if (existing.providerIdempotencyKey !== identity.providerIdempotencyKey) {\n throw operationConflict(\n 'payment_operation_conflict',\n identity.operationId,\n 'Payment operation id resolved to a different provider request',\n )\n }\n if (existing.status === 'succeeded' && existing.result) {\n return { kind: 'completed', result: existing.result }\n }\n\n const now = new Date()\n const stale = existing.status === 'in_progress'\n && existing.leaseExpiresAt instanceof Date\n && existing.leaseExpiresAt <= now\n if (existing.status !== 'failed' && !stale) {\n throw operationConflict(\n 'payment_operation_in_progress',\n identity.operationId,\n 'Payment operation is already in progress',\n )\n }\n\n const next = claimFields(now)\n const where = existing.status === 'failed'\n ? { id: existing.id, status: 'failed', attemptToken: existing.attemptToken }\n : { id: existing.id, status: 'in_progress', attemptToken: existing.attemptToken, leaseExpiresAt: { $lt: now } }\n const claimed = await em.nativeUpdate(\n GatewayPaymentOperation,\n where,\n { ...next, attemptCount: existing.attemptCount + 1 },\n )\n if (claimed !== 1) {\n throw operationConflict(\n 'payment_operation_in_progress',\n identity.operationId,\n 'Payment operation is already in progress',\n )\n }\n Object.assign(existing, next, { attemptCount: existing.attemptCount + 1 })\n return {\n kind: 'claimed',\n claim: {\n record: existing,\n attemptToken: next.attemptToken,\n providerIdempotencyKey: existing.providerIdempotencyKey,\n },\n }\n}\n\nexport async function preparePaymentOperation(\n input: PreparePaymentOperationInput,\n): Promise<PreparedPaymentOperation> {\n const identity = buildOperationIdentity(input)\n const existing = await findOperation(input.em, identity.operationId, input.scope)\n if (existing) {\n return resolveExistingOperation(input.em, existing, identity)\n }\n\n input.assertInitialAllowed()\n const now = new Date()\n const claim = claimFields(now)\n const record = input.em.create(GatewayPaymentOperation, {\n operationId: identity.operationId,\n transactionId: input.transactionId,\n operationType: input.action,\n providerKey: input.providerKey,\n requestHash: identity.requestHash,\n providerIdempotencyKey: identity.providerIdempotencyKey,\n status: claim.status,\n attemptToken: claim.attemptToken,\n attemptCount: 1,\n result: null,\n leaseExpiresAt: claim.leaseExpiresAt,\n organizationId: input.scope.organizationId,\n tenantId: input.scope.tenantId,\n createdAt: now,\n updatedAt: now,\n })\n try {\n await input.em.persist(record).flush()\n return {\n kind: 'claimed',\n claim: {\n record,\n attemptToken: claim.attemptToken,\n providerIdempotencyKey: identity.providerIdempotencyKey,\n },\n }\n } catch (error: unknown) {\n if (!isUniqueViolation(error, OPERATION_UNIQUE_CONSTRAINT) && !isUniqueViolation(error)) {\n throw error\n }\n const winner = await findOperation(input.em, identity.operationId, input.scope)\n if (!winner) throw error\n return resolveExistingOperation(input.em, winner, identity)\n }\n}\n\nexport async function completePaymentOperation<T extends Record<string, unknown>>(\n em: EntityManager,\n claim: ClaimedPaymentOperation,\n result: T,\n applyResult: (tx: EntityManager) => Promise<boolean>,\n): Promise<boolean> {\n const statusChanged = await em.transactional(async (tx) => {\n const completed = await tx.nativeUpdate(\n GatewayPaymentOperation,\n { id: claim.record.id, status: 'in_progress', attemptToken: claim.attemptToken },\n { status: 'succeeded', result, leaseExpiresAt: null, updatedAt: new Date() },\n )\n if (completed !== 1) {\n throw operationConflict(\n 'payment_operation_claim_lost',\n claim.record.operationId,\n 'Payment operation claim is no longer active',\n )\n }\n const changed = await applyResult(tx)\n await tx.flush()\n return changed\n })\n Object.assign(claim.record, { status: 'succeeded', result, leaseExpiresAt: null })\n return statusChanged\n}\n\nexport async function failPaymentOperation(\n em: EntityManager,\n claim: ClaimedPaymentOperation,\n): Promise<void> {\n const failed = await em.nativeUpdate(\n GatewayPaymentOperation,\n { id: claim.record.id, status: 'in_progress', attemptToken: claim.attemptToken },\n { status: 'failed', leaseExpiresAt: null, updatedAt: new Date() },\n )\n if (failed === 1) {\n Object.assign(claim.record, { status: 'failed', leaseExpiresAt: null })\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,13 @@
1
+ import { Migration } from "@mikro-orm/migrations";
2
+ class Migration20260709220735_payment_gateways extends Migration {
3
+ up() {
4
+ this.addSql(`create table "gateway_payment_operations" ("id" uuid not null default gen_random_uuid(), "operation_id" text not null, "transaction_id" uuid not null, "operation_type" text not null, "provider_key" text not null, "request_hash" text not null, "provider_idempotency_key" text not null, "status" text not null default 'in_progress', "attempt_token" text not null, "attempt_count" int not null default 1, "result" jsonb null, "lease_expires_at" timestamptz null, "organization_id" uuid not null, "tenant_id" uuid not null, "created_at" timestamptz not null, "updated_at" timestamptz not null, primary key ("id"));`);
5
+ this.addSql(`create index "gateway_payment_operations_status_lease_expires_at_index" on "gateway_payment_operations" ("status", "lease_expires_at");`);
6
+ this.addSql(`create index "gateway_payment_operations_transaction_id_operatio_615c8_index" on "gateway_payment_operations" ("transaction_id", "operation_type", "organization_id", "tenant_id");`);
7
+ this.addSql(`alter table "gateway_payment_operations" add constraint "gateway_payment_operations_scope_operation_unique" unique ("operation_id", "organization_id", "tenant_id");`);
8
+ }
9
+ }
10
+ export {
11
+ Migration20260709220735_payment_gateways
12
+ };
13
+ //# sourceMappingURL=Migration20260709220735_payment_gateways.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/payment_gateways/migrations/Migration20260709220735_payment_gateways.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260709220735_payment_gateways extends Migration {\n\n override up(): void | Promise<void> {\n this.addSql(`create table \"gateway_payment_operations\" (\"id\" uuid not null default gen_random_uuid(), \"operation_id\" text not null, \"transaction_id\" uuid not null, \"operation_type\" text not null, \"provider_key\" text not null, \"request_hash\" text not null, \"provider_idempotency_key\" text not null, \"status\" text not null default 'in_progress', \"attempt_token\" text not null, \"attempt_count\" int not null default 1, \"result\" jsonb null, \"lease_expires_at\" timestamptz null, \"organization_id\" uuid not null, \"tenant_id\" uuid not null, \"created_at\" timestamptz not null, \"updated_at\" timestamptz not null, primary key (\"id\"));`);\n this.addSql(`create index \"gateway_payment_operations_status_lease_expires_at_index\" on \"gateway_payment_operations\" (\"status\", \"lease_expires_at\");`);\n this.addSql(`create index \"gateway_payment_operations_transaction_id_operatio_615c8_index\" on \"gateway_payment_operations\" (\"transaction_id\", \"operation_type\", \"organization_id\", \"tenant_id\");`);\n this.addSql(`alter table \"gateway_payment_operations\" add constraint \"gateway_payment_operations_scope_operation_unique\" unique (\"operation_id\", \"organization_id\", \"tenant_id\");`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,iDAAiD,UAAU;AAAA,EAE7D,KAA2B;AAClC,SAAK,OAAO,omBAAomB;AAChnB,SAAK,OAAO,yIAAyI;AACrJ,SAAK,OAAO,qLAAqL;AACjM,SAAK,OAAO,sKAAsK;AAAA,EACpL;AAEF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ export const id = "id";
2
+ export const operation_id = "operation_id";
3
+ export const transaction_id = "transaction_id";
4
+ export const operation_type = "operation_type";
5
+ export const provider_key = "provider_key";
6
+ export const request_hash = "request_hash";
7
+ export const provider_idempotency_key = "provider_idempotency_key";
8
+ export const status = "status";
9
+ export const attempt_token = "attempt_token";
10
+ export const attempt_count = "attempt_count";
11
+ export const result = "result";
12
+ export const lease_expires_at = "lease_expires_at";
13
+ export const organization_id = "organization_id";
14
+ export const tenant_id = "tenant_id";
15
+ export const created_at = "created_at";
16
+ export const updated_at = "updated_at";
@@ -267,6 +267,7 @@ export const M = {
267
267
  },
268
268
  "payment_gateways": {
269
269
  "gateway_transaction": "payment_gateways:gateway_transaction",
270
+ "gateway_payment_operation": "payment_gateways:gateway_payment_operation",
270
271
  "gateway_session_initialization": "payment_gateways:gateway_session_initialization",
271
272
  "webhook_processed_event": "payment_gateways:webhook_processed_event"
272
273
  },
@@ -1225,6 +1225,24 @@ export const entityFieldsRegistry: Record<string, Record<string, string>> = {
1225
1225
  "updated_at": "updated_at",
1226
1226
  "value": "value"
1227
1227
  },
1228
+ "gateway_payment_operation": {
1229
+ "id": "id",
1230
+ "operation_id": "operation_id",
1231
+ "transaction_id": "transaction_id",
1232
+ "operation_type": "operation_type",
1233
+ "provider_key": "provider_key",
1234
+ "request_hash": "request_hash",
1235
+ "provider_idempotency_key": "provider_idempotency_key",
1236
+ "status": "status",
1237
+ "attempt_token": "attempt_token",
1238
+ "attempt_count": "attempt_count",
1239
+ "result": "result",
1240
+ "lease_expires_at": "lease_expires_at",
1241
+ "organization_id": "organization_id",
1242
+ "tenant_id": "tenant_id",
1243
+ "created_at": "created_at",
1244
+ "updated_at": "updated_at"
1245
+ },
1228
1246
  "gateway_session_initialization": {
1229
1247
  "id": "id",
1230
1248
  "operation_key": "operation_key",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.7-develop.6572.1.3d8e83062f",
3
+ "version": "0.6.7-develop.6574.1.05dcf72ba8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -253,16 +253,16 @@
253
253
  "zod": "^4.4.3"
254
254
  },
255
255
  "peerDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.7-develop.6572.1.3d8e83062f",
257
- "@open-mercato/shared": "0.6.7-develop.6572.1.3d8e83062f",
258
- "@open-mercato/ui": "0.6.7-develop.6572.1.3d8e83062f",
256
+ "@open-mercato/ai-assistant": "0.6.7-develop.6574.1.05dcf72ba8",
257
+ "@open-mercato/shared": "0.6.7-develop.6574.1.05dcf72ba8",
258
+ "@open-mercato/ui": "0.6.7-develop.6574.1.05dcf72ba8",
259
259
  "react": "^19.0.0",
260
260
  "react-dom": "^19.0.0"
261
261
  },
262
262
  "devDependencies": {
263
- "@open-mercato/ai-assistant": "0.6.7-develop.6572.1.3d8e83062f",
264
- "@open-mercato/shared": "0.6.7-develop.6572.1.3d8e83062f",
265
- "@open-mercato/ui": "0.6.7-develop.6572.1.3d8e83062f",
263
+ "@open-mercato/ai-assistant": "0.6.7-develop.6574.1.05dcf72ba8",
264
+ "@open-mercato/shared": "0.6.7-develop.6574.1.05dcf72ba8",
265
+ "@open-mercato/ui": "0.6.7-develop.6574.1.05dcf72ba8",
266
266
  "@testing-library/dom": "^10.4.1",
267
267
  "@testing-library/jest-dom": "^6.9.1",
268
268
  "@testing-library/react": "^16.3.1",
@@ -61,6 +61,7 @@ export async function POST(req: Request) {
61
61
  parsed.data.transactionId,
62
62
  parsed.data.reason,
63
63
  { organizationId: auth.orgId as string, tenantId: auth.tenantId },
64
+ parsed.data.operationId,
64
65
  )
65
66
  await runPaymentGatewayMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
66
67
  tenantId: auth.tenantId,
@@ -61,6 +61,7 @@ export async function POST(req: Request) {
61
61
  parsed.data.transactionId,
62
62
  parsed.data.amount,
63
63
  { organizationId: auth.orgId as string, tenantId: auth.tenantId },
64
+ parsed.data.operationId,
64
65
  )
65
66
  await runPaymentGatewayMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
66
67
  tenantId: auth.tenantId,
@@ -62,6 +62,7 @@ export async function POST(req: Request) {
62
62
  parsed.data.amount,
63
63
  parsed.data.reason,
64
64
  { organizationId: auth.orgId as string, tenantId: auth.tenantId },
65
+ parsed.data.operationId,
65
66
  )
66
67
  await runPaymentGatewayMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
67
68
  tenantId: auth.tenantId,
@@ -75,6 +75,65 @@ export class GatewayTransaction {
75
75
  deletedAt?: Date | null
76
76
  }
77
77
 
78
+ @Entity({ tableName: 'gateway_payment_operations' })
79
+ @Unique({
80
+ name: 'gateway_payment_operations_scope_operation_unique',
81
+ properties: ['operationId', 'organizationId', 'tenantId'],
82
+ })
83
+ @Index({ properties: ['transactionId', 'operationType', 'organizationId', 'tenantId'] })
84
+ @Index({ properties: ['status', 'leaseExpiresAt'] })
85
+ export class GatewayPaymentOperation {
86
+ [OptionalProps]?: 'status' | 'attemptCount' | 'result' | 'leaseExpiresAt' | 'createdAt' | 'updatedAt'
87
+
88
+ @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
89
+ id!: string
90
+
91
+ @Property({ name: 'operation_id', type: 'text' })
92
+ operationId!: string
93
+
94
+ @Property({ name: 'transaction_id', type: 'uuid' })
95
+ transactionId!: string
96
+
97
+ @Property({ name: 'operation_type', type: 'text' })
98
+ operationType!: string
99
+
100
+ @Property({ name: 'provider_key', type: 'text' })
101
+ providerKey!: string
102
+
103
+ @Property({ name: 'request_hash', type: 'text' })
104
+ requestHash!: string
105
+
106
+ @Property({ name: 'provider_idempotency_key', type: 'text' })
107
+ providerIdempotencyKey!: string
108
+
109
+ @Property({ name: 'status', type: 'text' })
110
+ status: string = 'in_progress'
111
+
112
+ @Property({ name: 'attempt_token', type: 'text' })
113
+ attemptToken!: string
114
+
115
+ @Property({ name: 'attempt_count', type: 'integer' })
116
+ attemptCount: number = 1
117
+
118
+ @Property({ name: 'result', type: 'jsonb', nullable: true })
119
+ result?: Record<string, unknown> | null
120
+
121
+ @Property({ name: 'lease_expires_at', type: Date, nullable: true })
122
+ leaseExpiresAt?: Date | null
123
+
124
+ @Property({ name: 'organization_id', type: 'uuid' })
125
+ organizationId!: string
126
+
127
+ @Property({ name: 'tenant_id', type: 'uuid' })
128
+ tenantId!: string
129
+
130
+ @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
131
+ createdAt: Date = new Date()
132
+
133
+ @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })
134
+ updatedAt: Date = new Date()
135
+ }
136
+
78
137
  @Entity({ tableName: 'gateway_session_initializations' })
79
138
  @Unique({
80
139
  name: 'gateway_session_initializations_scope_operation_unique',