@easypayment/medusa-paypal 0.6.3 → 0.6.4

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 (68) hide show
  1. package/.medusa/server/src/admin/index.js +12 -15
  2. package/.medusa/server/src/admin/index.mjs +12 -15
  3. package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.d.ts.map +1 -1
  4. package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.js.map +1 -1
  5. package/.medusa/server/src/api/store/paypal/capture-order/route.d.ts.map +1 -1
  6. package/.medusa/server/src/api/store/paypal/capture-order/route.js +1 -11
  7. package/.medusa/server/src/api/store/paypal/capture-order/route.js.map +1 -1
  8. package/.medusa/server/src/api/store/paypal/create-order/route.d.ts.map +1 -1
  9. package/.medusa/server/src/api/store/paypal/create-order/route.js +0 -9
  10. package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -1
  11. package/.medusa/server/src/api/store/paypal/webhook/route.d.ts.map +1 -1
  12. package/.medusa/server/src/api/store/paypal/webhook/route.js +162 -115
  13. package/.medusa/server/src/api/store/paypal/webhook/route.js.map +1 -1
  14. package/.medusa/server/src/api/store/paypal-complete/route.d.ts.map +1 -1
  15. package/.medusa/server/src/api/store/paypal-complete/route.js +0 -6
  16. package/.medusa/server/src/api/store/paypal-complete/route.js.map +1 -1
  17. package/.medusa/server/src/jobs/paypal-webhook-retry.d.ts.map +1 -1
  18. package/.medusa/server/src/jobs/paypal-webhook-retry.js +97 -43
  19. package/.medusa/server/src/jobs/paypal-webhook-retry.js.map +1 -1
  20. package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.d.ts +6 -0
  21. package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.d.ts.map +1 -0
  22. package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.js +20 -0
  23. package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.js.map +1 -0
  24. package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts.map +1 -1
  25. package/.medusa/server/src/modules/paypal/payment-provider/service.js +0 -42
  26. package/.medusa/server/src/modules/paypal/payment-provider/service.js.map +1 -1
  27. package/.medusa/server/src/modules/paypal/service.d.ts +0 -8
  28. package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -1
  29. package/.medusa/server/src/modules/paypal/service.js +6 -114
  30. package/.medusa/server/src/modules/paypal/service.js.map +1 -1
  31. package/.medusa/server/src/modules/paypal/types/config.d.ts +0 -2
  32. package/.medusa/server/src/modules/paypal/types/config.d.ts.map +1 -1
  33. package/.medusa/server/src/modules/paypal/types/config.js +0 -9
  34. package/.medusa/server/src/modules/paypal/types/config.js.map +1 -1
  35. package/.medusa/server/src/modules/paypal/webhook-processor.d.ts +21 -17
  36. package/.medusa/server/src/modules/paypal/webhook-processor.d.ts.map +1 -1
  37. package/.medusa/server/src/modules/paypal/webhook-processor.js +195 -99
  38. package/.medusa/server/src/modules/paypal/webhook-processor.js.map +1 -1
  39. package/README.md +156 -152
  40. package/package.json +1 -1
  41. package/src/admin/routes/settings/paypal/_components/Tabs.tsx +48 -52
  42. package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +0 -23
  43. package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +56 -65
  44. package/src/api/store/paypal/capture-order/route.ts +266 -276
  45. package/src/api/store/paypal/create-order/route.ts +0 -9
  46. package/src/api/store/paypal/webhook/route.ts +325 -246
  47. package/src/api/store/paypal-complete/route.ts +69 -75
  48. package/src/jobs/paypal-webhook-retry.ts +149 -85
  49. package/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.ts +17 -0
  50. package/src/modules/paypal/payment-provider/service.ts +1079 -1121
  51. package/src/modules/paypal/service.ts +6 -127
  52. package/src/modules/paypal/types/config.ts +33 -47
  53. package/src/modules/paypal/webhook-processor.ts +377 -215
  54. package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.d.ts +0 -3
  55. package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.d.ts.map +0 -1
  56. package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.js +0 -9
  57. package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.js.map +0 -1
  58. package/.medusa/server/src/jobs/paypal-reconcile.d.ts +0 -7
  59. package/.medusa/server/src/jobs/paypal-reconcile.d.ts.map +0 -1
  60. package/.medusa/server/src/jobs/paypal-reconcile.js +0 -109
  61. package/.medusa/server/src/jobs/paypal-reconcile.js.map +0 -1
  62. package/.medusa/server/src/modules/paypal/utils/crypto.d.ts +0 -4
  63. package/.medusa/server/src/modules/paypal/utils/crypto.d.ts.map +0 -1
  64. package/.medusa/server/src/modules/paypal/utils/crypto.js +0 -47
  65. package/.medusa/server/src/modules/paypal/utils/crypto.js.map +0 -1
  66. package/src/api/admin/paypal/rotate-credentials/route.ts +0 -8
  67. package/src/jobs/paypal-reconcile.ts +0 -113
  68. package/src/modules/paypal/utils/crypto.ts +0 -51
@@ -1,276 +1,266 @@
1
- import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
2
- import type { IPaymentModuleService } from "@medusajs/framework/types"
3
- import { Modules } from "@medusajs/framework/utils"
4
- import { randomUUID } from "crypto"
5
- import type PayPalModuleService from "../../../../modules/paypal/service"
6
- import { getPayPalAccessToken } from "../../../../modules/paypal/utils/paypal-auth"
7
- import { isPayPalProviderId } from "../../../../modules/paypal/utils/provider-ids"
8
-
9
- type Body = {
10
- cart_id: string
11
- order_id: string
12
- }
13
-
14
- function resolveIdempotencyKey(req: MedusaRequest, suffix: string, fallback: string) {
15
- const header =
16
- req.headers["idempotency-key"] ||
17
- req.headers["Idempotency-Key"] ||
18
- req.headers["x-idempotency-key"] ||
19
- req.headers["X-Idempotency-Key"]
20
- const key = Array.isArray(header) ? header[0] : header
21
- if (key && String(key).trim()) {
22
- return `${String(key).trim()}-${suffix}`
23
- }
24
- return fallback || `pp-${suffix}-${randomUUID()}`
25
- }
26
-
27
- async function findPayPalSessionForCart(
28
- cartId: string,
29
- scope: any
30
- ): Promise<{
31
- session_id: string
32
- session_data: Record<string, any>
33
- session_status: string
34
- } | null> {
35
- try {
36
- const query = scope.resolve("query")
37
- const { data: carts } = await query.graph({
38
- entity: "cart",
39
- fields: [
40
- "id",
41
- "payment_collection.payment_sessions.id",
42
- "payment_collection.payment_sessions.data",
43
- "payment_collection.payment_sessions.status",
44
- "payment_collection.payment_sessions.provider_id",
45
- "payment_collection.payment_sessions.created_at",
46
- ],
47
- filters: { id: cartId },
48
- })
49
- const cart = carts?.[0]
50
- const sessions = cart?.payment_collection?.payment_sessions || []
51
- const session = sessions
52
- .filter((s: any) => isPayPalProviderId(s.provider_id))
53
- .sort(
54
- (a: any, b: any) =>
55
- new Date(b.created_at || 0).getTime() - new Date(a.created_at || 0).getTime()
56
- )[0]
57
-
58
- if (!session) return null
59
-
60
- return {
61
- session_id: session.id,
62
- session_data: (session.data || {}) as Record<string, any>,
63
- session_status: session.status,
64
- }
65
- } catch (e: any) {
66
- console.warn("[PayPal] findPayPalSessionForCart failed:", e?.message)
67
- return null
68
- }
69
- }
70
-
71
- async function updatePayPalSession(
72
- sessionId: string,
73
- status: string,
74
- extraData: Record<string, any>,
75
- scope: any
76
- ): Promise<void> {
77
- try {
78
- const paymentModule = scope.resolve(Modules.PAYMENT) as IPaymentModuleService
79
- const [existing] = await paymentModule.listPaymentSessions({ id: [sessionId] }, { take: 1 })
80
- const mergedData = { ...(existing?.data || {}), ...extraData }
81
- await (paymentModule as any).updatePaymentSession({
82
- id: sessionId,
83
- data: mergedData,
84
- status: status as any,
85
- amount: existing?.amount,
86
- currency_code: existing?.currency_code, // ✅ add this
87
- })
88
- } catch (e: any) {
89
- console.error("[PayPal] updatePayPalSession failed:", e?.message)
90
- }
91
- }
92
-
93
- async function attachPayPalCaptureToSession(
94
- cartId: string,
95
- orderId: string,
96
- capture: any,
97
- scope: any
98
- ) {
99
- try {
100
- const session = await findPayPalSessionForCart(cartId, scope)
101
- if (!session) {
102
- console.warn("[PayPal] attachPayPalCaptureToSession: no session found for cart", cartId)
103
- return
104
- }
105
-
106
- const captureId = capture?.purchase_units?.[0]?.payments?.captures?.[0]?.id || capture?.id
107
-
108
- await updatePayPalSession(
109
- session.session_id,
110
- "captured",
111
- {
112
- paypal: {
113
- ...((session.session_data || {}).paypal || {}),
114
- order_id: orderId,
115
- capture_id: captureId,
116
- capture,
117
- },
118
- },
119
- scope
120
- )
121
-
122
- console.info("[PayPal] session captured via DB:", session.session_id)
123
- } catch {
124
- // ignore
125
- }
126
- }
127
-
128
- async function attachPayPalAuthorizationToSession(
129
- cartId: string,
130
- orderId: string,
131
- authorization: any,
132
- scope: any
133
- ) {
134
- try {
135
- const session = await findPayPalSessionForCart(cartId, scope)
136
- if (!session) {
137
- console.warn("[PayPal] attachPayPalAuthorizationToSession: no session found for cart", cartId)
138
- return
139
- }
140
-
141
- const authorizationId = authorization?.purchase_units?.[0]?.payments?.authorizations?.[0]?.id
142
-
143
- await updatePayPalSession(
144
- session.session_id,
145
- "authorized",
146
- {
147
- paypal: {
148
- ...((session.session_data || {}).paypal || {}),
149
- order_id: orderId,
150
- authorization_id: authorizationId,
151
- authorization,
152
- },
153
- },
154
- scope
155
- )
156
-
157
- console.info("[PayPal] session authorized via DB:", session.session_id)
158
- } catch {
159
- // ignore
160
- }
161
- }
162
-
163
- async function getExistingCapture(cartId: string, orderId: string, scope: any) {
164
- try {
165
- const session = await findPayPalSessionForCart(cartId, scope)
166
- if (!session) return null
167
-
168
- const paypalData = (session.session_data || {}).paypal || {}
169
- const existingOrderId = String(paypalData.order_id || "")
170
- if (existingOrderId && existingOrderId !== orderId) return null
171
- if (paypalData.capture) return paypalData.capture
172
- if (paypalData.capture_id) return { id: paypalData.capture_id }
173
- return null
174
- } catch {
175
- return null
176
- }
177
- }
178
-
179
- export async function POST(req: MedusaRequest, res: MedusaResponse) {
180
- const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
181
- const { scope } = req
182
- let debugId: string | null = null
183
-
184
- try {
185
- const body = (req.body || {}) as Body
186
- const cartId = body.cart_id
187
- const orderId = body.order_id
188
-
189
- if (!cartId || !orderId) {
190
- return res.status(400).json({ message: "cart_id and order_id are required" })
191
- }
192
-
193
- const existingCapture = await getExistingCapture(cartId, orderId, scope)
194
- if (existingCapture) {
195
- return res.json({ capture: existingCapture })
196
- }
197
-
198
- const creds = await paypal.getActiveCredentials()
199
- const { accessToken, base } = await getPayPalAccessToken(creds)
200
- const settings = await paypal.getSettings().catch(() => ({}))
201
- const data =
202
- settings && typeof settings === "object" && "data" in settings
203
- ? ((settings as { data?: Record<string, any> }).data ?? {})
204
- : {}
205
- const additionalSettings = (data.additional_settings || {}) as Record<string, any>
206
- const paymentAction =
207
- typeof additionalSettings.paymentAction === "string"
208
- ? additionalSettings.paymentAction
209
- : "capture"
210
-
211
- const requestId = resolveIdempotencyKey(req, "capture-order", `pp-capture-${orderId}`)
212
- const endpoint =
213
- paymentAction === "authorize"
214
- ? `${base}/v2/checkout/orders/${orderId}/authorize`
215
- : `${base}/v2/checkout/orders/${orderId}/capture`
216
-
217
- const ppResp = await fetch(endpoint, {
218
- method: "POST",
219
- headers: {
220
- Authorization: `Bearer ${accessToken}`,
221
- "Content-Type": "application/json",
222
- "PayPal-Request-Id": requestId,
223
- },
224
- })
225
-
226
- const ppText = await ppResp.text()
227
- debugId = ppResp.headers.get("paypal-debug-id")
228
- if (!ppResp.ok) {
229
- throw new Error(
230
- `PayPal capture error (${ppResp.status}): ${ppText}${debugId ? ` debug_id=${debugId}` : ""}`
231
- )
232
- }
233
-
234
- const payload = JSON.parse(ppText)
235
- console.info("[PayPal] capture-order raw payload:", JSON.stringify(payload, null, 2))
236
- if (paymentAction === "authorize") {
237
- await attachPayPalAuthorizationToSession(cartId, orderId, payload, req.scope)
238
- } else {
239
- await attachPayPalCaptureToSession(cartId, orderId, payload, req.scope)
240
- }
241
-
242
- console.info("[PayPal] capture-order", {
243
- cart_id: cartId,
244
- order_id: orderId,
245
- request_id: requestId,
246
- debug_id: ppResp.headers.get("paypal-debug-id"),
247
- capture_id: payload?.id,
248
- })
249
-
250
- try {
251
- await paypal.recordMetric(
252
- paymentAction === "authorize" ? "authorize_order_success" : "capture_order_success"
253
- )
254
- } catch {
255
- // metrics failure must never affect payment outcome
256
- }
257
-
258
- return paymentAction === "authorize"
259
- ? res.json({ authorization: payload })
260
- : res.json({ capture: payload })
261
- } catch (e: any) {
262
- try {
263
- const body = (req.body || {}) as Body
264
- await paypal.recordAuditEvent("capture_order_failed", {
265
- cart_id: body.cart_id,
266
- order_id: body.order_id,
267
- debug_id: debugId,
268
- message: e?.message || String(e),
269
- })
270
- await paypal.recordMetric("capture_order_failed")
271
- } catch {
272
- // ignore audit logging failures
273
- }
274
- return res.status(500).json({ message: e?.message || "Failed to capture PayPal order" })
275
- }
276
- }
1
+ import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
2
+ import type { IPaymentModuleService } from "@medusajs/framework/types"
3
+ import { Modules } from "@medusajs/framework/utils"
4
+ import { randomUUID } from "crypto"
5
+ import type PayPalModuleService from "../../../../modules/paypal/service"
6
+ import { getPayPalAccessToken } from "../../../../modules/paypal/utils/paypal-auth"
7
+ import { isPayPalProviderId } from "../../../../modules/paypal/utils/provider-ids"
8
+
9
+ type Body = {
10
+ cart_id: string
11
+ order_id: string
12
+ }
13
+
14
+ function resolveIdempotencyKey(req: MedusaRequest, suffix: string, fallback: string) {
15
+ const header =
16
+ req.headers["idempotency-key"] ||
17
+ req.headers["Idempotency-Key"] ||
18
+ req.headers["x-idempotency-key"] ||
19
+ req.headers["X-Idempotency-Key"]
20
+ const key = Array.isArray(header) ? header[0] : header
21
+ if (key && String(key).trim()) {
22
+ return `${String(key).trim()}-${suffix}`
23
+ }
24
+ return fallback || `pp-${suffix}-${randomUUID()}`
25
+ }
26
+
27
+ async function findPayPalSessionForCart(
28
+ cartId: string,
29
+ scope: any
30
+ ): Promise<{
31
+ session_id: string
32
+ session_data: Record<string, any>
33
+ session_status: string
34
+ } | null> {
35
+ try {
36
+ const query = scope.resolve("query")
37
+ const { data: carts } = await query.graph({
38
+ entity: "cart",
39
+ fields: [
40
+ "id",
41
+ "payment_collection.payment_sessions.id",
42
+ "payment_collection.payment_sessions.data",
43
+ "payment_collection.payment_sessions.status",
44
+ "payment_collection.payment_sessions.provider_id",
45
+ "payment_collection.payment_sessions.created_at",
46
+ ],
47
+ filters: { id: cartId },
48
+ })
49
+ const cart = carts?.[0]
50
+ const sessions = cart?.payment_collection?.payment_sessions || []
51
+ const session = sessions
52
+ .filter((s: any) => isPayPalProviderId(s.provider_id))
53
+ .sort(
54
+ (a: any, b: any) =>
55
+ new Date(b.created_at || 0).getTime() - new Date(a.created_at || 0).getTime()
56
+ )[0]
57
+
58
+ if (!session) return null
59
+
60
+ return {
61
+ session_id: session.id,
62
+ session_data: (session.data || {}) as Record<string, any>,
63
+ session_status: session.status,
64
+ }
65
+ } catch (e: any) {
66
+ console.warn("[PayPal] findPayPalSessionForCart failed:", e?.message)
67
+ return null
68
+ }
69
+ }
70
+
71
+ async function updatePayPalSession(
72
+ sessionId: string,
73
+ status: string,
74
+ extraData: Record<string, any>,
75
+ scope: any
76
+ ): Promise<void> {
77
+ try {
78
+ const paymentModule = scope.resolve(Modules.PAYMENT) as IPaymentModuleService
79
+ const [existing] = await paymentModule.listPaymentSessions({ id: [sessionId] }, { take: 1 })
80
+ const mergedData = { ...(existing?.data || {}), ...extraData }
81
+ await (paymentModule as any).updatePaymentSession({
82
+ id: sessionId,
83
+ data: mergedData,
84
+ status: status as any,
85
+ amount: existing?.amount,
86
+ currency_code: existing?.currency_code,
87
+ })
88
+ } catch (e: any) {
89
+ console.error("[PayPal] updatePayPalSession failed:", e?.message)
90
+ }
91
+ }
92
+
93
+ async function attachPayPalCaptureToSession(
94
+ cartId: string,
95
+ orderId: string,
96
+ capture: any,
97
+ scope: any
98
+ ) {
99
+ try {
100
+ const session = await findPayPalSessionForCart(cartId, scope)
101
+ if (!session) {
102
+ console.warn("[PayPal] attachPayPalCaptureToSession: no session found for cart", cartId)
103
+ return
104
+ }
105
+
106
+ const captureId = capture?.purchase_units?.[0]?.payments?.captures?.[0]?.id || capture?.id
107
+
108
+ await updatePayPalSession(
109
+ session.session_id,
110
+ "captured",
111
+ {
112
+ paypal: {
113
+ ...((session.session_data || {}).paypal || {}),
114
+ order_id: orderId,
115
+ capture_id: captureId,
116
+ capture,
117
+ },
118
+ },
119
+ scope
120
+ )
121
+
122
+ } catch {
123
+ // ignore
124
+ }
125
+ }
126
+
127
+ async function attachPayPalAuthorizationToSession(
128
+ cartId: string,
129
+ orderId: string,
130
+ authorization: any,
131
+ scope: any
132
+ ) {
133
+ try {
134
+ const session = await findPayPalSessionForCart(cartId, scope)
135
+ if (!session) {
136
+ console.warn("[PayPal] attachPayPalAuthorizationToSession: no session found for cart", cartId)
137
+ return
138
+ }
139
+
140
+ const authorizationId = authorization?.purchase_units?.[0]?.payments?.authorizations?.[0]?.id
141
+
142
+ await updatePayPalSession(
143
+ session.session_id,
144
+ "authorized",
145
+ {
146
+ paypal: {
147
+ ...((session.session_data || {}).paypal || {}),
148
+ order_id: orderId,
149
+ authorization_id: authorizationId,
150
+ authorization,
151
+ },
152
+ },
153
+ scope
154
+ )
155
+
156
+ } catch {
157
+ // ignore
158
+ }
159
+ }
160
+
161
+ async function getExistingCapture(cartId: string, orderId: string, scope: any) {
162
+ try {
163
+ const session = await findPayPalSessionForCart(cartId, scope)
164
+ if (!session) return null
165
+
166
+ const paypalData = (session.session_data || {}).paypal || {}
167
+ const existingOrderId = String(paypalData.order_id || "")
168
+ if (existingOrderId && existingOrderId !== orderId) return null
169
+ if (paypalData.capture) return paypalData.capture
170
+ if (paypalData.capture_id) return { id: paypalData.capture_id }
171
+ return null
172
+ } catch {
173
+ return null
174
+ }
175
+ }
176
+
177
+ export async function POST(req: MedusaRequest, res: MedusaResponse) {
178
+ const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
179
+ const { scope } = req
180
+ let debugId: string | null = null
181
+
182
+ try {
183
+ const body = (req.body || {}) as Body
184
+ const cartId = body.cart_id
185
+ const orderId = body.order_id
186
+
187
+ if (!cartId || !orderId) {
188
+ return res.status(400).json({ message: "cart_id and order_id are required" })
189
+ }
190
+
191
+ const existingCapture = await getExistingCapture(cartId, orderId, scope)
192
+ if (existingCapture) {
193
+ return res.json({ capture: existingCapture })
194
+ }
195
+
196
+ const creds = await paypal.getActiveCredentials()
197
+ const { accessToken, base } = await getPayPalAccessToken(creds)
198
+ const settings = await paypal.getSettings().catch(() => ({}))
199
+ const data =
200
+ settings && typeof settings === "object" && "data" in settings
201
+ ? ((settings as { data?: Record<string, any> }).data ?? {})
202
+ : {}
203
+ const additionalSettings = (data.additional_settings || {}) as Record<string, any>
204
+ const paymentAction =
205
+ typeof additionalSettings.paymentAction === "string"
206
+ ? additionalSettings.paymentAction
207
+ : "capture"
208
+
209
+ const requestId = resolveIdempotencyKey(req, "capture-order", `pp-capture-${orderId}`)
210
+ const endpoint =
211
+ paymentAction === "authorize"
212
+ ? `${base}/v2/checkout/orders/${orderId}/authorize`
213
+ : `${base}/v2/checkout/orders/${orderId}/capture`
214
+
215
+ const ppResp = await fetch(endpoint, {
216
+ method: "POST",
217
+ headers: {
218
+ Authorization: `Bearer ${accessToken}`,
219
+ "Content-Type": "application/json",
220
+ "PayPal-Request-Id": requestId,
221
+ },
222
+ })
223
+
224
+ const ppText = await ppResp.text()
225
+ debugId = ppResp.headers.get("paypal-debug-id")
226
+ if (!ppResp.ok) {
227
+ throw new Error(
228
+ `PayPal capture error (${ppResp.status}): ${ppText}${debugId ? ` debug_id=${debugId}` : ""}`
229
+ )
230
+ }
231
+
232
+ const payload = JSON.parse(ppText)
233
+ if (paymentAction === "authorize") {
234
+ await attachPayPalAuthorizationToSession(cartId, orderId, payload, req.scope)
235
+ } else {
236
+ await attachPayPalCaptureToSession(cartId, orderId, payload, req.scope)
237
+ }
238
+
239
+
240
+ try {
241
+ await paypal.recordMetric(
242
+ paymentAction === "authorize" ? "authorize_order_success" : "capture_order_success"
243
+ )
244
+ } catch {
245
+ // metrics failure must never affect payment outcome
246
+ }
247
+
248
+ return paymentAction === "authorize"
249
+ ? res.json({ authorization: payload })
250
+ : res.json({ capture: payload })
251
+ } catch (e: any) {
252
+ try {
253
+ const body = (req.body || {}) as Body
254
+ await paypal.recordAuditEvent("capture_order_failed", {
255
+ cart_id: body.cart_id,
256
+ order_id: body.order_id,
257
+ debug_id: debugId,
258
+ message: e?.message || String(e),
259
+ })
260
+ await paypal.recordMetric("capture_order_failed")
261
+ } catch {
262
+ // ignore audit logging failures
263
+ }
264
+ return res.status(500).json({ message: e?.message || "Failed to capture PayPal order" })
265
+ }
266
+ }
@@ -119,9 +119,6 @@ export async function POST(req: MedusaRequest, res: MedusaResponse) {
119
119
  return res.json({ id: existingOrderId })
120
120
  }
121
121
 
122
- /**
123
- * ✅ Medusa v2 cart retrieval via Query Graph
124
- */
125
122
  const query = req.scope.resolve("query")
126
123
 
127
124
  const { data } = await query.graph({
@@ -429,12 +426,6 @@ export async function POST(req: MedusaRequest, res: MedusaResponse) {
429
426
 
430
427
  await attachPayPalOrderToSession(req, cart.id, order.id)
431
428
 
432
- console.info("[PayPal] create-order", {
433
- cart_id: cart.id,
434
- order_id: order.id,
435
- request_id: requestId,
436
- debug_id: ppResp.headers.get("paypal-debug-id"),
437
- })
438
429
  try {
439
430
  await paypal.recordMetric("create_order_success")
440
431
  } catch {