@easypayment/medusa-paypal 0.6.4 → 0.6.6
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 +10 -10
- package/.medusa/server/src/admin/index.mjs +10 -10
- package/.medusa/server/src/api/store/paypal-complete/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal-complete/route.js +46 -18
- package/.medusa/server/src/api/store/paypal-complete/route.js.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts +0 -8
- package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/service.js +129 -131
- package/.medusa/server/src/modules/paypal/payment-provider/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/service.d.ts +0 -46
- package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/service.js +0 -94
- package/.medusa/server/src/modules/paypal/service.js.map +1 -1
- package/package.json +1 -1
- package/src/api/store/paypal-complete/route.ts +58 -23
- package/src/modules/paypal/payment-provider/service.ts +196 -244
- package/src/modules/paypal/service.ts +0 -98
|
@@ -38,7 +38,6 @@ function generateSessionId() {
|
|
|
38
38
|
try {
|
|
39
39
|
return randomUUID()
|
|
40
40
|
} catch {
|
|
41
|
-
// Fallback for environments where randomUUID isn't available
|
|
42
41
|
return `pp_${Date.now()}_${Math.random().toString(16).slice(2)}`
|
|
43
42
|
}
|
|
44
43
|
}
|
|
@@ -64,65 +63,72 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
async resolveSettings() {
|
|
68
|
-
const paypal = this.resolvePayPalService()
|
|
66
|
+
async resolveSettings() {
|
|
67
|
+
const paypal = this.resolvePayPalService()
|
|
69
68
|
if (!paypal) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
} catch {
|
|
82
|
-
return {
|
|
83
|
-
additionalSettings: {} as Record<string, unknown>,
|
|
84
|
-
apiDetails: {} as Record<string, unknown>,
|
|
85
|
-
}
|
|
69
|
+
try {
|
|
70
|
+
const { Pool: _SettingsPool } = require("pg")
|
|
71
|
+
const _sPool = new _SettingsPool({ connectionString: process.env.DATABASE_URL })
|
|
72
|
+
const _sResult = await _sPool
|
|
73
|
+
.query("SELECT data FROM paypal_settings ORDER BY created_at DESC LIMIT 1")
|
|
74
|
+
.finally(() => _sPool.end())
|
|
75
|
+
const _sData = _sResult.rows[0]?.data || {}
|
|
76
|
+
return {
|
|
77
|
+
additionalSettings: (_sData.additional_settings || {}) as Record<string, unknown>,
|
|
78
|
+
apiDetails: (_sData.api_details || {}) as Record<string, unknown>,
|
|
86
79
|
}
|
|
80
|
+
} catch {
|
|
81
|
+
return {
|
|
82
|
+
additionalSettings: {} as Record<string, unknown>,
|
|
83
|
+
apiDetails: {} as Record<string, unknown>,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
87
86
|
}
|
|
88
87
|
const settings = await paypal.getSettings().catch(() => ({}))
|
|
89
|
-
const data =
|
|
88
|
+
const data =
|
|
89
|
+
settings && typeof settings === "object" && "data" in settings
|
|
90
90
|
? ((settings as any).data ?? {})
|
|
91
91
|
: {}
|
|
92
92
|
return {
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
additionalSettings: (data.additional_settings || {}) as Record<string, unknown>,
|
|
94
|
+
apiDetails: (data.api_details || {}) as Record<string, unknown>,
|
|
95
95
|
}
|
|
96
|
-
}
|
|
96
|
+
}
|
|
97
97
|
|
|
98
98
|
private async resolveCurrencyOverride() {
|
|
99
99
|
const { apiDetails } = await this.resolveSettings()
|
|
100
|
-
if (typeof apiDetails.currency_code === "string" && apiDetails.currency_code.trim()) {
|
|
101
|
-
return normalizeCurrencyCode(apiDetails.currency_code)
|
|
100
|
+
if (typeof apiDetails.currency_code === "string" && (apiDetails.currency_code as string).trim()) {
|
|
101
|
+
return normalizeCurrencyCode(apiDetails.currency_code as string)
|
|
102
102
|
}
|
|
103
103
|
return normalizeCurrencyCode(process.env.PAYPAL_CURRENCY || "EUR")
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
private async getPayPalAccessToken() {
|
|
107
107
|
const paypal = this.resolvePayPalService()
|
|
108
108
|
if (!paypal) {
|
|
109
|
-
// Fallback: load credentials directly from paypal_connection table
|
|
110
109
|
const { Pool: _FbPool } = require("pg")
|
|
111
110
|
const _fbPool = new _FbPool({ connectionString: process.env.DATABASE_URL })
|
|
112
|
-
const _fbResult = await _fbPool
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
const _fbResult = await _fbPool
|
|
112
|
+
.query(
|
|
113
|
+
"SELECT metadata, environment, seller_client_id, seller_client_secret FROM paypal_connection WHERE status='connected' ORDER BY created_at DESC LIMIT 1"
|
|
114
|
+
)
|
|
115
|
+
.finally(() => _fbPool.end())
|
|
115
116
|
const _fbRow = _fbResult.rows[0]
|
|
116
117
|
if (!_fbRow) throw new Error("No active PayPal connection found in DB")
|
|
117
118
|
const _fbEnv = _fbRow.environment || "sandbox"
|
|
118
|
-
const _fbCreds =
|
|
119
|
+
const _fbCreds =
|
|
120
|
+
(_fbRow.metadata && _fbRow.metadata.credentials && _fbRow.metadata.credentials[_fbEnv]) || {}
|
|
119
121
|
const _fbId = _fbCreds.client_id || _fbRow.seller_client_id
|
|
120
122
|
const _fbSec = _fbCreds.client_secret || _fbRow.seller_client_secret
|
|
121
|
-
const _fbBase =
|
|
123
|
+
const _fbBase =
|
|
124
|
+
_fbEnv === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com"
|
|
122
125
|
const _fbAuth = Buffer.from(`${_fbId}:${_fbSec}`).toString("base64")
|
|
123
126
|
const _fbResp = await fetch(`${_fbBase}/v1/oauth2/token`, {
|
|
124
127
|
method: "POST",
|
|
125
|
-
headers: {
|
|
128
|
+
headers: {
|
|
129
|
+
Authorization: `Basic ${_fbAuth}`,
|
|
130
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
131
|
+
},
|
|
126
132
|
body: "grant_type=client_credentials",
|
|
127
133
|
})
|
|
128
134
|
const _fbText = await _fbResp.text()
|
|
@@ -155,7 +161,6 @@ async resolveSettings() {
|
|
|
155
161
|
return { accessToken: String(json.access_token), base }
|
|
156
162
|
}
|
|
157
163
|
|
|
158
|
-
|
|
159
164
|
private async getOrderDetails(orderId: string) {
|
|
160
165
|
const { accessToken, base } = await this.getPayPalAccessToken()
|
|
161
166
|
const resp = await fetch(`${base}/v2/checkout/orders/${orderId}`, {
|
|
@@ -174,7 +179,10 @@ async resolveSettings() {
|
|
|
174
179
|
return JSON.parse(text)
|
|
175
180
|
}
|
|
176
181
|
|
|
177
|
-
private getIdempotencyKey(
|
|
182
|
+
private getIdempotencyKey(
|
|
183
|
+
input: { context?: { idempotency_key?: string } },
|
|
184
|
+
suffix: string
|
|
185
|
+
) {
|
|
178
186
|
const key = input?.context?.idempotency_key?.trim()
|
|
179
187
|
if (key) {
|
|
180
188
|
return `${key}-${suffix}`
|
|
@@ -198,47 +206,20 @@ async resolveSettings() {
|
|
|
198
206
|
|
|
199
207
|
private mapCaptureStatus(status?: string) {
|
|
200
208
|
const normalized = String(status || "").toUpperCase()
|
|
201
|
-
if (!normalized)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
return "captured"
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (normalized === "PENDING") {
|
|
210
|
-
return "pending"
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (["DENIED", "DECLINED", "FAILED"].includes(normalized)) {
|
|
214
|
-
return "error"
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (["REFUNDED", "PARTIALLY_REFUNDED", "REVERSED"].includes(normalized)) {
|
|
218
|
-
return "canceled"
|
|
219
|
-
}
|
|
220
|
-
|
|
209
|
+
if (!normalized) return null
|
|
210
|
+
if (normalized === "COMPLETED") return "captured"
|
|
211
|
+
if (normalized === "PENDING") return "pending"
|
|
212
|
+
if (["DENIED", "DECLINED", "FAILED"].includes(normalized)) return "error"
|
|
213
|
+
if (["REFUNDED", "PARTIALLY_REFUNDED", "REVERSED"].includes(normalized)) return "canceled"
|
|
221
214
|
return null
|
|
222
215
|
}
|
|
223
216
|
|
|
224
217
|
private mapAuthorizationStatus(status?: string) {
|
|
225
218
|
const normalized = String(status || "").toUpperCase()
|
|
226
|
-
if (!normalized)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (["CREATED", "APPROVED", "PENDING"].includes(normalized)) {
|
|
231
|
-
return "authorized"
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (["VOIDED", "EXPIRED"].includes(normalized)) {
|
|
235
|
-
return "canceled"
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (["DENIED", "DECLINED", "FAILED"].includes(normalized)) {
|
|
239
|
-
return "error"
|
|
240
|
-
}
|
|
241
|
-
|
|
219
|
+
if (!normalized) return null
|
|
220
|
+
if (["CREATED", "APPROVED", "PENDING"].includes(normalized)) return "authorized"
|
|
221
|
+
if (["VOIDED", "EXPIRED"].includes(normalized)) return "canceled"
|
|
222
|
+
if (["DENIED", "DECLINED", "FAILED"].includes(normalized)) return "error"
|
|
242
223
|
return null
|
|
243
224
|
}
|
|
244
225
|
|
|
@@ -252,87 +233,53 @@ async resolveSettings() {
|
|
|
252
233
|
stack: error.stack,
|
|
253
234
|
cause:
|
|
254
235
|
cause instanceof Error
|
|
255
|
-
? {
|
|
256
|
-
name: cause.name,
|
|
257
|
-
message: cause.message,
|
|
258
|
-
stack: cause.stack,
|
|
259
|
-
}
|
|
236
|
+
? { name: cause.name, message: cause.message, stack: cause.stack }
|
|
260
237
|
: cause,
|
|
261
238
|
}
|
|
262
239
|
}
|
|
263
|
-
|
|
264
|
-
return {
|
|
265
|
-
message: String(error),
|
|
266
|
-
}
|
|
240
|
+
return { message: String(error) }
|
|
267
241
|
}
|
|
268
242
|
|
|
269
243
|
private mapOrderStatus(status?: string) {
|
|
270
244
|
const normalized = String(status || "").toUpperCase()
|
|
271
|
-
if (!normalized)
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (normalized === "APPROVED") {
|
|
280
|
-
return "authorized"
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (["VOIDED", "CANCELLED"].includes(normalized)) {
|
|
284
|
-
return "canceled"
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (["CREATED", "SAVED", "PAYER_ACTION_REQUIRED"].includes(normalized)) {
|
|
288
|
-
return "pending"
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (["FAILED", "EXPIRED"].includes(normalized)) {
|
|
292
|
-
return "error"
|
|
293
|
-
}
|
|
294
|
-
|
|
245
|
+
if (!normalized) return "pending"
|
|
246
|
+
if (normalized === "COMPLETED") return "captured"
|
|
247
|
+
if (normalized === "APPROVED") return "authorized"
|
|
248
|
+
if (["VOIDED", "CANCELLED"].includes(normalized)) return "canceled"
|
|
249
|
+
if (["CREATED", "SAVED", "PAYER_ACTION_REQUIRED"].includes(normalized)) return "pending"
|
|
250
|
+
if (["FAILED", "EXPIRED"].includes(normalized)) return "error"
|
|
295
251
|
return "pending"
|
|
296
252
|
}
|
|
297
253
|
|
|
298
254
|
private async recordFailure(eventType: string, metadata?: Record<string, unknown>) {
|
|
299
255
|
const paypal = this.resolvePayPalService()
|
|
300
|
-
if (!paypal)
|
|
301
|
-
return
|
|
302
|
-
}
|
|
303
|
-
|
|
256
|
+
if (!paypal) return
|
|
304
257
|
try {
|
|
305
258
|
await paypal.recordPaymentLog(eventType, metadata)
|
|
306
259
|
await paypal.recordAuditEvent(eventType, metadata)
|
|
307
260
|
await paypal.recordMetric(eventType)
|
|
308
261
|
} catch {
|
|
309
|
-
// ignore
|
|
262
|
+
// ignore
|
|
310
263
|
}
|
|
311
264
|
}
|
|
312
265
|
|
|
313
266
|
private async recordSuccess(metricName: string) {
|
|
314
267
|
const paypal = this.resolvePayPalService()
|
|
315
|
-
if (!paypal)
|
|
316
|
-
return
|
|
317
|
-
}
|
|
318
|
-
|
|
268
|
+
if (!paypal) return
|
|
319
269
|
try {
|
|
320
270
|
await paypal.recordMetric(metricName)
|
|
321
271
|
} catch {
|
|
322
|
-
// ignore
|
|
272
|
+
// ignore
|
|
323
273
|
}
|
|
324
274
|
}
|
|
325
275
|
|
|
326
276
|
private async recordPaymentEvent(eventType: string, metadata?: Record<string, unknown>) {
|
|
327
277
|
const paypal = this.resolvePayPalService()
|
|
328
|
-
if (!paypal)
|
|
329
|
-
return
|
|
330
|
-
}
|
|
331
|
-
|
|
278
|
+
if (!paypal) return
|
|
332
279
|
try {
|
|
333
280
|
await paypal.recordPaymentLog(eventType, metadata)
|
|
334
281
|
} catch {
|
|
335
|
-
// ignore
|
|
282
|
+
// ignore
|
|
336
283
|
}
|
|
337
284
|
}
|
|
338
285
|
|
|
@@ -341,7 +288,6 @@ async resolveSettings() {
|
|
|
341
288
|
): Promise<CreateAccountHolderOutput> {
|
|
342
289
|
const customerId = input.context?.customer?.id
|
|
343
290
|
const externalId = customerId ? `paypal_${customerId}` : `paypal_${generateSessionId()}`
|
|
344
|
-
|
|
345
291
|
return {
|
|
346
292
|
id: externalId,
|
|
347
293
|
data: {
|
|
@@ -351,13 +297,7 @@ async resolveSettings() {
|
|
|
351
297
|
}
|
|
352
298
|
}
|
|
353
299
|
|
|
354
|
-
|
|
355
|
-
* Create a payment session when the customer selects PayPal.
|
|
356
|
-
* Must return an object containing an `id` and `data`.
|
|
357
|
-
*/
|
|
358
|
-
async initiatePayment(
|
|
359
|
-
input: InitiatePaymentInput
|
|
360
|
-
): Promise<InitiatePaymentOutput> {
|
|
300
|
+
async initiatePayment(input: InitiatePaymentInput): Promise<InitiatePaymentOutput> {
|
|
361
301
|
const providerId = (input.data as Record<string, any> | undefined)?.provider_id
|
|
362
302
|
try {
|
|
363
303
|
const currencyOverride = await this.resolveCurrencyOverride()
|
|
@@ -368,8 +308,6 @@ async resolveSettings() {
|
|
|
368
308
|
currencyCode,
|
|
369
309
|
paypalCurrencyOverride: currencyOverride,
|
|
370
310
|
})
|
|
371
|
-
|
|
372
|
-
|
|
373
311
|
return {
|
|
374
312
|
id: generateSessionId(),
|
|
375
313
|
data: {
|
|
@@ -380,15 +318,6 @@ async resolveSettings() {
|
|
|
380
318
|
},
|
|
381
319
|
}
|
|
382
320
|
} catch (error) {
|
|
383
|
-
console.error("[PayPal] provider initiate failed", {
|
|
384
|
-
provider_id: providerId,
|
|
385
|
-
payment_collection_id: (input.data as Record<string, any> | undefined)
|
|
386
|
-
?.payment_collection_id,
|
|
387
|
-
cart_id: (input.data as Record<string, any> | undefined)?.cart_id,
|
|
388
|
-
amount: input.amount,
|
|
389
|
-
currency_code: input.currency_code,
|
|
390
|
-
error: this.serializeError(error),
|
|
391
|
-
})
|
|
392
321
|
await this.recordFailure("initiate_failed", {
|
|
393
322
|
error: this.serializeError(error),
|
|
394
323
|
currency_code: input.currency_code,
|
|
@@ -409,9 +338,7 @@ async resolveSettings() {
|
|
|
409
338
|
currencyCode,
|
|
410
339
|
paypalCurrencyOverride: currencyOverride,
|
|
411
340
|
})
|
|
412
|
-
|
|
413
341
|
const providerId = (input.data as Record<string, any> | undefined)?.provider_id
|
|
414
|
-
|
|
415
342
|
return {
|
|
416
343
|
data: {
|
|
417
344
|
...(input.data || {}),
|
|
@@ -426,104 +353,152 @@ async resolveSettings() {
|
|
|
426
353
|
input: AuthorizePaymentInput
|
|
427
354
|
): Promise<AuthorizePaymentOutput> {
|
|
428
355
|
const { data, amount, currencyCode } = await this.normalizePaymentData(input)
|
|
429
|
-
|
|
430
356
|
const existingPayPal = (data.paypal || {}) as Record<string, any>
|
|
431
|
-
if (
|
|
432
|
-
existingPayPal.capture_id ||
|
|
433
|
-
existingPayPal.authorization_id ||
|
|
434
|
-
(data as any).authorized_at ||
|
|
435
|
-
(data as any).captured_at
|
|
436
|
-
) {
|
|
437
|
-
const { additionalSettings } = await this.resolveSettings()
|
|
438
|
-
const paymentAction =
|
|
439
|
-
typeof additionalSettings.paymentAction === "string"
|
|
440
|
-
? additionalSettings.paymentAction
|
|
441
|
-
: "capture"
|
|
442
|
-
const returnStatus = paymentAction === "authorize" ? "authorized" : "captured"
|
|
443
|
-
return {
|
|
444
|
-
status: returnStatus,
|
|
445
|
-
data: {
|
|
446
|
-
...(input.data || {}),
|
|
447
|
-
...(paymentAction === "authorize"
|
|
448
|
-
? { authorized_at: new Date().toISOString() }
|
|
449
|
-
: { captured_at: new Date().toISOString() }),
|
|
450
|
-
},
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
357
|
|
|
455
|
-
const requestId = this.getIdempotencyKey(input, "authorize")
|
|
456
|
-
let debugId: string | null = null
|
|
457
358
|
const { additionalSettings } = await this.resolveSettings()
|
|
458
359
|
const paymentActionRaw =
|
|
459
360
|
typeof additionalSettings.paymentAction === "string"
|
|
460
361
|
? additionalSettings.paymentAction
|
|
461
362
|
: "capture"
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
363
|
+
const returnStatus = paymentActionRaw === "authorize" ? "authorized" : "captured"
|
|
364
|
+
const timestampKey = paymentActionRaw === "authorize" ? "authorized_at" : "captured_at"
|
|
365
|
+
|
|
366
|
+
// ── CASE 1: Already processed (has capture_id, authorization_id, or timestamp) ──
|
|
367
|
+
if (
|
|
368
|
+
existingPayPal.capture_id ||
|
|
369
|
+
existingPayPal.authorization_id ||
|
|
370
|
+
(data as any).authorized_at ||
|
|
371
|
+
(data as any).captured_at
|
|
372
|
+
) {
|
|
373
|
+
console.info("[PayPal] authorizePayment: session already processed, returning", returnStatus)
|
|
374
|
+
return {
|
|
375
|
+
status: returnStatus,
|
|
376
|
+
data: {
|
|
377
|
+
...(input.data || {}),
|
|
378
|
+
[timestampKey]: (data as any)[timestampKey] || new Date().toISOString(),
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
}
|
|
470
382
|
|
|
471
|
-
|
|
472
|
-
|
|
383
|
+
// ── CASE 2: Has order_id — fetch current status from PayPal ──────────────
|
|
384
|
+
// This is the KEY fix: capture-order already processed the payment but
|
|
385
|
+
// Medusa's cart/complete workflow calls authorizePayment again with the
|
|
386
|
+
// original session data (before capture-order wrote authorization_id).
|
|
387
|
+
// We fetch the live PayPal order to get the real status.
|
|
388
|
+
const orderId = String(existingPayPal.order_id || data.order_id || "")
|
|
389
|
+
if (orderId) {
|
|
390
|
+
try {
|
|
391
|
+
console.info("[PayPal] authorizePayment: fetching live order status for", orderId)
|
|
392
|
+
const order = await this.getOrderDetails(orderId)
|
|
393
|
+
const capture = order?.purchase_units?.[0]?.payments?.captures?.[0]
|
|
394
|
+
const authorization = order?.purchase_units?.[0]?.payments?.authorizations?.[0]
|
|
395
|
+
|
|
396
|
+
// Order was already captured or authorized by capture-order route
|
|
397
|
+
if (capture?.id || authorization?.id) {
|
|
398
|
+
console.info("[PayPal] authorizePayment: order already processed by PayPal, returning", returnStatus)
|
|
399
|
+
return {
|
|
400
|
+
status: returnStatus,
|
|
401
|
+
data: {
|
|
402
|
+
...(input.data || {}),
|
|
403
|
+
paypal: {
|
|
404
|
+
...existingPayPal,
|
|
405
|
+
order_id: orderId,
|
|
406
|
+
order,
|
|
407
|
+
authorization_id: authorization?.id || existingPayPal.authorization_id,
|
|
408
|
+
capture_id: capture?.id || existingPayPal.capture_id,
|
|
409
|
+
},
|
|
410
|
+
[timestampKey]: new Date().toISOString(),
|
|
411
|
+
},
|
|
412
|
+
}
|
|
413
|
+
}
|
|
473
414
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
415
|
+
// Order exists and is APPROVED — customer completed PayPal flow
|
|
416
|
+
// but capture-order hasn't run yet (edge case). Mark as authorized.
|
|
417
|
+
if (["APPROVED", "CREATED", "SAVED"].includes(String(order?.status || "").toUpperCase())) {
|
|
418
|
+
console.info("[PayPal] authorizePayment: order approved, marking authorized")
|
|
419
|
+
return {
|
|
420
|
+
status: "authorized",
|
|
421
|
+
data: {
|
|
422
|
+
...(input.data || {}),
|
|
423
|
+
paypal: {
|
|
424
|
+
...existingPayPal,
|
|
425
|
+
order_id: orderId,
|
|
426
|
+
order,
|
|
483
427
|
},
|
|
428
|
+
authorized_at: new Date().toISOString(),
|
|
484
429
|
},
|
|
485
|
-
|
|
486
|
-
custom_id: data.session_id || data.cart_id || data.payment_collection_id || undefined,
|
|
430
|
+
}
|
|
487
431
|
}
|
|
432
|
+
} catch (e: any) {
|
|
433
|
+
// Non-fatal — log and fall through to creating a new order below
|
|
434
|
+
console.warn("[PayPal] authorizePayment: order lookup failed:", e?.message)
|
|
435
|
+
}
|
|
436
|
+
}
|
|
488
437
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
438
|
+
// ── CASE 3: No order_id — create a new PayPal order ──────────────────────
|
|
439
|
+
// This handles the rare case where Medusa calls authorizePayment before
|
|
440
|
+
// the frontend has created a PayPal order (e.g. admin-created orders).
|
|
441
|
+
const requestId = this.getIdempotencyKey(input, "authorize")
|
|
442
|
+
let debugId: string | null = null
|
|
443
|
+
const orderIntent = paymentActionRaw === "authorize" ? "AUTHORIZE" : "CAPTURE"
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
const { accessToken, base } = await this.getPayPalAccessToken()
|
|
447
|
+
const value = formatAmountForPayPal(amount, currencyCode || "EUR")
|
|
448
|
+
|
|
449
|
+
const orderPayload = {
|
|
450
|
+
intent: orderIntent,
|
|
451
|
+
purchase_units: [
|
|
452
|
+
{
|
|
453
|
+
reference_id: data.cart_id || data.payment_collection_id || undefined,
|
|
454
|
+
custom_id:
|
|
455
|
+
data.session_id || data.cart_id || data.payment_collection_id || undefined,
|
|
456
|
+
amount: {
|
|
457
|
+
currency_code: currencyCode || "EUR",
|
|
458
|
+
value,
|
|
459
|
+
},
|
|
495
460
|
},
|
|
496
|
-
|
|
497
|
-
|
|
461
|
+
],
|
|
462
|
+
custom_id:
|
|
463
|
+
data.session_id || data.cart_id || data.payment_collection_id || undefined,
|
|
464
|
+
}
|
|
498
465
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
466
|
+
const ppResp = await fetch(`${base}/v2/checkout/orders`, {
|
|
467
|
+
method: "POST",
|
|
468
|
+
headers: {
|
|
469
|
+
Authorization: `Bearer ${accessToken}`,
|
|
470
|
+
"Content-Type": "application/json",
|
|
471
|
+
"PayPal-Request-Id": requestId,
|
|
472
|
+
},
|
|
473
|
+
body: JSON.stringify(orderPayload),
|
|
474
|
+
})
|
|
508
475
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
476
|
+
const ppText = await ppResp.text()
|
|
477
|
+
debugId = ppResp.headers.get("paypal-debug-id")
|
|
478
|
+
if (!ppResp.ok) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
`PayPal create order error (${ppResp.status}): ${ppText}${
|
|
481
|
+
debugId ? ` debug_id=${debugId}` : ""
|
|
482
|
+
}`
|
|
483
|
+
)
|
|
513
484
|
}
|
|
514
485
|
|
|
515
|
-
|
|
486
|
+
const order = JSON.parse(ppText) as Record<string, any>
|
|
487
|
+
const newOrderId = String(order.id || "")
|
|
488
|
+
|
|
489
|
+
if (!order || !newOrderId) {
|
|
516
490
|
throw new Error("Unable to resolve PayPal order details for authorization.")
|
|
517
491
|
}
|
|
518
492
|
|
|
519
493
|
const existingAuthorization =
|
|
520
494
|
order?.purchase_units?.[0]?.payments?.authorizations?.[0] || null
|
|
521
495
|
|
|
496
|
+
let authorization: any = null
|
|
522
497
|
if (existingAuthorization) {
|
|
523
498
|
authorization = order
|
|
524
499
|
} else {
|
|
525
500
|
const authorizeResp = await fetch(
|
|
526
|
-
`${base}/v2/checkout/orders/${
|
|
501
|
+
`${base}/v2/checkout/orders/${newOrderId}/authorize`,
|
|
527
502
|
{
|
|
528
503
|
method: "POST",
|
|
529
504
|
headers: {
|
|
@@ -553,7 +528,7 @@ if (
|
|
|
553
528
|
|
|
554
529
|
await this.recordSuccess("authorize_success")
|
|
555
530
|
await this.recordPaymentEvent("authorize", {
|
|
556
|
-
order_id:
|
|
531
|
+
order_id: newOrderId,
|
|
557
532
|
authorization_id: authorizationId,
|
|
558
533
|
amount,
|
|
559
534
|
currency_code: currencyCode,
|
|
@@ -566,7 +541,7 @@ if (
|
|
|
566
541
|
...(input.data || {}),
|
|
567
542
|
paypal: {
|
|
568
543
|
...((input.data || {}).paypal as Record<string, unknown>),
|
|
569
|
-
order_id:
|
|
544
|
+
order_id: newOrderId,
|
|
570
545
|
order: order || authorization,
|
|
571
546
|
authorization_id: authorizationId,
|
|
572
547
|
authorizations:
|
|
@@ -587,9 +562,7 @@ if (
|
|
|
587
562
|
}
|
|
588
563
|
}
|
|
589
564
|
|
|
590
|
-
async retrievePayment(
|
|
591
|
-
input: RetrievePaymentInput
|
|
592
|
-
): Promise<RetrievePaymentOutput> {
|
|
565
|
+
async retrievePayment(input: RetrievePaymentInput): Promise<RetrievePaymentOutput> {
|
|
593
566
|
const data = (input.data || {}) as Record<string, any>
|
|
594
567
|
const paypalData = (data.paypal || {}) as Record<string, any>
|
|
595
568
|
const orderId = String(paypalData.order_id || data.order_id || "")
|
|
@@ -614,9 +587,7 @@ if (
|
|
|
614
587
|
}
|
|
615
588
|
}
|
|
616
589
|
|
|
617
|
-
async getPaymentStatus(
|
|
618
|
-
input: GetPaymentStatusInput
|
|
619
|
-
): Promise<GetPaymentStatusOutput> {
|
|
590
|
+
async getPaymentStatus(input: GetPaymentStatusInput): Promise<GetPaymentStatusOutput> {
|
|
620
591
|
const data = (input.data || {}) as Record<string, any>
|
|
621
592
|
const paypalData = (data.paypal || {}) as Record<string, any>
|
|
622
593
|
const orderId = String(paypalData.order_id || data.order_id || "")
|
|
@@ -656,9 +627,7 @@ if (
|
|
|
656
627
|
}
|
|
657
628
|
}
|
|
658
629
|
|
|
659
|
-
async capturePayment(
|
|
660
|
-
input: CapturePaymentInput
|
|
661
|
-
): Promise<CapturePaymentOutput> {
|
|
630
|
+
async capturePayment(input: CapturePaymentInput): Promise<CapturePaymentOutput> {
|
|
662
631
|
const data = (input.data || {}) as Record<string, any>
|
|
663
632
|
const paypalData = (data.paypal || {}) as Record<string, any>
|
|
664
633
|
const orderId = String(paypalData.order_id || data.order_id || "")
|
|
@@ -704,9 +673,11 @@ if (
|
|
|
704
673
|
},
|
|
705
674
|
}
|
|
706
675
|
}
|
|
676
|
+
|
|
707
677
|
const resolvedIntent = String(
|
|
708
678
|
order?.intent || paypalData.order?.intent || data.intent || ""
|
|
709
679
|
).toUpperCase()
|
|
680
|
+
|
|
710
681
|
if (!authorizationId && resolvedIntent === "AUTHORIZE") {
|
|
711
682
|
const authorizeResp = await fetch(
|
|
712
683
|
`${base}/v2/checkout/orders/${orderId}/authorize`,
|
|
@@ -782,9 +753,7 @@ if (
|
|
|
782
753
|
const capture = JSON.parse(ppText)
|
|
783
754
|
const captureId =
|
|
784
755
|
capture?.id || capture?.purchase_units?.[0]?.payments?.captures?.[0]?.id
|
|
785
|
-
const existingCaptures = Array.isArray(paypalData.captures)
|
|
786
|
-
? paypalData.captures
|
|
787
|
-
: []
|
|
756
|
+
const existingCaptures = Array.isArray(paypalData.captures) ? paypalData.captures : []
|
|
788
757
|
const captureEntry = {
|
|
789
758
|
id: captureId,
|
|
790
759
|
status: capture?.status,
|
|
@@ -827,9 +796,7 @@ if (
|
|
|
827
796
|
}
|
|
828
797
|
}
|
|
829
798
|
|
|
830
|
-
async refundPayment(
|
|
831
|
-
input: RefundPaymentInput
|
|
832
|
-
): Promise<RefundPaymentOutput> {
|
|
799
|
+
async refundPayment(input: RefundPaymentInput): Promise<RefundPaymentOutput> {
|
|
833
800
|
const data = (input.data || {}) as Record<string, any>
|
|
834
801
|
const paypalData = (data.paypal || {}) as Record<string, any>
|
|
835
802
|
const captureId = String(paypalData.capture_id || data.capture_id || "")
|
|
@@ -936,9 +903,7 @@ if (
|
|
|
936
903
|
}
|
|
937
904
|
}
|
|
938
905
|
|
|
939
|
-
async cancelPayment(
|
|
940
|
-
input: CancelPaymentInput
|
|
941
|
-
): Promise<CancelPaymentOutput> {
|
|
906
|
+
async cancelPayment(input: CancelPaymentInput): Promise<CancelPaymentOutput> {
|
|
942
907
|
const data = (input.data || {}) as Record<string, any>
|
|
943
908
|
const paypalData = (data.paypal || {}) as Record<string, any>
|
|
944
909
|
const orderId = String(paypalData.order_id || data.order_id || "")
|
|
@@ -985,7 +950,6 @@ if (
|
|
|
985
950
|
await this.recordPaymentEvent("void", {
|
|
986
951
|
order_id: orderId,
|
|
987
952
|
authorization_id: authorizationId,
|
|
988
|
-
request_id: requestId,
|
|
989
953
|
})
|
|
990
954
|
} else if (captureId) {
|
|
991
955
|
const { accessToken, base } = await this.getPayPalAccessToken()
|
|
@@ -1024,12 +988,6 @@ if (
|
|
|
1024
988
|
paypalData.refunds = [...existingRefunds, refundEntry]
|
|
1025
989
|
|
|
1026
990
|
await this.recordSuccess("cancel_refund_success")
|
|
1027
|
-
await this.recordPaymentEvent("cancel_refund", {
|
|
1028
|
-
order_id: orderId,
|
|
1029
|
-
capture_id: captureId,
|
|
1030
|
-
refund_id: refund?.id,
|
|
1031
|
-
request_id: requestId,
|
|
1032
|
-
})
|
|
1033
991
|
}
|
|
1034
992
|
|
|
1035
993
|
return {
|
|
@@ -1058,16 +1016,10 @@ if (
|
|
|
1058
1016
|
}
|
|
1059
1017
|
}
|
|
1060
1018
|
|
|
1061
|
-
async deletePayment(
|
|
1062
|
-
_input: DeletePaymentInput
|
|
1063
|
-
): Promise<DeletePaymentOutput> {
|
|
1019
|
+
async deletePayment(_input: DeletePaymentInput): Promise<DeletePaymentOutput> {
|
|
1064
1020
|
return { data: {} }
|
|
1065
1021
|
}
|
|
1066
1022
|
|
|
1067
|
-
/**
|
|
1068
|
-
* Required by AbstractPaymentProvider in Medusa v2.
|
|
1069
|
-
* This is used by /hooks/payment/{identifier}_{providerId}
|
|
1070
|
-
*/
|
|
1071
1023
|
async getWebhookActionAndData(
|
|
1072
1024
|
payload: ProviderWebhookPayload["payload"]
|
|
1073
1025
|
): Promise<WebhookActionResult> {
|