@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.
Files changed (278) hide show
  1. package/dist/generated/entities/carrier_shipment/index.js +37 -0
  2. package/dist/generated/entities/carrier_shipment/index.js.map +7 -0
  3. package/dist/generated/entities/gateway_transaction/index.js +47 -0
  4. package/dist/generated/entities/gateway_transaction/index.js.map +7 -0
  5. package/dist/generated/entities/webhook_processed_event/index.js +17 -0
  6. package/dist/generated/entities/webhook_processed_event/index.js.map +7 -0
  7. package/dist/generated/entities.ids.generated.js +10 -1
  8. package/dist/generated/entities.ids.generated.js.map +2 -2
  9. package/dist/generated/entity-fields-registry.js +6 -0
  10. package/dist/generated/entity-fields-registry.js.map +2 -2
  11. package/dist/modules/data_sync/api/runs/[id]/cancel.js +14 -5
  12. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  13. package/dist/modules/data_sync/backend/data-sync/page.meta.js +2 -2
  14. package/dist/modules/data_sync/backend/data-sync/page.meta.js.map +1 -1
  15. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +37 -12
  16. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
  17. package/dist/modules/directory/api/get/tenants/lookup.js +1 -0
  18. package/dist/modules/directory/api/get/tenants/lookup.js.map +2 -2
  19. package/dist/modules/integrations/api/[id]/route.js +38 -11
  20. package/dist/modules/integrations/api/[id]/route.js.map +2 -2
  21. package/dist/modules/integrations/api/logs/route.js +52 -26
  22. package/dist/modules/integrations/api/logs/route.js.map +2 -2
  23. package/dist/modules/integrations/api/route.js +37 -7
  24. package/dist/modules/integrations/api/route.js.map +2 -2
  25. package/dist/modules/integrations/api/umes-read.js +121 -0
  26. package/dist/modules/integrations/api/umes-read.js.map +7 -0
  27. package/dist/modules/integrations/backend/integrations/[id]/page.js +715 -183
  28. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  29. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +30 -9
  30. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  31. package/dist/modules/integrations/backend/integrations/detail-page-widgets.js +46 -0
  32. package/dist/modules/integrations/backend/integrations/detail-page-widgets.js.map +7 -0
  33. package/dist/modules/integrations/backend/integrations/page.js +78 -62
  34. package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
  35. package/dist/modules/integrations/backend/integrations/page.meta.js +2 -2
  36. package/dist/modules/integrations/backend/integrations/page.meta.js.map +1 -1
  37. package/dist/modules/integrations/setup.js +2 -2
  38. package/dist/modules/integrations/setup.js.map +2 -2
  39. package/dist/modules/payment_gateways/acl.js +12 -0
  40. package/dist/modules/payment_gateways/acl.js.map +7 -0
  41. package/dist/modules/payment_gateways/api/cancel/route.js +55 -0
  42. package/dist/modules/payment_gateways/api/cancel/route.js.map +7 -0
  43. package/dist/modules/payment_gateways/api/capture/route.js +55 -0
  44. package/dist/modules/payment_gateways/api/capture/route.js.map +7 -0
  45. package/dist/modules/payment_gateways/api/interceptors.js +24 -0
  46. package/dist/modules/payment_gateways/api/interceptors.js.map +7 -0
  47. package/dist/modules/payment_gateways/api/openapi.js +5 -0
  48. package/dist/modules/payment_gateways/api/openapi.js.map +7 -0
  49. package/dist/modules/payment_gateways/api/refund/route.js +56 -0
  50. package/dist/modules/payment_gateways/api/refund/route.js.map +7 -0
  51. package/dist/modules/payment_gateways/api/sessions/route.js +74 -0
  52. package/dist/modules/payment_gateways/api/sessions/route.js.map +7 -0
  53. package/dist/modules/payment_gateways/api/status/route.js +66 -0
  54. package/dist/modules/payment_gateways/api/status/route.js.map +7 -0
  55. package/dist/modules/payment_gateways/api/transactions/[id]/route.js +118 -0
  56. package/dist/modules/payment_gateways/api/transactions/[id]/route.js.map +7 -0
  57. package/dist/modules/payment_gateways/api/transactions/route.js +113 -0
  58. package/dist/modules/payment_gateways/api/transactions/route.js.map +7 -0
  59. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +136 -0
  60. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +7 -0
  61. package/dist/modules/payment_gateways/backend/payment-gateways/page.js +496 -0
  62. package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +7 -0
  63. package/dist/modules/payment_gateways/backend/payment-gateways/page.meta.js +23 -0
  64. package/dist/modules/payment_gateways/backend/payment-gateways/page.meta.js.map +7 -0
  65. package/dist/modules/payment_gateways/data/enrichers.js +5 -0
  66. package/dist/modules/payment_gateways/data/enrichers.js.map +7 -0
  67. package/dist/modules/payment_gateways/data/entities.js +131 -0
  68. package/dist/modules/payment_gateways/data/entities.js.map +7 -0
  69. package/dist/modules/payment_gateways/data/validators.js +57 -0
  70. package/dist/modules/payment_gateways/data/validators.js.map +7 -0
  71. package/dist/modules/payment_gateways/di.js +16 -0
  72. package/dist/modules/payment_gateways/di.js.map +7 -0
  73. package/dist/modules/payment_gateways/events.js +21 -0
  74. package/dist/modules/payment_gateways/events.js.map +7 -0
  75. package/dist/modules/payment_gateways/i18n/en.js +6 -0
  76. package/dist/modules/payment_gateways/i18n/en.js.map +7 -0
  77. package/dist/modules/payment_gateways/i18n/pl.js +6 -0
  78. package/dist/modules/payment_gateways/i18n/pl.js.map +7 -0
  79. package/dist/modules/payment_gateways/index.js +9 -0
  80. package/dist/modules/payment_gateways/index.js.map +7 -0
  81. package/dist/modules/payment_gateways/lib/gateway-service.js +378 -0
  82. package/dist/modules/payment_gateways/lib/gateway-service.js.map +7 -0
  83. package/dist/modules/payment_gateways/lib/queue.js +17 -0
  84. package/dist/modules/payment_gateways/lib/queue.js.map +7 -0
  85. package/dist/modules/payment_gateways/lib/status-machine.js +29 -0
  86. package/dist/modules/payment_gateways/lib/status-machine.js.map +7 -0
  87. package/dist/modules/payment_gateways/lib/webhook-processor.js +88 -0
  88. package/dist/modules/payment_gateways/lib/webhook-processor.js.map +7 -0
  89. package/dist/modules/payment_gateways/lib/webhook-utils.js +42 -0
  90. package/dist/modules/payment_gateways/lib/webhook-utils.js.map +7 -0
  91. package/dist/modules/payment_gateways/migrations/Migration20260305122155.js +19 -0
  92. package/dist/modules/payment_gateways/migrations/Migration20260305122155.js.map +7 -0
  93. package/dist/modules/payment_gateways/setup.js +13 -0
  94. package/dist/modules/payment_gateways/setup.js.map +7 -0
  95. package/dist/modules/payment_gateways/widgets/injection-table.js +7 -0
  96. package/dist/modules/payment_gateways/widgets/injection-table.js.map +7 -0
  97. package/dist/modules/payment_gateways/workers/status-poller.js +44 -0
  98. package/dist/modules/payment_gateways/workers/status-poller.js.map +7 -0
  99. package/dist/modules/payment_gateways/workers/webhook-processor.js +20 -0
  100. package/dist/modules/payment_gateways/workers/webhook-processor.js.map +7 -0
  101. package/dist/modules/sales/data/enrichers.js +72 -0
  102. package/dist/modules/sales/data/enrichers.js.map +7 -0
  103. package/dist/modules/sales/lib/makeSalesLineRoute.js +3 -0
  104. package/dist/modules/sales/lib/makeSalesLineRoute.js.map +2 -2
  105. package/dist/modules/sales/widgets/injection/payment-gateway-config-field/widget.js +29 -0
  106. package/dist/modules/sales/widgets/injection/payment-gateway-config-field/widget.js.map +7 -0
  107. package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js +23 -0
  108. package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js.map +7 -0
  109. package/dist/modules/sales/widgets/injection-table.js +13 -1
  110. package/dist/modules/sales/widgets/injection-table.js.map +2 -2
  111. package/dist/modules/shipping_carriers/acl.js +10 -0
  112. package/dist/modules/shipping_carriers/acl.js.map +7 -0
  113. package/dist/modules/shipping_carriers/api/cancel/route.js +55 -0
  114. package/dist/modules/shipping_carriers/api/cancel/route.js.map +7 -0
  115. package/dist/modules/shipping_carriers/api/interceptors.js +21 -0
  116. package/dist/modules/shipping_carriers/api/interceptors.js.map +7 -0
  117. package/dist/modules/shipping_carriers/api/openapi.js +5 -0
  118. package/dist/modules/shipping_carriers/api/openapi.js.map +7 -0
  119. package/dist/modules/shipping_carriers/api/rates/route.js +55 -0
  120. package/dist/modules/shipping_carriers/api/rates/route.js.map +7 -0
  121. package/dist/modules/shipping_carriers/api/shipments/route.js +61 -0
  122. package/dist/modules/shipping_carriers/api/shipments/route.js.map +7 -0
  123. package/dist/modules/shipping_carriers/api/tracking/route.js +58 -0
  124. package/dist/modules/shipping_carriers/api/tracking/route.js.map +7 -0
  125. package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js +119 -0
  126. package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js.map +7 -0
  127. package/dist/modules/shipping_carriers/data/enrichers.js +82 -0
  128. package/dist/modules/shipping_carriers/data/enrichers.js.map +7 -0
  129. package/dist/modules/shipping_carriers/data/entities.js +80 -0
  130. package/dist/modules/shipping_carriers/data/entities.js.map +7 -0
  131. package/dist/modules/shipping_carriers/data/validators.js +49 -0
  132. package/dist/modules/shipping_carriers/data/validators.js.map +7 -0
  133. package/dist/modules/shipping_carriers/di.js +15 -0
  134. package/dist/modules/shipping_carriers/di.js.map +7 -0
  135. package/dist/modules/shipping_carriers/events.js +19 -0
  136. package/dist/modules/shipping_carriers/events.js.map +7 -0
  137. package/dist/modules/shipping_carriers/i18n/en.js +11 -0
  138. package/dist/modules/shipping_carriers/i18n/en.js.map +7 -0
  139. package/dist/modules/shipping_carriers/i18n/pl.js +11 -0
  140. package/dist/modules/shipping_carriers/i18n/pl.js.map +7 -0
  141. package/dist/modules/shipping_carriers/index.js +9 -0
  142. package/dist/modules/shipping_carriers/index.js.map +7 -0
  143. package/dist/modules/shipping_carriers/lib/adapter-registry.js +29 -0
  144. package/dist/modules/shipping_carriers/lib/adapter-registry.js.map +7 -0
  145. package/dist/modules/shipping_carriers/lib/adapter.js +1 -0
  146. package/dist/modules/shipping_carriers/lib/adapter.js.map +7 -0
  147. package/dist/modules/shipping_carriers/lib/queue.js +17 -0
  148. package/dist/modules/shipping_carriers/lib/queue.js.map +7 -0
  149. package/dist/modules/shipping_carriers/lib/shipping-service.js +155 -0
  150. package/dist/modules/shipping_carriers/lib/shipping-service.js.map +7 -0
  151. package/dist/modules/shipping_carriers/lib/status-sync.js +37 -0
  152. package/dist/modules/shipping_carriers/lib/status-sync.js.map +7 -0
  153. package/dist/modules/shipping_carriers/migrations/Migration20260305170000.js +16 -0
  154. package/dist/modules/shipping_carriers/migrations/Migration20260305170000.js.map +7 -0
  155. package/dist/modules/shipping_carriers/setup.js +13 -0
  156. package/dist/modules/shipping_carriers/setup.js.map +7 -0
  157. package/dist/modules/shipping_carriers/widgets/injection/create-shipment-button/widget.js +25 -0
  158. package/dist/modules/shipping_carriers/widgets/injection/create-shipment-button/widget.js.map +7 -0
  159. package/dist/modules/shipping_carriers/widgets/injection/tracking-column/widget.js +23 -0
  160. package/dist/modules/shipping_carriers/widgets/injection/tracking-column/widget.js.map +7 -0
  161. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js +40 -0
  162. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js.map +7 -0
  163. package/dist/modules/shipping_carriers/widgets/injection-table.js +24 -0
  164. package/dist/modules/shipping_carriers/widgets/injection-table.js.map +7 -0
  165. package/dist/modules/shipping_carriers/workers/status-poller.js +21 -0
  166. package/dist/modules/shipping_carriers/workers/status-poller.js.map +7 -0
  167. package/dist/modules/shipping_carriers/workers/webhook-processor.js +54 -0
  168. package/dist/modules/shipping_carriers/workers/webhook-processor.js.map +7 -0
  169. package/dist/modules/translations/api/get/locales.js +1 -0
  170. package/dist/modules/translations/api/get/locales.js.map +2 -2
  171. package/dist/modules/translations/api/put/locales.js +1 -0
  172. package/dist/modules/translations/api/put/locales.js.map +2 -2
  173. package/generated/entities/carrier_shipment/index.ts +17 -0
  174. package/generated/entities/gateway_transaction/index.ts +22 -0
  175. package/generated/entities/webhook_processed_event/index.ts +7 -0
  176. package/generated/entities.ids.generated.ts +10 -1
  177. package/generated/entity-fields-registry.ts +6 -0
  178. package/jest.config.cjs +1 -0
  179. package/package.json +5 -2
  180. package/src/modules/auth/i18n/de.json +1 -0
  181. package/src/modules/auth/i18n/en.json +1 -0
  182. package/src/modules/auth/i18n/es.json +1 -0
  183. package/src/modules/auth/i18n/pl.json +1 -0
  184. package/src/modules/data_sync/api/runs/[id]/cancel.ts +18 -5
  185. package/src/modules/data_sync/backend/data-sync/page.meta.ts +2 -2
  186. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +50 -12
  187. package/src/modules/directory/api/get/tenants/lookup.ts +1 -0
  188. package/src/modules/integrations/AGENTS.md +31 -0
  189. package/src/modules/integrations/api/[id]/route.ts +38 -11
  190. package/src/modules/integrations/api/logs/route.ts +53 -27
  191. package/src/modules/integrations/api/route.ts +31 -1
  192. package/src/modules/integrations/api/umes-read.ts +177 -0
  193. package/src/modules/integrations/backend/integrations/[id]/page.tsx +902 -202
  194. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +43 -9
  195. package/src/modules/integrations/backend/integrations/detail-page-widgets.ts +74 -0
  196. package/src/modules/integrations/backend/integrations/page.meta.ts +2 -2
  197. package/src/modules/integrations/backend/integrations/page.tsx +65 -54
  198. package/src/modules/integrations/i18n/de.json +15 -0
  199. package/src/modules/integrations/i18n/en.json +15 -0
  200. package/src/modules/integrations/i18n/es.json +15 -0
  201. package/src/modules/integrations/i18n/pl.json +15 -0
  202. package/src/modules/integrations/setup.ts +2 -2
  203. package/src/modules/payment_gateways/acl.ts +8 -0
  204. package/src/modules/payment_gateways/api/cancel/route.ts +56 -0
  205. package/src/modules/payment_gateways/api/capture/route.ts +56 -0
  206. package/src/modules/payment_gateways/api/interceptors.ts +22 -0
  207. package/src/modules/payment_gateways/api/openapi.ts +1 -0
  208. package/src/modules/payment_gateways/api/refund/route.ts +57 -0
  209. package/src/modules/payment_gateways/api/sessions/route.ts +76 -0
  210. package/src/modules/payment_gateways/api/status/route.ts +69 -0
  211. package/src/modules/payment_gateways/api/transactions/[id]/route.ts +123 -0
  212. package/src/modules/payment_gateways/api/transactions/route.ts +120 -0
  213. package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +161 -0
  214. package/src/modules/payment_gateways/backend/payment-gateways/page.meta.ts +19 -0
  215. package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +660 -0
  216. package/src/modules/payment_gateways/data/enrichers.ts +8 -0
  217. package/src/modules/payment_gateways/data/entities.ts +106 -0
  218. package/src/modules/payment_gateways/data/validators.ts +67 -0
  219. package/src/modules/payment_gateways/di.ts +26 -0
  220. package/src/modules/payment_gateways/events.ts +17 -0
  221. package/src/modules/payment_gateways/i18n/de.json +77 -0
  222. package/src/modules/payment_gateways/i18n/en.json +77 -0
  223. package/src/modules/payment_gateways/i18n/en.ts +4 -0
  224. package/src/modules/payment_gateways/i18n/es.json +77 -0
  225. package/src/modules/payment_gateways/i18n/pl.json +77 -0
  226. package/src/modules/payment_gateways/i18n/pl.ts +4 -0
  227. package/src/modules/payment_gateways/index.ts +5 -0
  228. package/src/modules/payment_gateways/lib/gateway-service.ts +486 -0
  229. package/src/modules/payment_gateways/lib/queue.ts +19 -0
  230. package/src/modules/payment_gateways/lib/status-machine.ts +28 -0
  231. package/src/modules/payment_gateways/lib/webhook-processor.ts +133 -0
  232. package/src/modules/payment_gateways/lib/webhook-utils.ts +52 -0
  233. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +373 -0
  234. package/src/modules/payment_gateways/migrations/Migration20260305122155.ts +20 -0
  235. package/src/modules/payment_gateways/setup.ts +11 -0
  236. package/src/modules/payment_gateways/widgets/injection-table.ts +9 -0
  237. package/src/modules/payment_gateways/workers/status-poller.ts +58 -0
  238. package/src/modules/payment_gateways/workers/webhook-processor.ts +30 -0
  239. package/src/modules/sales/data/enrichers.ts +120 -0
  240. package/src/modules/sales/lib/makeSalesLineRoute.ts +3 -0
  241. package/src/modules/sales/widgets/injection/payment-gateway-config-field/widget.ts +28 -0
  242. package/src/modules/sales/widgets/injection/payment-gateway-status-column/widget.ts +22 -0
  243. package/src/modules/sales/widgets/injection-table.ts +12 -0
  244. package/src/modules/shipping_carriers/acl.ts +6 -0
  245. package/src/modules/shipping_carriers/api/cancel/route.ts +53 -0
  246. package/src/modules/shipping_carriers/api/interceptors.ts +19 -0
  247. package/src/modules/shipping_carriers/api/openapi.ts +1 -0
  248. package/src/modules/shipping_carriers/api/rates/route.ts +53 -0
  249. package/src/modules/shipping_carriers/api/shipments/route.ts +59 -0
  250. package/src/modules/shipping_carriers/api/tracking/route.ts +56 -0
  251. package/src/modules/shipping_carriers/api/webhook/[provider]/route.ts +134 -0
  252. package/src/modules/shipping_carriers/data/enrichers.ts +89 -0
  253. package/src/modules/shipping_carriers/data/entities.ts +60 -0
  254. package/src/modules/shipping_carriers/data/validators.ts +48 -0
  255. package/src/modules/shipping_carriers/di.ts +20 -0
  256. package/src/modules/shipping_carriers/events.ts +16 -0
  257. package/src/modules/shipping_carriers/i18n/de.json +7 -0
  258. package/src/modules/shipping_carriers/i18n/en.json +7 -0
  259. package/src/modules/shipping_carriers/i18n/en.ts +7 -0
  260. package/src/modules/shipping_carriers/i18n/es.json +7 -0
  261. package/src/modules/shipping_carriers/i18n/pl.json +7 -0
  262. package/src/modules/shipping_carriers/i18n/pl.ts +7 -0
  263. package/src/modules/shipping_carriers/index.ts +5 -0
  264. package/src/modules/shipping_carriers/lib/adapter-registry.ts +33 -0
  265. package/src/modules/shipping_carriers/lib/adapter.ts +93 -0
  266. package/src/modules/shipping_carriers/lib/queue.ts +19 -0
  267. package/src/modules/shipping_carriers/lib/shipping-service.ts +204 -0
  268. package/src/modules/shipping_carriers/lib/status-sync.ts +38 -0
  269. package/src/modules/shipping_carriers/migrations/Migration20260305170000.ts +14 -0
  270. package/src/modules/shipping_carriers/setup.ts +11 -0
  271. package/src/modules/shipping_carriers/widgets/injection/create-shipment-button/widget.ts +24 -0
  272. package/src/modules/shipping_carriers/widgets/injection/tracking-column/widget.ts +22 -0
  273. package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.tsx +44 -0
  274. package/src/modules/shipping_carriers/widgets/injection-table.ts +22 -0
  275. package/src/modules/shipping_carriers/workers/status-poller.ts +33 -0
  276. package/src/modules/shipping_carriers/workers/webhook-processor.ts +79 -0
  277. package/src/modules/translations/api/get/locales.ts +1 -0
  278. 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