@easypayment/medusa-paypal 0.7.1 → 0.7.3
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 +6 -7
- package/.medusa/server/src/admin/index.mjs +6 -7
- package/.medusa/server/src/api/admin/paypal/onboard-complete/route.d.ts +0 -4
- package/.medusa/server/src/api/admin/paypal/onboard-complete/route.d.ts.map +1 -1
- package/.medusa/server/src/api/admin/paypal/onboard-complete/route.js +0 -10
- package/.medusa/server/src/api/admin/paypal/onboard-complete/route.js.map +1 -1
- package/.medusa/server/src/api/admin/paypal/onboarding-status/route.d.ts.map +1 -1
- package/.medusa/server/src/api/admin/paypal/onboarding-status/route.js +0 -1
- package/.medusa/server/src/api/admin/paypal/onboarding-status/route.js.map +1 -1
- 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 +0 -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 +2 -4
- package/.medusa/server/src/api/store/paypal/capture-order/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/config/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/config/route.js +1 -4
- package/.medusa/server/src/api/store/paypal/config/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 +2 -13
- package/.medusa/server/src/api/store/paypal/create-order/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 -10
- package/.medusa/server/src/api/store/paypal-complete/route.js.map +1 -1
- package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.d.ts +1 -0
- package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.js +2 -0
- package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.js.map +1 -1
- package/.medusa/server/src/modules/paypal/models/paypal_connection.js +2 -2
- package/.medusa/server/src/modules/paypal/models/paypal_connection.js.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/card-service.d.ts +0 -3
- package/.medusa/server/src/modules/paypal/payment-provider/card-service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/card-service.js +8 -4
- package/.medusa/server/src/modules/paypal/payment-provider/card-service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/index.d.ts +0 -4
- package/.medusa/server/src/modules/paypal/payment-provider/index.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/index.js +0 -4
- package/.medusa/server/src/modules/paypal/payment-provider/index.js.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/service.js +11 -24
- package/.medusa/server/src/modules/paypal/payment-provider/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/service.d.ts +1 -0
- package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/service.js +20 -5
- package/.medusa/server/src/modules/paypal/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/types/config.js +2 -2
- package/.medusa/server/src/modules/paypal/types/config.js.map +1 -1
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.d.ts +0 -4
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.js +2 -4
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.js.map +1 -1
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/webhook-processor.js +0 -11
- package/.medusa/server/src/modules/paypal/webhook-processor.js.map +1 -1
- package/.medusa/server/src/providers/paypal/index.d.ts.map +1 -1
- package/.medusa/server/src/providers/paypal/index.js +0 -1
- package/.medusa/server/src/providers/paypal/index.js.map +1 -1
- package/.medusa/server/src/providers/paypal_card/index.d.ts.map +1 -1
- package/.medusa/server/src/providers/paypal_card/index.js +0 -1
- package/.medusa/server/src/providers/paypal_card/index.js.map +1 -1
- package/.medusa/server/src/subscribers/paypal-order-invoice.d.ts.map +1 -1
- package/.medusa/server/src/subscribers/paypal-order-invoice.js +0 -16
- package/.medusa/server/src/subscribers/paypal-order-invoice.js.map +1 -1
- package/README.md +9 -22
- package/package.json +7 -4
- package/src/admin/routes/settings/paypal/additional-settings/page.tsx +0 -1
- package/src/admin/routes/settings/paypal/advanced-card-payments/page.tsx +0 -1
- package/src/admin/routes/settings/paypal/connection/page.tsx +1 -49
- package/src/admin/routes/settings/paypal/page.tsx +13 -17
- package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +0 -2
- package/src/api/admin/paypal/onboard-complete/route.ts +34 -44
- package/src/api/admin/paypal/onboarding-status/route.ts +17 -18
- package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +0 -1
- package/src/api/store/paypal/capture-order/route.ts +3 -4
- package/src/api/store/paypal/config/route.ts +99 -102
- package/src/api/store/paypal/create-order/route.ts +3 -13
- package/src/api/store/paypal-complete/route.ts +0 -10
- package/src/modules/paypal/clients/paypal-seller.client.ts +62 -59
- package/src/modules/paypal/models/paypal_connection.ts +2 -2
- package/src/modules/paypal/payment-provider/card-service.ts +9 -4
- package/src/modules/paypal/payment-provider/index.ts +15 -19
- package/src/modules/paypal/payment-provider/service.ts +12 -24
- package/src/modules/paypal/service.ts +21 -5
- package/src/modules/paypal/types/config.ts +2 -2
- package/src/modules/paypal/utils/paypal-auth.ts +31 -32
- package/src/modules/paypal/webhook-processor.ts +0 -14
- package/src/providers/paypal/index.ts +8 -10
- package/src/providers/paypal_card/index.ts +8 -10
- package/src/subscribers/paypal-order-invoice.ts +0 -16
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
import { ModuleProvider, Modules } from "@medusajs/framework/utils"
|
|
2
|
-
import PayPalPaymentProvider from "./service"
|
|
3
|
-
import PayPalAdvancedCardProvider from "./card-service"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
PayPalPaymentProvider,
|
|
18
|
-
PayPalAdvancedCardProvider,
|
|
19
|
-
}
|
|
1
|
+
import { ModuleProvider, Modules } from "@medusajs/framework/utils"
|
|
2
|
+
import PayPalPaymentProvider from "./service"
|
|
3
|
+
import PayPalAdvancedCardProvider from "./card-service"
|
|
4
|
+
|
|
5
|
+
export default ModuleProvider(Modules.PAYMENT, {
|
|
6
|
+
services: [
|
|
7
|
+
PayPalPaymentProvider,
|
|
8
|
+
PayPalAdvancedCardProvider,
|
|
9
|
+
],
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
PayPalPaymentProvider,
|
|
14
|
+
PayPalAdvancedCardProvider,
|
|
15
|
+
}
|
|
@@ -34,6 +34,8 @@ import { getPayPalWebhookActionAndData } from "./webhook-utils"
|
|
|
34
34
|
|
|
35
35
|
type Options = {}
|
|
36
36
|
|
|
37
|
+
const BN_CODE = "MBJTechnolabs_SI_SPB"
|
|
38
|
+
|
|
37
39
|
function generateSessionId() {
|
|
38
40
|
try {
|
|
39
41
|
return randomUUID()
|
|
@@ -128,6 +130,7 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
128
130
|
headers: {
|
|
129
131
|
Authorization: `Basic ${_fbAuth}`,
|
|
130
132
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
133
|
+
"PayPal-Partner-Attribution-Id": BN_CODE,
|
|
131
134
|
},
|
|
132
135
|
body: "grant_type=client_credentials",
|
|
133
136
|
})
|
|
@@ -148,6 +151,7 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
148
151
|
headers: {
|
|
149
152
|
Authorization: `Basic ${auth}`,
|
|
150
153
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
154
|
+
"PayPal-Partner-Attribution-Id": BN_CODE,
|
|
151
155
|
},
|
|
152
156
|
body: "grant_type=client_credentials",
|
|
153
157
|
})
|
|
@@ -168,6 +172,7 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
168
172
|
headers: {
|
|
169
173
|
Authorization: `Bearer ${accessToken}`,
|
|
170
174
|
"Content-Type": "application/json",
|
|
175
|
+
"PayPal-Partner-Attribution-Id": BN_CODE,
|
|
171
176
|
},
|
|
172
177
|
})
|
|
173
178
|
|
|
@@ -259,7 +264,6 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
259
264
|
await paypal.recordAuditEvent(eventType, metadata)
|
|
260
265
|
await paypal.recordMetric(eventType)
|
|
261
266
|
} catch {
|
|
262
|
-
// ignore
|
|
263
267
|
}
|
|
264
268
|
}
|
|
265
269
|
|
|
@@ -269,7 +273,6 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
269
273
|
try {
|
|
270
274
|
await paypal.recordMetric(metricName)
|
|
271
275
|
} catch {
|
|
272
|
-
// ignore
|
|
273
276
|
}
|
|
274
277
|
}
|
|
275
278
|
|
|
@@ -279,7 +282,6 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
279
282
|
try {
|
|
280
283
|
await paypal.recordPaymentLog(eventType, metadata)
|
|
281
284
|
} catch {
|
|
282
|
-
// ignore
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
287
|
|
|
@@ -363,7 +365,6 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
363
365
|
const returnStatus = paymentActionRaw === "authorize" ? "authorized" : "captured"
|
|
364
366
|
const timestampKey = paymentActionRaw === "authorize" ? "authorized_at" : "captured_at"
|
|
365
367
|
|
|
366
|
-
// ── CASE 1: Already processed (has capture_id, authorization_id, or timestamp) ──
|
|
367
368
|
if (
|
|
368
369
|
existingPayPal.capture_id ||
|
|
369
370
|
existingPayPal.authorization_id ||
|
|
@@ -380,11 +381,6 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
380
381
|
}
|
|
381
382
|
}
|
|
382
383
|
|
|
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
384
|
const orderId = String(existingPayPal.order_id || data.order_id || "")
|
|
389
385
|
if (orderId) {
|
|
390
386
|
try {
|
|
@@ -393,7 +389,6 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
393
389
|
const capture = order?.purchase_units?.[0]?.payments?.captures?.[0]
|
|
394
390
|
const authorization = order?.purchase_units?.[0]?.payments?.authorizations?.[0]
|
|
395
391
|
|
|
396
|
-
// Order was already captured or authorized by capture-order route
|
|
397
392
|
if (capture?.id || authorization?.id) {
|
|
398
393
|
console.info("[PayPal] authorizePayment: order already processed by PayPal, returning", returnStatus)
|
|
399
394
|
return {
|
|
@@ -412,8 +407,6 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
412
407
|
}
|
|
413
408
|
}
|
|
414
409
|
|
|
415
|
-
// Order exists and is APPROVED — customer completed PayPal flow
|
|
416
|
-
// but capture-order hasn't run yet (edge case). Mark as authorized.
|
|
417
410
|
if (["APPROVED", "CREATED", "SAVED"].includes(String(order?.status || "").toUpperCase())) {
|
|
418
411
|
console.info("[PayPal] authorizePayment: order approved, marking authorized")
|
|
419
412
|
return {
|
|
@@ -430,14 +423,10 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
430
423
|
}
|
|
431
424
|
}
|
|
432
425
|
} catch (e: any) {
|
|
433
|
-
// Non-fatal — log and fall through to creating a new order below
|
|
434
426
|
console.warn("[PayPal] authorizePayment: order lookup failed:", e?.message)
|
|
435
427
|
}
|
|
436
428
|
}
|
|
437
429
|
|
|
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
430
|
const requestId = this.getIdempotencyKey(input, "authorize")
|
|
442
431
|
let debugId: string | null = null
|
|
443
432
|
const orderIntent = paymentActionRaw === "authorize" ? "AUTHORIZE" : "CAPTURE"
|
|
@@ -469,6 +458,7 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
469
458
|
Authorization: `Bearer ${accessToken}`,
|
|
470
459
|
"Content-Type": "application/json",
|
|
471
460
|
"PayPal-Request-Id": requestId,
|
|
461
|
+
"PayPal-Partner-Attribution-Id": BN_CODE,
|
|
472
462
|
},
|
|
473
463
|
body: JSON.stringify(orderPayload),
|
|
474
464
|
})
|
|
@@ -505,6 +495,7 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
505
495
|
Authorization: `Bearer ${accessToken}`,
|
|
506
496
|
"Content-Type": "application/json",
|
|
507
497
|
"PayPal-Request-Id": `${requestId}-auth`,
|
|
498
|
+
"PayPal-Partner-Attribution-Id": BN_CODE,
|
|
508
499
|
},
|
|
509
500
|
}
|
|
510
501
|
)
|
|
@@ -687,6 +678,7 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
687
678
|
Authorization: `Bearer ${accessToken}`,
|
|
688
679
|
"Content-Type": "application/json",
|
|
689
680
|
"PayPal-Request-Id": `${requestId}-auth`,
|
|
681
|
+
"PayPal-Partner-Attribution-Id": BN_CODE,
|
|
690
682
|
},
|
|
691
683
|
}
|
|
692
684
|
)
|
|
@@ -737,6 +729,7 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
737
729
|
Authorization: `Bearer ${accessToken}`,
|
|
738
730
|
"Content-Type": "application/json",
|
|
739
731
|
"PayPal-Request-Id": requestId,
|
|
732
|
+
"PayPal-Partner-Attribution-Id": BN_CODE,
|
|
740
733
|
},
|
|
741
734
|
body: JSON.stringify(capturePayload),
|
|
742
735
|
})
|
|
@@ -818,19 +811,11 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
818
811
|
|
|
819
812
|
const requestId = this.getIdempotencyKey(input, `refund-${captureId}`)
|
|
820
813
|
|
|
821
|
-
// ── IMPORTANT: Use input.amount (major units e.g. 20.00) NOT data.amount ──
|
|
822
|
-
// Medusa v2 passes the refund amount via input.amount in major currency units.
|
|
823
|
-
// data.amount is the original session amount in minor units (e.g. 2000 cents).
|
|
824
|
-
// formatAmountForPayPal divides by 100, so using data.amount would give 0.20
|
|
825
|
-
// instead of 20.00. We must use input.amount directly and format it as-is.
|
|
826
814
|
const currencyOverride = await this.resolveCurrencyOverride()
|
|
827
815
|
const currencyCode = normalizeCurrencyCode(
|
|
828
816
|
data.currency_code || currencyOverride || "EUR"
|
|
829
817
|
)
|
|
830
818
|
|
|
831
|
-
// input.amount is already in major units (e.g. 20.00 EUR)
|
|
832
|
-
// Just convert to string with correct decimal places
|
|
833
|
-
// getCurrencyExponent is already available via the amounts utils
|
|
834
819
|
const exponent = currencyCode.toUpperCase() === "JPY" ? 0
|
|
835
820
|
: ["BHD", "JOD", "KWD", "OMR", "TND"].includes(currencyCode.toUpperCase()) ? 3
|
|
836
821
|
: 2
|
|
@@ -861,6 +846,7 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
861
846
|
Authorization: `Bearer ${accessToken}`,
|
|
862
847
|
"Content-Type": "application/json",
|
|
863
848
|
"PayPal-Request-Id": requestId,
|
|
849
|
+
"PayPal-Partner-Attribution-Id": BN_CODE,
|
|
864
850
|
},
|
|
865
851
|
body: JSON.stringify(refundPayload),
|
|
866
852
|
})
|
|
@@ -952,6 +938,7 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
952
938
|
Authorization: `Bearer ${accessToken}`,
|
|
953
939
|
"Content-Type": "application/json",
|
|
954
940
|
"PayPal-Request-Id": requestId,
|
|
941
|
+
"PayPal-Partner-Attribution-Id": BN_CODE,
|
|
955
942
|
},
|
|
956
943
|
}
|
|
957
944
|
)
|
|
@@ -981,6 +968,7 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
|
|
|
981
968
|
Authorization: `Bearer ${accessToken}`,
|
|
982
969
|
"Content-Type": "application/json",
|
|
983
970
|
"PayPal-Request-Id": requestId,
|
|
971
|
+
"PayPal-Partner-Attribution-Id": BN_CODE,
|
|
984
972
|
},
|
|
985
973
|
body: JSON.stringify({}),
|
|
986
974
|
})
|
|
@@ -23,6 +23,10 @@ class PayPalModuleService extends MedusaService({
|
|
|
23
23
|
}) {
|
|
24
24
|
protected cfg = getPayPalConfig()
|
|
25
25
|
|
|
26
|
+
private get bnCode(): string {
|
|
27
|
+
return this.cfg.bnCode || "MBJTechnolabs_SI_SPB"
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
private async getSettingsData() {
|
|
27
31
|
const settings = await this.getSettings()
|
|
28
32
|
return (settings?.data || {}) as Record<string, any>
|
|
@@ -198,7 +202,6 @@ class PayPalModuleService extends MedusaService({
|
|
|
198
202
|
throw new Error("Missing PayPal partner merchant id configuration.")
|
|
199
203
|
}
|
|
200
204
|
|
|
201
|
-
const { onboarding } = await this.ensureSettingsDefaults()
|
|
202
205
|
const baseUrl = env === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com"
|
|
203
206
|
const accessToken = accessTokenOverride ?? await this.getAppAccessToken()
|
|
204
207
|
|
|
@@ -211,7 +214,7 @@ class PayPalModuleService extends MedusaService({
|
|
|
211
214
|
headers: {
|
|
212
215
|
"Content-Type": "application/json",
|
|
213
216
|
Authorization: `Bearer ${accessToken}`,
|
|
214
|
-
|
|
217
|
+
"PayPal-Partner-Attribution-Id": this.bnCode,
|
|
215
218
|
},
|
|
216
219
|
}
|
|
217
220
|
)
|
|
@@ -248,6 +251,7 @@ class PayPalModuleService extends MedusaService({
|
|
|
248
251
|
headers: {
|
|
249
252
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
250
253
|
Authorization: `Basic ${basic}`,
|
|
254
|
+
"PayPal-Partner-Attribution-Id": this.bnCode,
|
|
251
255
|
},
|
|
252
256
|
body,
|
|
253
257
|
})
|
|
@@ -304,6 +308,7 @@ class PayPalModuleService extends MedusaService({
|
|
|
304
308
|
Authorization: `Bearer ${accessToken}`,
|
|
305
309
|
"Content-Type": "application/json",
|
|
306
310
|
Accept: "application/json",
|
|
311
|
+
"PayPal-Partner-Attribution-Id": this.bnCode,
|
|
307
312
|
},
|
|
308
313
|
})
|
|
309
314
|
if (userInfoResp.ok) {
|
|
@@ -329,6 +334,7 @@ class PayPalModuleService extends MedusaService({
|
|
|
329
334
|
headers: {
|
|
330
335
|
Authorization: `Bearer ${accessToken}`,
|
|
331
336
|
"Content-Type": "application/json",
|
|
337
|
+
"PayPal-Partner-Attribution-Id": this.bnCode,
|
|
332
338
|
},
|
|
333
339
|
}
|
|
334
340
|
)
|
|
@@ -467,6 +473,7 @@ class PayPalModuleService extends MedusaService({
|
|
|
467
473
|
form.set("return_url", return_url)
|
|
468
474
|
form.set("return_url_description", "Return to your shop.")
|
|
469
475
|
form.set("partner_merchant_id", partner_merchant_id)
|
|
476
|
+
form.set("from", "medusa")
|
|
470
477
|
|
|
471
478
|
const products = input?.products?.length ? input.products : ["PPCP"]
|
|
472
479
|
|
|
@@ -621,6 +628,7 @@ class PayPalModuleService extends MedusaService({
|
|
|
621
628
|
headers: {
|
|
622
629
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
623
630
|
Authorization: `Basic ${basic}`,
|
|
631
|
+
"PayPal-Partner-Attribution-Id": this.bnCode,
|
|
624
632
|
},
|
|
625
633
|
body: tokenBody,
|
|
626
634
|
})
|
|
@@ -656,7 +664,7 @@ class PayPalModuleService extends MedusaService({
|
|
|
656
664
|
headers: {
|
|
657
665
|
"Content-Type": "application/json",
|
|
658
666
|
Authorization: `Bearer ${sellerAccessToken}`,
|
|
659
|
-
|
|
667
|
+
"PayPal-Partner-Attribution-Id": this.bnCode,
|
|
660
668
|
},
|
|
661
669
|
}
|
|
662
670
|
)
|
|
@@ -900,6 +908,7 @@ class PayPalModuleService extends MedusaService({
|
|
|
900
908
|
headers: {
|
|
901
909
|
Authorization: `Bearer ${accessToken}`,
|
|
902
910
|
"Content-Type": "application/json",
|
|
911
|
+
"PayPal-Partner-Attribution-Id": this.bnCode,
|
|
903
912
|
},
|
|
904
913
|
})
|
|
905
914
|
|
|
@@ -920,6 +929,7 @@ class PayPalModuleService extends MedusaService({
|
|
|
920
929
|
headers: {
|
|
921
930
|
Authorization: `Bearer ${accessToken}`,
|
|
922
931
|
"Content-Type": "application/json",
|
|
932
|
+
"PayPal-Partner-Attribution-Id": this.bnCode,
|
|
923
933
|
},
|
|
924
934
|
body: JSON.stringify({
|
|
925
935
|
url: webhookUrl,
|
|
@@ -1093,7 +1103,11 @@ class PayPalModuleService extends MedusaService({
|
|
|
1093
1103
|
|
|
1094
1104
|
const res = await fetch(`${baseUrl}/v1/oauth2/token`, {
|
|
1095
1105
|
method: "POST",
|
|
1096
|
-
headers: {
|
|
1106
|
+
headers: {
|
|
1107
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1108
|
+
Authorization: `Basic ${basic}`,
|
|
1109
|
+
"PayPal-Partner-Attribution-Id": this.bnCode,
|
|
1110
|
+
},
|
|
1097
1111
|
body,
|
|
1098
1112
|
})
|
|
1099
1113
|
|
|
@@ -1125,8 +1139,8 @@ class PayPalModuleService extends MedusaService({
|
|
|
1125
1139
|
Authorization: `Bearer ${accessToken}`,
|
|
1126
1140
|
"Content-Type": "application/json",
|
|
1127
1141
|
Accept: "application/json",
|
|
1142
|
+
"PayPal-Partner-Attribution-Id": this.bnCode,
|
|
1128
1143
|
...(opts?.locale ? { "Accept-Language": opts.locale } : {}),
|
|
1129
|
-
...(this.cfg.bnCode ? { "PayPal-Partner-Attribution-Id": this.cfg.bnCode } : {}),
|
|
1130
1144
|
},
|
|
1131
1145
|
})
|
|
1132
1146
|
|
|
@@ -1230,6 +1244,7 @@ class PayPalModuleService extends MedusaService({
|
|
|
1230
1244
|
headers: {
|
|
1231
1245
|
Authorization: `Basic ${auth}`,
|
|
1232
1246
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
1247
|
+
"PayPal-Partner-Attribution-Id": this.bnCode,
|
|
1233
1248
|
},
|
|
1234
1249
|
body: "grant_type=client_credentials",
|
|
1235
1250
|
})
|
|
@@ -1247,6 +1262,7 @@ class PayPalModuleService extends MedusaService({
|
|
|
1247
1262
|
headers: {
|
|
1248
1263
|
Authorization: `Bearer ${accessToken}`,
|
|
1249
1264
|
"Content-Type": "application/json",
|
|
1265
|
+
"PayPal-Partner-Attribution-Id": this.bnCode,
|
|
1250
1266
|
},
|
|
1251
1267
|
})
|
|
1252
1268
|
|
|
@@ -10,11 +10,11 @@ export type PayPalModuleConfig = {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const STATIC_CFG: PayPalModuleConfig = {
|
|
13
|
-
partnerServiceUrl: "https://mbjtechnolabs.com/ppcp-seller-onboarding/seller-onboarding.php",
|
|
13
|
+
partnerServiceUrl: "https://mbjtechnolabs.com/ppcp-seller-onboarding/seller-onboarding.php?from=medusa",
|
|
14
14
|
partnerJsUrl: "https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js",
|
|
15
15
|
backendUrl: "http://localhost:9000",
|
|
16
16
|
sellerNonce: "a1233wtergfsdt4365tzrshgfbaewa36AGa1233wtergfsdt4365tzrshgfbaewa36AG",
|
|
17
|
-
bnCode: "",
|
|
17
|
+
bnCode: "MBJTechnolabs_SI_SPB",
|
|
18
18
|
partnerMerchantIdSandbox: "K6QLN2LPGQRHL",
|
|
19
19
|
partnerMerchantIdLive: "GT5R877JNBPLL",
|
|
20
20
|
alertWebhookUrls: [],
|
|
@@ -1,32 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
1
|
+
export function getPayPalApiBase(environment: string): string {
|
|
2
|
+
return environment === "live"
|
|
3
|
+
? "https://api-m.paypal.com"
|
|
4
|
+
: "https://api-m.sandbox.paypal.com"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const BN_CODE = "MBJTechnolabs_SI_SPB"
|
|
8
|
+
|
|
9
|
+
export async function getPayPalAccessToken(opts: {
|
|
10
|
+
environment: string
|
|
11
|
+
client_id: string
|
|
12
|
+
client_secret: string
|
|
13
|
+
}): Promise<{ accessToken: string; base: string }> {
|
|
14
|
+
const base = getPayPalApiBase(opts.environment)
|
|
15
|
+
const auth = Buffer.from(`${opts.client_id}:${opts.client_secret}`).toString("base64")
|
|
16
|
+
const resp = await fetch(`${base}/v1/oauth2/token`, {
|
|
17
|
+
method: "POST",
|
|
18
|
+
headers: {
|
|
19
|
+
Authorization: `Basic ${auth}`,
|
|
20
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
21
|
+
"PayPal-Partner-Attribution-Id": BN_CODE,
|
|
22
|
+
},
|
|
23
|
+
body: "grant_type=client_credentials",
|
|
24
|
+
})
|
|
25
|
+
const text = await resp.text()
|
|
26
|
+
if (!resp.ok) {
|
|
27
|
+
throw new Error(`PayPal token error (${resp.status}): ${text}`)
|
|
28
|
+
}
|
|
29
|
+
const json = JSON.parse(text)
|
|
30
|
+
return { accessToken: String(json.access_token), base }
|
|
31
|
+
}
|
|
@@ -2,7 +2,6 @@ import type { MedusaContainer } from "@medusajs/framework/types"
|
|
|
2
2
|
import { Modules } from "@medusajs/framework/utils"
|
|
3
3
|
import { isPayPalProviderId } from "./utils/provider-ids"
|
|
4
4
|
|
|
5
|
-
// ─── Event → Medusa status mapping ───────────────────────────────────────────
|
|
6
5
|
|
|
7
6
|
export const EVENT_STATUS_MAP: Record<
|
|
8
7
|
string,
|
|
@@ -23,9 +22,6 @@ export const EVENT_STATUS_MAP: Record<
|
|
|
23
22
|
"PAYMENT.REFUND.DENIED": "error",
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
// ─── Status transition guard ──────────────────────────────────────────────────
|
|
27
|
-
// Only allow forward/meaningful moves.
|
|
28
|
-
// Prevents a late-arriving webhook from downgrading an already-captured payment.
|
|
29
25
|
|
|
30
26
|
const ALLOWED_TRANSITIONS: Record<string, Set<string>> = {
|
|
31
27
|
pending: new Set(["authorized", "captured", "canceled", "error"]),
|
|
@@ -39,7 +35,6 @@ export function isTransitionAllowed(from: string, to: string): boolean {
|
|
|
39
35
|
return ALLOWED_TRANSITIONS[from]?.has(to) ?? false
|
|
40
36
|
}
|
|
41
37
|
|
|
42
|
-
// ─── Event type helpers ───────────────────────────────────────────────────────
|
|
43
38
|
|
|
44
39
|
export const SUPPORTED_EVENT_PREFIXES = [
|
|
45
40
|
"PAYMENT.CAPTURE.",
|
|
@@ -52,9 +47,6 @@ export function isAllowedEventType(eventType: string): boolean {
|
|
|
52
47
|
return SUPPORTED_EVENT_PREFIXES.some((prefix) => eventType.startsWith(prefix))
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
// ─── Error classification ─────────────────────────────────────────────────────
|
|
56
|
-
// Non-retryable: event is permanently unprocessable (wrong cart, missing session).
|
|
57
|
-
// Retryable: transient failure (DB down, network error) — worth trying again.
|
|
58
50
|
|
|
59
51
|
const NON_RETRYABLE_PATTERNS = [
|
|
60
52
|
"payment collection not found",
|
|
@@ -71,7 +63,6 @@ export function isRetryableError(error: unknown): boolean {
|
|
|
71
63
|
return !NON_RETRYABLE_PATTERNS.some((p) => message.includes(p))
|
|
72
64
|
}
|
|
73
65
|
|
|
74
|
-
// ─── Retry schedule ───────────────────────────────────────────────────────────
|
|
75
66
|
|
|
76
67
|
const RETRY_SCHEDULE_MINUTES = [2, 10, 30, 60, 120]
|
|
77
68
|
export const MAX_WEBHOOK_ATTEMPTS = RETRY_SCHEDULE_MINUTES.length + 1
|
|
@@ -83,7 +74,6 @@ export function computeNextRetryAt(attemptCount: number): Date | null {
|
|
|
83
74
|
return new Date(Date.now() + delayMinutes * 60 * 1000)
|
|
84
75
|
}
|
|
85
76
|
|
|
86
|
-
// ─── Payload normalisation ────────────────────────────────────────────────────
|
|
87
77
|
|
|
88
78
|
export function normalizeResource(payload: Record<string, any>): Record<string, any> {
|
|
89
79
|
const resource = payload?.resource
|
|
@@ -109,7 +99,6 @@ export function normalizeEventVersion(payload: Record<string, any>): string | nu
|
|
|
109
99
|
return String(raw).trim().replace(/^v/i, "")
|
|
110
100
|
}
|
|
111
101
|
|
|
112
|
-
// ─── Identifier extraction ────────────────────────────────────────────────────
|
|
113
102
|
|
|
114
103
|
export interface ExtractedIdentifiers {
|
|
115
104
|
orderId: string | null
|
|
@@ -160,7 +149,6 @@ export function extractIdentifiers(
|
|
|
160
149
|
return { orderId, captureId, refundId, cartId }
|
|
161
150
|
}
|
|
162
151
|
|
|
163
|
-
// ─── Session lookup ───────────────────────────────────────────────────────────
|
|
164
152
|
|
|
165
153
|
interface ResolvedSession {
|
|
166
154
|
sessionId: string
|
|
@@ -216,7 +204,6 @@ async function findPayPalSession(
|
|
|
216
204
|
}
|
|
217
205
|
}
|
|
218
206
|
|
|
219
|
-
// ─── Session update ───────────────────────────────────────────────────────────
|
|
220
207
|
|
|
221
208
|
function mergeRefunds(existing: any[], incoming: any[]): any[] {
|
|
222
209
|
const seen = new Set<string>()
|
|
@@ -270,7 +257,6 @@ async function applyStatusToSession(
|
|
|
270
257
|
})
|
|
271
258
|
}
|
|
272
259
|
|
|
273
|
-
// ─── Main event processor ─────────────────────────────────────────────────────
|
|
274
260
|
|
|
275
261
|
export interface ProcessResult {
|
|
276
262
|
orderId: string | null
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { ModuleProvider, Modules } from "@medusajs/framework/utils"
|
|
2
|
-
import { PayPalPaymentProvider } from "../../modules/paypal/payment-provider/service"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// Optional named export (doesn't hurt, can help for testing/imports)
|
|
10
|
-
export { PayPalPaymentProvider }
|
|
1
|
+
import { ModuleProvider, Modules } from "@medusajs/framework/utils"
|
|
2
|
+
import { PayPalPaymentProvider } from "../../modules/paypal/payment-provider/service"
|
|
3
|
+
|
|
4
|
+
export default ModuleProvider(Modules.PAYMENT, {
|
|
5
|
+
services: [PayPalPaymentProvider],
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
export { PayPalPaymentProvider }
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { ModuleProvider, Modules } from "@medusajs/framework/utils"
|
|
2
|
-
import { PayPalAdvancedCardProvider } from "../../modules/paypal/payment-provider/card-service"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// Optional named export
|
|
10
|
-
export { PayPalAdvancedCardProvider }
|
|
1
|
+
import { ModuleProvider, Modules } from "@medusajs/framework/utils"
|
|
2
|
+
import { PayPalAdvancedCardProvider } from "../../modules/paypal/payment-provider/card-service"
|
|
3
|
+
|
|
4
|
+
export default ModuleProvider(Modules.PAYMENT, {
|
|
5
|
+
services: [PayPalAdvancedCardProvider],
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
export { PayPalAdvancedCardProvider }
|
|
@@ -3,8 +3,6 @@ import type PayPalModuleService from "../modules/paypal/service"
|
|
|
3
3
|
import { getPayPalAccessToken } from "../modules/paypal/utils/paypal-auth"
|
|
4
4
|
import { isPayPalProviderId } from "../modules/paypal/utils/provider-ids"
|
|
5
5
|
|
|
6
|
-
// PayPal orders can only be PATCHed in CREATED or APPROVED status.
|
|
7
|
-
// Once COMPLETED (post-capture) the order is immutable.
|
|
8
6
|
const PATCHABLE_STATUSES = new Set(["CREATED", "APPROVED", "SAVED"])
|
|
9
7
|
|
|
10
8
|
export default async function paypalOrderInvoiceHandler({
|
|
@@ -18,7 +16,6 @@ export default async function paypalOrderInvoiceHandler({
|
|
|
18
16
|
const query = container.resolve("query") as any
|
|
19
17
|
const paypal = container.resolve<PayPalModuleService>("paypal_onboarding")
|
|
20
18
|
|
|
21
|
-
// Fetch the Medusa order with payment session data
|
|
22
19
|
const { data: orders } = await query.graph({
|
|
23
20
|
entity: "order",
|
|
24
21
|
fields: [
|
|
@@ -36,7 +33,6 @@ export default async function paypalOrderInvoiceHandler({
|
|
|
36
33
|
const order = orders?.[0]
|
|
37
34
|
if (!order) return
|
|
38
35
|
|
|
39
|
-
// Find the PayPal payment session
|
|
40
36
|
const sessions = (order.payment_collections || []).flatMap(
|
|
41
37
|
(pc: any) => pc.payment_sessions || []
|
|
42
38
|
)
|
|
@@ -69,7 +65,6 @@ export default async function paypalOrderInvoiceHandler({
|
|
|
69
65
|
return
|
|
70
66
|
}
|
|
71
67
|
|
|
72
|
-
// Build the target invoice_id using display_id (ideal format)
|
|
73
68
|
const settings = await paypal.getSettings().catch(() => ({}))
|
|
74
69
|
const settingsData =
|
|
75
70
|
settings && typeof settings === "object" && "data" in settings
|
|
@@ -87,14 +82,9 @@ export default async function paypalOrderInvoiceHandler({
|
|
|
87
82
|
|
|
88
83
|
if (!invoiceId) return
|
|
89
84
|
|
|
90
|
-
// Get PayPal access token
|
|
91
85
|
const creds = await paypal.getActiveCredentials()
|
|
92
86
|
const { accessToken, base } = await getPayPalAccessToken(creds)
|
|
93
87
|
|
|
94
|
-
// Check PayPal order status before attempting PATCH.
|
|
95
|
-
// PayPal only allows PATCH on CREATED or APPROVED orders.
|
|
96
|
-
// COMPLETED orders (post-capture) are immutable, so skip PATCH and
|
|
97
|
-
// log the reconciliation mapping instead.
|
|
98
88
|
let paypalOrderStatus = ""
|
|
99
89
|
let currentInvoiceId = ""
|
|
100
90
|
try {
|
|
@@ -114,7 +104,6 @@ export default async function paypalOrderInvoiceHandler({
|
|
|
114
104
|
)
|
|
115
105
|
}
|
|
116
106
|
|
|
117
|
-
// Always log the display_id -> paypalOrderId mapping for reconciliation
|
|
118
107
|
console.info("[PayPal] invoice reconciliation mapping:", {
|
|
119
108
|
medusaOrderId: orderId,
|
|
120
109
|
displayId,
|
|
@@ -124,7 +113,6 @@ export default async function paypalOrderInvoiceHandler({
|
|
|
124
113
|
currentInvoiceId,
|
|
125
114
|
})
|
|
126
115
|
|
|
127
|
-
// If order is COMPLETED, invoice_id is immutable — skip PATCH
|
|
128
116
|
if (paypalOrderStatus && !PATCHABLE_STATUSES.has(paypalOrderStatus)) {
|
|
129
117
|
console.info(
|
|
130
118
|
`[PayPal] invoice_id PATCH skipped — order status is ${paypalOrderStatus} (immutable).`,
|
|
@@ -133,7 +121,6 @@ export default async function paypalOrderInvoiceHandler({
|
|
|
133
121
|
return
|
|
134
122
|
}
|
|
135
123
|
|
|
136
|
-
// If invoice_id already matches target, skip PATCH
|
|
137
124
|
if (currentInvoiceId === invoiceId) {
|
|
138
125
|
console.info(
|
|
139
126
|
`[PayPal] invoice_id already "${invoiceId}" — skipping PATCH`
|
|
@@ -141,8 +128,6 @@ export default async function paypalOrderInvoiceHandler({
|
|
|
141
128
|
return
|
|
142
129
|
}
|
|
143
130
|
|
|
144
|
-
// Attempt PATCH only for CREATED/APPROVED orders.
|
|
145
|
-
// This handles the authorize-only flow where capture hasn't happened yet.
|
|
146
131
|
const patchResp = await fetch(
|
|
147
132
|
`${base}/v2/checkout/orders/${paypalOrderId}`,
|
|
148
133
|
{
|
|
@@ -177,7 +162,6 @@ export default async function paypalOrderInvoiceHandler({
|
|
|
177
162
|
})
|
|
178
163
|
}
|
|
179
164
|
} catch (e: any) {
|
|
180
|
-
// Non-fatal — never block order placement
|
|
181
165
|
console.warn("[PayPal] paypalOrderInvoiceHandler error:", e?.message || e)
|
|
182
166
|
}
|
|
183
167
|
}
|