@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.
- package/.medusa/server/src/admin/index.js +12 -15
- package/.medusa/server/src/admin/index.mjs +12 -15
- package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/capture-order/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/capture-order/route.js +1 -11
- package/.medusa/server/src/api/store/paypal/capture-order/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/create-order/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/create-order/route.js +0 -9
- package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/webhook/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/webhook/route.js +162 -115
- package/.medusa/server/src/api/store/paypal/webhook/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal-complete/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal-complete/route.js +0 -6
- package/.medusa/server/src/api/store/paypal-complete/route.js.map +1 -1
- package/.medusa/server/src/jobs/paypal-webhook-retry.d.ts.map +1 -1
- package/.medusa/server/src/jobs/paypal-webhook-retry.js +97 -43
- package/.medusa/server/src/jobs/paypal-webhook-retry.js.map +1 -1
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.js +20 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.js.map +1 -0
- package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/service.js +0 -42
- package/.medusa/server/src/modules/paypal/payment-provider/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/service.d.ts +0 -8
- package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/service.js +6 -114
- package/.medusa/server/src/modules/paypal/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/types/config.d.ts +0 -2
- package/.medusa/server/src/modules/paypal/types/config.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/types/config.js +0 -9
- package/.medusa/server/src/modules/paypal/types/config.js.map +1 -1
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts +21 -17
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/webhook-processor.js +195 -99
- package/.medusa/server/src/modules/paypal/webhook-processor.js.map +1 -1
- package/README.md +156 -152
- package/package.json +1 -1
- package/src/admin/routes/settings/paypal/_components/Tabs.tsx +48 -52
- package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +0 -23
- package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +56 -65
- package/src/api/store/paypal/capture-order/route.ts +266 -276
- package/src/api/store/paypal/create-order/route.ts +0 -9
- package/src/api/store/paypal/webhook/route.ts +325 -246
- package/src/api/store/paypal-complete/route.ts +69 -75
- package/src/jobs/paypal-webhook-retry.ts +149 -85
- package/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.ts +17 -0
- package/src/modules/paypal/payment-provider/service.ts +1079 -1121
- package/src/modules/paypal/service.ts +6 -127
- package/src/modules/paypal/types/config.ts +33 -47
- package/src/modules/paypal/webhook-processor.ts +377 -215
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.d.ts +0 -3
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.d.ts.map +0 -1
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.js +0 -9
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.js.map +0 -1
- package/.medusa/server/src/jobs/paypal-reconcile.d.ts +0 -7
- package/.medusa/server/src/jobs/paypal-reconcile.d.ts.map +0 -1
- package/.medusa/server/src/jobs/paypal-reconcile.js +0 -109
- package/.medusa/server/src/jobs/paypal-reconcile.js.map +0 -1
- package/.medusa/server/src/modules/paypal/utils/crypto.d.ts +0 -4
- package/.medusa/server/src/modules/paypal/utils/crypto.d.ts.map +0 -1
- package/.medusa/server/src/modules/paypal/utils/crypto.js +0 -47
- package/.medusa/server/src/modules/paypal/utils/crypto.js.map +0 -1
- package/src/api/admin/paypal/rotate-credentials/route.ts +0 -8
- package/src/jobs/paypal-reconcile.ts +0 -113
- 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,
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return null
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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 {
|