@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,56 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
5
|
+
import { captureSchema } from '../../data/validators'
|
|
6
|
+
import type { PaymentGatewayService } from '../../lib/gateway-service'
|
|
7
|
+
import { paymentGatewaysTag } from '../openapi'
|
|
8
|
+
|
|
9
|
+
export const metadata = {
|
|
10
|
+
path: '/payment_gateways/capture',
|
|
11
|
+
POST: { requireAuth: true, requireFeatures: ['payment_gateways.capture'] },
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function POST(req: Request) {
|
|
15
|
+
const auth = await getAuthFromRequest(req)
|
|
16
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
17
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const payload = await readJsonSafe<unknown>(req)
|
|
21
|
+
const parsed = captureSchema.safeParse(payload)
|
|
22
|
+
if (!parsed.success) {
|
|
23
|
+
return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const container = await createRequestContainer()
|
|
27
|
+
const service = container.resolve('paymentGatewayService') as PaymentGatewayService
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const result = await service.capturePayment(
|
|
31
|
+
parsed.data.transactionId,
|
|
32
|
+
parsed.data.amount,
|
|
33
|
+
{ organizationId: auth.orgId as string, tenantId: auth.tenantId },
|
|
34
|
+
)
|
|
35
|
+
return NextResponse.json(result)
|
|
36
|
+
} catch (err: unknown) {
|
|
37
|
+
const message = err instanceof Error ? err.message : 'Capture failed'
|
|
38
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const openApi = {
|
|
43
|
+
tags: [paymentGatewaysTag],
|
|
44
|
+
summary: 'Capture an authorized payment',
|
|
45
|
+
methods: {
|
|
46
|
+
POST: {
|
|
47
|
+
summary: 'Capture payment',
|
|
48
|
+
tags: [paymentGatewaysTag],
|
|
49
|
+
responses: [
|
|
50
|
+
{ status: 200, description: 'Payment captured' },
|
|
51
|
+
{ status: 422, description: 'Invalid payload' },
|
|
52
|
+
{ status: 502, description: 'Gateway provider error' },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ApiInterceptor } from '@open-mercato/shared/lib/crud/api-interceptor'
|
|
2
|
+
import { getGatewayAdapter } from '@open-mercato/shared/modules/payment_gateways/types'
|
|
3
|
+
|
|
4
|
+
export const interceptors: ApiInterceptor[] = [
|
|
5
|
+
{
|
|
6
|
+
id: 'payment_gateways.validate-provider',
|
|
7
|
+
targetRoute: 'sessions',
|
|
8
|
+
methods: ['POST'],
|
|
9
|
+
priority: 100,
|
|
10
|
+
async before(request) {
|
|
11
|
+
const providerKey = request.body?.providerKey
|
|
12
|
+
if (typeof providerKey !== 'string' || providerKey.trim().length === 0) {
|
|
13
|
+
return { ok: false, statusCode: 422, message: 'providerKey is required' }
|
|
14
|
+
}
|
|
15
|
+
const adapter = getGatewayAdapter(providerKey.trim())
|
|
16
|
+
if (!adapter) {
|
|
17
|
+
return { ok: false, statusCode: 422, message: `Unknown payment provider: ${providerKey}` }
|
|
18
|
+
}
|
|
19
|
+
return { ok: true }
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const paymentGatewaysTag = 'PaymentGateways'
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
5
|
+
import { refundSchema } from '../../data/validators'
|
|
6
|
+
import type { PaymentGatewayService } from '../../lib/gateway-service'
|
|
7
|
+
import { paymentGatewaysTag } from '../openapi'
|
|
8
|
+
|
|
9
|
+
export const metadata = {
|
|
10
|
+
path: '/payment_gateways/refund',
|
|
11
|
+
POST: { requireAuth: true, requireFeatures: ['payment_gateways.refund'] },
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function POST(req: Request) {
|
|
15
|
+
const auth = await getAuthFromRequest(req)
|
|
16
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
17
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const payload = await readJsonSafe<unknown>(req)
|
|
21
|
+
const parsed = refundSchema.safeParse(payload)
|
|
22
|
+
if (!parsed.success) {
|
|
23
|
+
return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const container = await createRequestContainer()
|
|
27
|
+
const service = container.resolve('paymentGatewayService') as PaymentGatewayService
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const result = await service.refundPayment(
|
|
31
|
+
parsed.data.transactionId,
|
|
32
|
+
parsed.data.amount,
|
|
33
|
+
parsed.data.reason,
|
|
34
|
+
{ organizationId: auth.orgId as string, tenantId: auth.tenantId },
|
|
35
|
+
)
|
|
36
|
+
return NextResponse.json(result)
|
|
37
|
+
} catch (err: unknown) {
|
|
38
|
+
const message = err instanceof Error ? err.message : 'Refund failed'
|
|
39
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const openApi = {
|
|
44
|
+
tags: [paymentGatewaysTag],
|
|
45
|
+
summary: 'Refund a captured payment',
|
|
46
|
+
methods: {
|
|
47
|
+
POST: {
|
|
48
|
+
summary: 'Refund payment',
|
|
49
|
+
tags: [paymentGatewaysTag],
|
|
50
|
+
responses: [
|
|
51
|
+
{ status: 200, description: 'Payment refunded' },
|
|
52
|
+
{ status: 422, description: 'Invalid payload' },
|
|
53
|
+
{ status: 502, description: 'Gateway provider error' },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
5
|
+
import { createSessionSchema } from '../../data/validators'
|
|
6
|
+
import type { PaymentGatewayService } from '../../lib/gateway-service'
|
|
7
|
+
import { paymentGatewaysTag } from '../openapi'
|
|
8
|
+
|
|
9
|
+
export const metadata = {
|
|
10
|
+
path: '/payment_gateways/sessions',
|
|
11
|
+
POST: { requireAuth: true, requireFeatures: ['payment_gateways.manage'] },
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function POST(req: Request) {
|
|
15
|
+
const auth = await getAuthFromRequest(req)
|
|
16
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
17
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const payload = await readJsonSafe<unknown>(req)
|
|
21
|
+
const parsed = createSessionSchema.safeParse(payload)
|
|
22
|
+
if (!parsed.success) {
|
|
23
|
+
return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const container = await createRequestContainer()
|
|
27
|
+
const service = container.resolve('paymentGatewayService') as PaymentGatewayService
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const { transaction, session } = await service.createPaymentSession({
|
|
31
|
+
providerKey: parsed.data.providerKey,
|
|
32
|
+
paymentId: crypto.randomUUID(),
|
|
33
|
+
orderId: parsed.data.orderId,
|
|
34
|
+
amount: parsed.data.amount,
|
|
35
|
+
currencyCode: parsed.data.currencyCode,
|
|
36
|
+
captureMethod: parsed.data.captureMethod,
|
|
37
|
+
description: parsed.data.description,
|
|
38
|
+
successUrl: parsed.data.successUrl,
|
|
39
|
+
cancelUrl: parsed.data.cancelUrl,
|
|
40
|
+
metadata: parsed.data.metadata,
|
|
41
|
+
organizationId: auth.orgId as string,
|
|
42
|
+
tenantId: auth.tenantId,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return NextResponse.json({
|
|
46
|
+
transactionId: transaction.id,
|
|
47
|
+
sessionId: session.sessionId,
|
|
48
|
+
providerKey: transaction.providerKey,
|
|
49
|
+
clientSecret: session.clientSecret,
|
|
50
|
+
redirectUrl: session.redirectUrl,
|
|
51
|
+
providerData: session.providerData ?? null,
|
|
52
|
+
status: session.status,
|
|
53
|
+
paymentId: transaction.paymentId,
|
|
54
|
+
}, { status: 201 })
|
|
55
|
+
} catch (err: unknown) {
|
|
56
|
+
const message = err instanceof Error ? err.message : 'Failed to create payment session'
|
|
57
|
+
const status = message.includes('No gateway adapter') ? 422 : 502
|
|
58
|
+
return NextResponse.json({ error: message }, { status })
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const openApi = {
|
|
63
|
+
tags: [paymentGatewaysTag],
|
|
64
|
+
summary: 'Create a payment session via a gateway provider',
|
|
65
|
+
methods: {
|
|
66
|
+
POST: {
|
|
67
|
+
summary: 'Create payment session',
|
|
68
|
+
tags: [paymentGatewaysTag],
|
|
69
|
+
responses: [
|
|
70
|
+
{ status: 201, description: 'Payment session created' },
|
|
71
|
+
{ status: 422, description: 'Invalid payload or unknown provider' },
|
|
72
|
+
{ status: 502, description: 'Gateway provider error' },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import type { PaymentGatewayService } from '../../lib/gateway-service'
|
|
5
|
+
import { paymentGatewaysTag } from '../openapi'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
path: '/payment_gateways/status',
|
|
9
|
+
GET: { requireAuth: true, requireFeatures: ['payment_gateways.view'] },
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function GET(req: Request) {
|
|
13
|
+
const auth = await getAuthFromRequest(req)
|
|
14
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
15
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const url = new URL(req.url)
|
|
19
|
+
const transactionId = url.searchParams.get('transactionId')
|
|
20
|
+
if (!transactionId) {
|
|
21
|
+
return NextResponse.json({ error: 'transactionId is required' }, { status: 400 })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const container = await createRequestContainer()
|
|
25
|
+
const service = container.resolve('paymentGatewayService') as PaymentGatewayService
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
|
|
29
|
+
const transaction = await service.findTransaction(transactionId, scope)
|
|
30
|
+
if (!transaction) {
|
|
31
|
+
return NextResponse.json({ error: 'Transaction not found' }, { status: 404 })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const status = await service.getPaymentStatus(transactionId, scope)
|
|
35
|
+
|
|
36
|
+
return NextResponse.json({
|
|
37
|
+
transactionId: transaction.id,
|
|
38
|
+
paymentId: transaction.paymentId,
|
|
39
|
+
providerKey: transaction.providerKey,
|
|
40
|
+
sessionId: transaction.providerSessionId,
|
|
41
|
+
status: status.status,
|
|
42
|
+
gatewayStatus: transaction.gatewayStatus,
|
|
43
|
+
amount: status.amount,
|
|
44
|
+
amountReceived: status.amountReceived,
|
|
45
|
+
currencyCode: status.currencyCode,
|
|
46
|
+
redirectUrl: transaction.redirectUrl,
|
|
47
|
+
createdAt: transaction.createdAt.toISOString(),
|
|
48
|
+
updatedAt: transaction.updatedAt.toISOString(),
|
|
49
|
+
})
|
|
50
|
+
} catch (err: unknown) {
|
|
51
|
+
const message = err instanceof Error ? err.message : 'Failed to get status'
|
|
52
|
+
return NextResponse.json({ error: message }, { status: 500 })
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const openApi = {
|
|
57
|
+
tags: [paymentGatewaysTag],
|
|
58
|
+
summary: 'Get payment transaction status',
|
|
59
|
+
methods: {
|
|
60
|
+
GET: {
|
|
61
|
+
summary: 'Get transaction status',
|
|
62
|
+
tags: [paymentGatewaysTag],
|
|
63
|
+
responses: [
|
|
64
|
+
{ status: 200, description: 'Transaction status' },
|
|
65
|
+
{ status: 404, description: 'Transaction not found' },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
3
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
4
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
6
|
+
import type { IntegrationLogService } from '../../../../integrations/lib/log-service'
|
|
7
|
+
import { GatewayTransaction } from '../../../data/entities'
|
|
8
|
+
import { paymentGatewaysTag } from '../../openapi'
|
|
9
|
+
|
|
10
|
+
export const metadata = {
|
|
11
|
+
path: '/payment_gateways/transactions/[id]',
|
|
12
|
+
GET: { requireAuth: true, requireFeatures: ['payment_gateways.view'] },
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function toIsoString(value: unknown): string | null {
|
|
16
|
+
if (!value) return null
|
|
17
|
+
if (value instanceof Date) return value.toISOString()
|
|
18
|
+
if (typeof value === 'string') {
|
|
19
|
+
const parsed = new Date(value)
|
|
20
|
+
if (!Number.isNaN(parsed.getTime())) return parsed.toISOString()
|
|
21
|
+
return value
|
|
22
|
+
}
|
|
23
|
+
const parsed = new Date(value as string | number)
|
|
24
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> | { id: string } }) {
|
|
28
|
+
const auth = await getAuthFromRequest(req)
|
|
29
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
30
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const resolvedParams = await params
|
|
34
|
+
const transactionId = resolvedParams?.id
|
|
35
|
+
if (!transactionId) {
|
|
36
|
+
return NextResponse.json({ error: 'Transaction id is required' }, { status: 400 })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { resolve } = await createRequestContainer()
|
|
40
|
+
const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
|
|
41
|
+
const em = resolve('em') as EntityManager
|
|
42
|
+
const integrationLogService = resolve('integrationLogService') as IntegrationLogService
|
|
43
|
+
const transaction = await findOneWithDecryption(
|
|
44
|
+
em,
|
|
45
|
+
GatewayTransaction,
|
|
46
|
+
{
|
|
47
|
+
id: transactionId,
|
|
48
|
+
organizationId: scope.organizationId,
|
|
49
|
+
tenantId: scope.tenantId,
|
|
50
|
+
deletedAt: null,
|
|
51
|
+
},
|
|
52
|
+
undefined,
|
|
53
|
+
scope,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if (!transaction) {
|
|
57
|
+
return NextResponse.json({ error: 'Transaction not found' }, { status: 404 })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const integrationId = `gateway_${transaction.providerKey}`
|
|
61
|
+
const { items: logRows } = await integrationLogService.query(
|
|
62
|
+
{
|
|
63
|
+
integrationId,
|
|
64
|
+
entityType: 'payment_transaction',
|
|
65
|
+
entityId: transactionId,
|
|
66
|
+
page: 1,
|
|
67
|
+
pageSize: 100,
|
|
68
|
+
},
|
|
69
|
+
scope,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return NextResponse.json({
|
|
73
|
+
transaction: {
|
|
74
|
+
id: transaction.id,
|
|
75
|
+
paymentId: transaction.paymentId,
|
|
76
|
+
providerKey: transaction.providerKey,
|
|
77
|
+
providerSessionId: transaction.providerSessionId ?? null,
|
|
78
|
+
gatewayPaymentId: transaction.gatewayPaymentId ?? null,
|
|
79
|
+
gatewayRefundId: transaction.gatewayRefundId ?? null,
|
|
80
|
+
unifiedStatus: transaction.unifiedStatus,
|
|
81
|
+
gatewayStatus: transaction.gatewayStatus ?? null,
|
|
82
|
+
redirectUrl: transaction.redirectUrl ?? null,
|
|
83
|
+
amount: transaction.amount,
|
|
84
|
+
currencyCode: transaction.currencyCode,
|
|
85
|
+
gatewayMetadata: transaction.gatewayMetadata ?? null,
|
|
86
|
+
webhookLog: Array.isArray(transaction.webhookLog) ? transaction.webhookLog : [],
|
|
87
|
+
lastWebhookAt: toIsoString(transaction.lastWebhookAt),
|
|
88
|
+
lastPolledAt: toIsoString(transaction.lastPolledAt),
|
|
89
|
+
expiresAt: toIsoString(transaction.expiresAt),
|
|
90
|
+
createdAt: toIsoString(transaction.createdAt),
|
|
91
|
+
updatedAt: toIsoString(transaction.updatedAt),
|
|
92
|
+
},
|
|
93
|
+
logs: logRows.map((row) => ({
|
|
94
|
+
id: row.id,
|
|
95
|
+
integrationId: row.integrationId,
|
|
96
|
+
runId: row.runId ?? null,
|
|
97
|
+
scopeEntityType: row.scopeEntityType ?? null,
|
|
98
|
+
scopeEntityId: row.scopeEntityId ?? null,
|
|
99
|
+
level: row.level,
|
|
100
|
+
message: row.message,
|
|
101
|
+
code: row.code ?? null,
|
|
102
|
+
payload: row.payload ?? null,
|
|
103
|
+
createdAt: toIsoString(row.createdAt),
|
|
104
|
+
})),
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const openApi = {
|
|
109
|
+
tags: [paymentGatewaysTag],
|
|
110
|
+
summary: 'Get payment transaction details',
|
|
111
|
+
methods: {
|
|
112
|
+
GET: {
|
|
113
|
+
summary: 'Get payment transaction details',
|
|
114
|
+
tags: [paymentGatewaysTag],
|
|
115
|
+
responses: [
|
|
116
|
+
{ status: 200, description: 'Payment transaction details' },
|
|
117
|
+
{ status: 404, description: 'Transaction not found' },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default GET
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
3
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
4
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
|
+
import { GatewayTransaction } from '../../data/entities'
|
|
6
|
+
import { listTransactionsQuerySchema } from '../../data/validators'
|
|
7
|
+
import { paymentGatewaysTag } from '../openapi'
|
|
8
|
+
|
|
9
|
+
export const metadata = {
|
|
10
|
+
path: '/payment_gateways/transactions',
|
|
11
|
+
GET: { requireAuth: true, requireFeatures: ['payment_gateways.view'] },
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function escapeLikePattern(value: string): string {
|
|
15
|
+
return value.replace(/[\\%_]/g, '\\$&')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatDateValue(value: unknown): string | null {
|
|
19
|
+
if (!value) return null
|
|
20
|
+
if (value instanceof Date) return value.toISOString()
|
|
21
|
+
if (typeof value === 'string') {
|
|
22
|
+
const parsed = new Date(value)
|
|
23
|
+
if (!Number.isNaN(parsed.getTime())) return parsed.toISOString()
|
|
24
|
+
return value
|
|
25
|
+
}
|
|
26
|
+
const fallback = new Date(value as string | number)
|
|
27
|
+
return Number.isNaN(fallback.getTime()) ? null : fallback.toISOString()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function GET(req: Request) {
|
|
31
|
+
const auth = await getAuthFromRequest(req)
|
|
32
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
33
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const url = new URL(req.url)
|
|
37
|
+
const parsed = listTransactionsQuerySchema.safeParse(Object.fromEntries(url.searchParams.entries()))
|
|
38
|
+
if (!parsed.success) {
|
|
39
|
+
return NextResponse.json({ error: 'Invalid query', details: parsed.error.flatten() }, { status: 400 })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { page, pageSize, search, providerKey, status } = parsed.data
|
|
43
|
+
const offset = (page - 1) * pageSize
|
|
44
|
+
const { resolve } = await createRequestContainer()
|
|
45
|
+
const em = resolve('em') as EntityManager
|
|
46
|
+
const qb = em.createQueryBuilder(GatewayTransaction, 'gt')
|
|
47
|
+
|
|
48
|
+
qb.where({
|
|
49
|
+
organizationId: auth.orgId,
|
|
50
|
+
tenantId: auth.tenantId,
|
|
51
|
+
deletedAt: null,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
if (providerKey) {
|
|
55
|
+
qb.andWhere({ providerKey })
|
|
56
|
+
}
|
|
57
|
+
if (status) {
|
|
58
|
+
qb.andWhere({ unifiedStatus: status })
|
|
59
|
+
}
|
|
60
|
+
if (search) {
|
|
61
|
+
const pattern = `%${escapeLikePattern(search)}%`
|
|
62
|
+
qb.andWhere(`(
|
|
63
|
+
cast(gt.id as text) ilike ?
|
|
64
|
+
or cast(gt.payment_id as text) ilike ?
|
|
65
|
+
or coalesce(gt.provider_key, '') ilike ?
|
|
66
|
+
or coalesce(gt.provider_session_id, '') ilike ?
|
|
67
|
+
or coalesce(gt.gateway_payment_id, '') ilike ?
|
|
68
|
+
or coalesce(gt.gateway_refund_id, '') ilike ?
|
|
69
|
+
) escape '\\'`, [pattern, pattern, pattern, pattern, pattern, pattern])
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const countQb = qb.clone()
|
|
73
|
+
qb.orderBy({ createdAt: 'desc' })
|
|
74
|
+
qb.limit(pageSize).offset(offset)
|
|
75
|
+
|
|
76
|
+
const [items, total] = await Promise.all([
|
|
77
|
+
qb.getResultList(),
|
|
78
|
+
countQb.count('gt.id', true),
|
|
79
|
+
])
|
|
80
|
+
|
|
81
|
+
return NextResponse.json({
|
|
82
|
+
items: items.map((item) => ({
|
|
83
|
+
id: item.id,
|
|
84
|
+
paymentId: item.paymentId,
|
|
85
|
+
providerKey: item.providerKey,
|
|
86
|
+
providerSessionId: item.providerSessionId ?? null,
|
|
87
|
+
gatewayPaymentId: item.gatewayPaymentId ?? null,
|
|
88
|
+
gatewayRefundId: item.gatewayRefundId ?? null,
|
|
89
|
+
unifiedStatus: item.unifiedStatus,
|
|
90
|
+
gatewayStatus: item.gatewayStatus ?? null,
|
|
91
|
+
amount: item.amount,
|
|
92
|
+
currencyCode: item.currencyCode,
|
|
93
|
+
redirectUrl: item.redirectUrl ?? null,
|
|
94
|
+
lastWebhookAt: formatDateValue(item.lastWebhookAt),
|
|
95
|
+
lastPolledAt: formatDateValue(item.lastPolledAt),
|
|
96
|
+
createdAt: formatDateValue(item.createdAt),
|
|
97
|
+
updatedAt: formatDateValue(item.updatedAt),
|
|
98
|
+
})),
|
|
99
|
+
total,
|
|
100
|
+
page,
|
|
101
|
+
pageSize,
|
|
102
|
+
totalPages: Math.max(1, Math.ceil(total / pageSize)),
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const openApi = {
|
|
107
|
+
tags: [paymentGatewaysTag],
|
|
108
|
+
summary: 'List payment transactions',
|
|
109
|
+
methods: {
|
|
110
|
+
GET: {
|
|
111
|
+
summary: 'List payment transactions',
|
|
112
|
+
tags: [paymentGatewaysTag],
|
|
113
|
+
responses: [
|
|
114
|
+
{ status: 200, description: 'Payment transaction list' },
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export default GET
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
5
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
6
|
+
import { getWebhookHandler } from '@open-mercato/shared/modules/payment_gateways/types'
|
|
7
|
+
import type { IntegrationLogService } from '../../../../integrations/lib/log-service'
|
|
8
|
+
import type { PaymentGatewayService } from '../../../lib/gateway-service'
|
|
9
|
+
import type { CredentialsService } from '../../../../integrations/lib/credentials-service'
|
|
10
|
+
import { GatewayTransaction } from '../../../data/entities'
|
|
11
|
+
import { getPaymentGatewayQueue } from '../../../lib/queue'
|
|
12
|
+
import { processPaymentGatewayWebhookJob } from '../../../lib/webhook-processor'
|
|
13
|
+
import { paymentGatewaysTag } from '../../openapi'
|
|
14
|
+
|
|
15
|
+
export const metadata = {
|
|
16
|
+
path: '/payment_gateways/webhook/[provider]',
|
|
17
|
+
POST: { requireAuth: false },
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readScopeFromEventData(data: Record<string, unknown>): { organizationId: string; tenantId: string } | null {
|
|
21
|
+
const metadata = data.metadata
|
|
22
|
+
if (!metadata || typeof metadata !== 'object') return null
|
|
23
|
+
|
|
24
|
+
const metadataRecord = metadata as Record<string, unknown>
|
|
25
|
+
const organizationId = typeof metadataRecord.organizationId === 'string'
|
|
26
|
+
? metadataRecord.organizationId.trim()
|
|
27
|
+
: ''
|
|
28
|
+
const tenantId = typeof metadataRecord.tenantId === 'string'
|
|
29
|
+
? metadataRecord.tenantId.trim()
|
|
30
|
+
: ''
|
|
31
|
+
|
|
32
|
+
if (!organizationId || !tenantId) return null
|
|
33
|
+
return { organizationId, tenantId }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function POST(req: Request, { params }: { params: Promise<{ provider: string }> | { provider: string } }) {
|
|
37
|
+
const resolvedParams = await params
|
|
38
|
+
const providerKey = resolvedParams.provider
|
|
39
|
+
const container = await createRequestContainer()
|
|
40
|
+
const registration = getWebhookHandler(providerKey)
|
|
41
|
+
if (!registration) {
|
|
42
|
+
return NextResponse.json({ error: `No webhook handler for provider: ${providerKey}` }, { status: 404 })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const rawBody = await req.text()
|
|
46
|
+
const headers: Record<string, string> = {}
|
|
47
|
+
req.headers.forEach((value, key) => {
|
|
48
|
+
headers[key] = value
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const service = container.resolve('paymentGatewayService') as PaymentGatewayService
|
|
52
|
+
const em = container.resolve('em') as EntityManager
|
|
53
|
+
const integrationCredentialsService = container.resolve('integrationCredentialsService') as CredentialsService
|
|
54
|
+
const queue = getPaymentGatewayQueue(registration.queue ?? 'payment-gateways-webhook')
|
|
55
|
+
const payload = await readJsonSafe<Record<string, unknown>>(rawBody)
|
|
56
|
+
const sessionIdHint = registration.readSessionIdHint?.(payload) ?? null
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const candidates = sessionIdHint
|
|
60
|
+
? await findWithDecryption(
|
|
61
|
+
em,
|
|
62
|
+
GatewayTransaction,
|
|
63
|
+
{
|
|
64
|
+
providerKey,
|
|
65
|
+
providerSessionId: sessionIdHint,
|
|
66
|
+
deletedAt: null,
|
|
67
|
+
},
|
|
68
|
+
{ limit: 10, orderBy: { createdAt: 'desc' } },
|
|
69
|
+
)
|
|
70
|
+
: []
|
|
71
|
+
|
|
72
|
+
let transaction = null as GatewayTransaction | null
|
|
73
|
+
let matchedScope = null as { organizationId: string; tenantId: string } | null
|
|
74
|
+
let event = null as Awaited<ReturnType<typeof registration.handler>> | null
|
|
75
|
+
let lastVerificationError: unknown = null
|
|
76
|
+
|
|
77
|
+
for (const candidate of candidates) {
|
|
78
|
+
const candidateScope = { organizationId: candidate.organizationId, tenantId: candidate.tenantId }
|
|
79
|
+
const credentials = await integrationCredentialsService.resolve(`gateway_${providerKey}`, candidateScope) ?? {}
|
|
80
|
+
try {
|
|
81
|
+
event = await registration.handler({ rawBody, headers, credentials })
|
|
82
|
+
transaction = candidate
|
|
83
|
+
matchedScope = candidateScope
|
|
84
|
+
break
|
|
85
|
+
} catch (error: unknown) {
|
|
86
|
+
lastVerificationError = error
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!event) {
|
|
91
|
+
try {
|
|
92
|
+
event = await registration.handler({ rawBody, headers, credentials: {} })
|
|
93
|
+
} catch (error: unknown) {
|
|
94
|
+
throw lastVerificationError ?? error
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!event) {
|
|
98
|
+
throw new Error('Webhook verification failed')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!transaction && sessionIdHint) {
|
|
102
|
+
const derivedScope = readScopeFromEventData(event.data)
|
|
103
|
+
if (derivedScope) {
|
|
104
|
+
transaction = await service.findTransactionBySessionId(sessionIdHint, derivedScope, providerKey)
|
|
105
|
+
matchedScope = transaction
|
|
106
|
+
? { organizationId: transaction.organizationId, tenantId: transaction.tenantId }
|
|
107
|
+
: derivedScope
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const scope = transaction
|
|
112
|
+
? { organizationId: transaction.organizationId, tenantId: transaction.tenantId }
|
|
113
|
+
: matchedScope ?? readScopeFromEventData(event.data)
|
|
114
|
+
|
|
115
|
+
const jobPayload = {
|
|
116
|
+
providerKey,
|
|
117
|
+
event,
|
|
118
|
+
transactionId: transaction?.id ?? null,
|
|
119
|
+
scope,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (process.env.QUEUE_STRATEGY === 'async') {
|
|
123
|
+
await queue.enqueue({
|
|
124
|
+
name: 'payment-gateway-webhook',
|
|
125
|
+
payload: jobPayload,
|
|
126
|
+
})
|
|
127
|
+
} else {
|
|
128
|
+
await processPaymentGatewayWebhookJob(
|
|
129
|
+
{
|
|
130
|
+
em: container.resolve('em') as EntityManager,
|
|
131
|
+
paymentGatewayService: service,
|
|
132
|
+
integrationLogService: container.resolve('integrationLogService') as IntegrationLogService,
|
|
133
|
+
},
|
|
134
|
+
jobPayload,
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return NextResponse.json({ received: true, queued: true }, { status: 202 })
|
|
139
|
+
} catch (err: unknown) {
|
|
140
|
+
const message = err instanceof Error ? err.message : 'Webhook verification failed'
|
|
141
|
+
return NextResponse.json({ error: message }, { status: 401 })
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const openApi = {
|
|
146
|
+
tags: [paymentGatewaysTag],
|
|
147
|
+
summary: 'Receive payment gateway webhook',
|
|
148
|
+
methods: {
|
|
149
|
+
POST: {
|
|
150
|
+
summary: 'Process inbound webhook from payment provider',
|
|
151
|
+
tags: [paymentGatewaysTag],
|
|
152
|
+
responses: [
|
|
153
|
+
{ status: 202, description: 'Webhook accepted for async processing' },
|
|
154
|
+
{ status: 401, description: 'Signature verification failed' },
|
|
155
|
+
{ status: 404, description: 'Unknown provider' },
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export default POST
|