@easypayment/medusa-paypal 0.4.6 → 0.4.8
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 +7 -7
- package/.medusa/server/src/admin/index.mjs +7 -7
- 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 +10 -3
- package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -1
- package/.medusa/server/src/modules/paypal/migrations/20260115120000_create_paypal_connection.js +22 -22
- package/.medusa/server/src/modules/paypal/migrations/20260123090000_create_paypal_settings.js +11 -11
- package/.medusa/server/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.js +18 -18
- package/.medusa/server/src/modules/paypal/migrations/20260401090000_create_paypal_metric.js +16 -16
- package/.medusa/server/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.js +20 -20
- package/.medusa/server/src/modules/paypal/migrations/20261101090000_remove_paypal_reconciliation_status.js +14 -14
- package/.medusa/server/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.js +15 -15
- package/README.md +142 -142
- package/package.json +75 -75
- package/src/admin/index.ts +7 -7
- package/src/admin/routes/settings/paypal/_components/Tabs.tsx +52 -52
- package/src/admin/routes/settings/paypal/_components/Toast.tsx +51 -51
- package/src/admin/routes/settings/paypal/additional-settings/page.tsx +200 -200
- package/src/admin/routes/settings/paypal/advanced-card-payments/page.tsx +183 -183
- package/src/admin/routes/settings/paypal/apple-pay/page.tsx +5 -5
- package/src/admin/routes/settings/paypal/connection/page.tsx +754 -754
- package/src/admin/routes/settings/paypal/google-pay/page.tsx +5 -5
- package/src/admin/routes/settings/paypal/pay-later-messaging/page.tsx +5 -5
- package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +376 -376
- package/src/api/admin/payment-collections/[id]/payment-sessions/route.ts +24 -24
- package/src/api/admin/paypal/disconnect/route.ts +8 -8
- package/src/api/admin/paypal/environment/route.ts +25 -25
- package/src/api/admin/paypal/onboard-complete/route.ts +44 -44
- package/src/api/admin/paypal/onboarding-link/route.ts +45 -45
- package/src/api/admin/paypal/onboarding-status/route.ts +18 -18
- package/src/api/admin/paypal/rotate-credentials/route.ts +8 -8
- package/src/api/admin/paypal/save-credentials/route.ts +14 -14
- package/src/api/admin/paypal/settings/route.ts +14 -14
- package/src/api/admin/paypal/status/route.ts +12 -12
- package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +65 -65
- package/src/api/store/paypal/capture-order/route.ts +276 -276
- package/src/api/store/paypal/config/route.ts +102 -102
- package/src/api/store/paypal/create-order/route.ts +13 -3
- package/src/api/store/paypal/settings/route.ts +19 -19
- package/src/api/store/paypal/webhook/route.ts +246 -246
- package/src/api/store/paypal-complete/route.ts +75 -75
- package/src/jobs/paypal-reconcile.ts +112 -112
- package/src/jobs/paypal-webhook-retry.ts +85 -85
- package/src/modules/paypal/clients/paypal-seller.client.ts +59 -59
- package/src/modules/paypal/index.ts +8 -8
- package/src/modules/paypal/migrations/20260115120000_create_paypal_connection.ts +33 -33
- package/src/modules/paypal/migrations/20260123090000_create_paypal_settings.ts +22 -22
- package/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.ts +29 -29
- package/src/modules/paypal/migrations/20260401090000_create_paypal_metric.ts +27 -27
- package/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.ts +31 -31
- package/src/modules/paypal/migrations/20261101090000_remove_paypal_reconciliation_status.ts +25 -25
- package/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.ts +26 -26
- package/src/modules/paypal/migrations/20270101090000_set_paypal_environment_default_live.ts +11 -11
- package/src/modules/paypal/models/paypal_connection.ts +21 -21
- package/src/modules/paypal/models/paypal_metric.ts +9 -9
- package/src/modules/paypal/models/paypal_settings.ts +8 -8
- package/src/modules/paypal/models/paypal_webhook_event.ts +19 -19
- package/src/modules/paypal/payment-provider/README.md +22 -22
- package/src/modules/paypal/payment-provider/card-service.ts +760 -760
- package/src/modules/paypal/payment-provider/index.ts +19 -19
- package/src/modules/paypal/payment-provider/service.ts +1121 -1121
- package/src/modules/paypal/payment-provider/webhook-utils.ts +88 -88
- package/src/modules/paypal/service.ts +1247 -1247
- package/src/modules/paypal/types/config.ts +47 -47
- package/src/modules/paypal/utils/amounts.ts +41 -41
- package/src/modules/paypal/utils/crypto.ts +51 -51
- package/src/modules/paypal/utils/currencies.ts +84 -84
- package/src/modules/paypal/utils/paypal-auth.ts +32 -32
- package/src/modules/paypal/utils/provider-ids.ts +15 -15
- package/src/modules/paypal/webhook-processor.ts +215 -215
|
@@ -1,103 +1,103 @@
|
|
|
1
|
-
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
-
import type PayPalModuleService from "../../../../modules/paypal/service"
|
|
3
|
-
import {
|
|
4
|
-
getPayPalCurrencyCompatibility,
|
|
5
|
-
getPayPalSupportedCurrencies,
|
|
6
|
-
normalizeCurrencyCode,
|
|
7
|
-
} from "../../../../modules/paypal/utils/currencies"
|
|
8
|
-
|
|
9
|
-
export async function GET(req: MedusaRequest, res: MedusaResponse) {
|
|
10
|
-
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
11
|
-
try {
|
|
12
|
-
const creds = await paypal.getActiveCredentials()
|
|
13
|
-
const apiDetails = await paypal.getApiDetails().catch(() => null)
|
|
14
|
-
const client_token = await paypal.generateClientToken({ locale: "en_US" }).catch(() => "")
|
|
15
|
-
const cartId = (req.query?.cart_id as string) || ""
|
|
16
|
-
const query = req.scope.resolve("query")
|
|
17
|
-
let currency = normalizeCurrencyCode(
|
|
18
|
-
apiDetails?.apiDetails?.currency_code || process.env.PAYPAL_CURRENCY || "EUR"
|
|
19
|
-
)
|
|
20
|
-
if (cartId) {
|
|
21
|
-
const { data: carts } = await query.graph({
|
|
22
|
-
entity: "cart",
|
|
23
|
-
fields: ["id", "currency_code", "region.currency_code"],
|
|
24
|
-
filters: { id: cartId },
|
|
25
|
-
})
|
|
26
|
-
const cart = carts?.[0]
|
|
27
|
-
if (cart) {
|
|
28
|
-
currency = normalizeCurrencyCode(
|
|
29
|
-
cart.region?.currency_code || cart.currency_code || currency
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
const compatibility = getPayPalCurrencyCompatibility({
|
|
34
|
-
currencyCode: currency,
|
|
35
|
-
paypalCurrencyOverride:
|
|
36
|
-
apiDetails?.apiDetails?.currency_code || process.env.PAYPAL_CURRENCY,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
// Read settings so frontend SDK mirrors admin controls.
|
|
40
|
-
const settings = await paypal.getSettings().catch(() => ({}))
|
|
41
|
-
const data =
|
|
42
|
-
settings && typeof settings === "object" && "data" in settings
|
|
43
|
-
? ((settings as any).data || {})
|
|
44
|
-
: {}
|
|
45
|
-
|
|
46
|
-
const additionalSettings =
|
|
47
|
-
data && typeof data === "object"
|
|
48
|
-
? ((data as Record<string, any>).additional_settings || {})
|
|
49
|
-
: {}
|
|
50
|
-
|
|
51
|
-
const paypalSettings =
|
|
52
|
-
data && typeof data === "object"
|
|
53
|
-
? ((data as Record<string, any>).paypal_settings || {})
|
|
54
|
-
: {}
|
|
55
|
-
|
|
56
|
-
const paymentAction =
|
|
57
|
-
typeof additionalSettings.paymentAction === "string"
|
|
58
|
-
? additionalSettings.paymentAction
|
|
59
|
-
: "capture"
|
|
60
|
-
|
|
61
|
-
// P1 — enforce disable at API level: return 403 when admin disables PayPal wallet
|
|
62
|
-
if (paypalSettings.enabled === false) {
|
|
63
|
-
return res.status(403).json({ message: "PayPal is currently disabled." })
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// P2 — read advanced card payments settings
|
|
68
|
-
const advancedCardSettings =
|
|
69
|
-
data && typeof data === "object"
|
|
70
|
-
? ((data as Record<string, any>).advanced_card_payments || {})
|
|
71
|
-
: {}
|
|
72
|
-
|
|
73
|
-
const cardEnabled: boolean = advancedCardSettings.enabled !== false
|
|
74
|
-
|
|
75
|
-
const cardThreeDS =
|
|
76
|
-
typeof advancedCardSettings.threeDS === "string"
|
|
77
|
-
? advancedCardSettings.threeDS
|
|
78
|
-
: "when_required"
|
|
79
|
-
|
|
80
|
-
return res.json({
|
|
81
|
-
environment: creds.environment,
|
|
82
|
-
client_id: creds.client_id,
|
|
83
|
-
currency: compatibility.currency,
|
|
84
|
-
currency_supported: compatibility.supported,
|
|
85
|
-
currency_errors: compatibility.errors,
|
|
86
|
-
supported_currencies: getPayPalSupportedCurrencies(),
|
|
87
|
-
client_token,
|
|
88
|
-
intent: paymentAction,
|
|
89
|
-
paypal_enabled: paypalSettings.enabled ?? true,
|
|
90
|
-
paypal_title: paypalSettings.title || "PayPal",
|
|
91
|
-
card_enabled: cardEnabled,
|
|
92
|
-
card_title: advancedCardSettings.title || "Credit or Debit Card",
|
|
93
|
-
card_three_ds: cardThreeDS, // ← added
|
|
94
|
-
button_color: paypalSettings.buttonColor || "gold",
|
|
95
|
-
button_shape: paypalSettings.buttonShape || "rect",
|
|
96
|
-
button_width: paypalSettings.buttonWidth || "responsive",
|
|
97
|
-
button_height: paypalSettings.buttonHeight ?? 45,
|
|
98
|
-
button_label: paypalSettings.buttonLabel || "paypal",
|
|
99
|
-
})
|
|
100
|
-
} catch (e: any) {
|
|
101
|
-
return res.status(500).json({ message: e?.message || "Failed to load PayPal config" })
|
|
102
|
-
}
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import type PayPalModuleService from "../../../../modules/paypal/service"
|
|
3
|
+
import {
|
|
4
|
+
getPayPalCurrencyCompatibility,
|
|
5
|
+
getPayPalSupportedCurrencies,
|
|
6
|
+
normalizeCurrencyCode,
|
|
7
|
+
} from "../../../../modules/paypal/utils/currencies"
|
|
8
|
+
|
|
9
|
+
export async function GET(req: MedusaRequest, res: MedusaResponse) {
|
|
10
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
11
|
+
try {
|
|
12
|
+
const creds = await paypal.getActiveCredentials()
|
|
13
|
+
const apiDetails = await paypal.getApiDetails().catch(() => null)
|
|
14
|
+
const client_token = await paypal.generateClientToken({ locale: "en_US" }).catch(() => "")
|
|
15
|
+
const cartId = (req.query?.cart_id as string) || ""
|
|
16
|
+
const query = req.scope.resolve("query")
|
|
17
|
+
let currency = normalizeCurrencyCode(
|
|
18
|
+
apiDetails?.apiDetails?.currency_code || process.env.PAYPAL_CURRENCY || "EUR"
|
|
19
|
+
)
|
|
20
|
+
if (cartId) {
|
|
21
|
+
const { data: carts } = await query.graph({
|
|
22
|
+
entity: "cart",
|
|
23
|
+
fields: ["id", "currency_code", "region.currency_code"],
|
|
24
|
+
filters: { id: cartId },
|
|
25
|
+
})
|
|
26
|
+
const cart = carts?.[0]
|
|
27
|
+
if (cart) {
|
|
28
|
+
currency = normalizeCurrencyCode(
|
|
29
|
+
cart.region?.currency_code || cart.currency_code || currency
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const compatibility = getPayPalCurrencyCompatibility({
|
|
34
|
+
currencyCode: currency,
|
|
35
|
+
paypalCurrencyOverride:
|
|
36
|
+
apiDetails?.apiDetails?.currency_code || process.env.PAYPAL_CURRENCY,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// Read settings so frontend SDK mirrors admin controls.
|
|
40
|
+
const settings = await paypal.getSettings().catch(() => ({}))
|
|
41
|
+
const data =
|
|
42
|
+
settings && typeof settings === "object" && "data" in settings
|
|
43
|
+
? ((settings as any).data || {})
|
|
44
|
+
: {}
|
|
45
|
+
|
|
46
|
+
const additionalSettings =
|
|
47
|
+
data && typeof data === "object"
|
|
48
|
+
? ((data as Record<string, any>).additional_settings || {})
|
|
49
|
+
: {}
|
|
50
|
+
|
|
51
|
+
const paypalSettings =
|
|
52
|
+
data && typeof data === "object"
|
|
53
|
+
? ((data as Record<string, any>).paypal_settings || {})
|
|
54
|
+
: {}
|
|
55
|
+
|
|
56
|
+
const paymentAction =
|
|
57
|
+
typeof additionalSettings.paymentAction === "string"
|
|
58
|
+
? additionalSettings.paymentAction
|
|
59
|
+
: "capture"
|
|
60
|
+
|
|
61
|
+
// P1 — enforce disable at API level: return 403 when admin disables PayPal wallet
|
|
62
|
+
if (paypalSettings.enabled === false) {
|
|
63
|
+
return res.status(403).json({ message: "PayPal is currently disabled." })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
// P2 — read advanced card payments settings
|
|
68
|
+
const advancedCardSettings =
|
|
69
|
+
data && typeof data === "object"
|
|
70
|
+
? ((data as Record<string, any>).advanced_card_payments || {})
|
|
71
|
+
: {}
|
|
72
|
+
|
|
73
|
+
const cardEnabled: boolean = advancedCardSettings.enabled !== false
|
|
74
|
+
|
|
75
|
+
const cardThreeDS =
|
|
76
|
+
typeof advancedCardSettings.threeDS === "string"
|
|
77
|
+
? advancedCardSettings.threeDS
|
|
78
|
+
: "when_required"
|
|
79
|
+
|
|
80
|
+
return res.json({
|
|
81
|
+
environment: creds.environment,
|
|
82
|
+
client_id: creds.client_id,
|
|
83
|
+
currency: compatibility.currency,
|
|
84
|
+
currency_supported: compatibility.supported,
|
|
85
|
+
currency_errors: compatibility.errors,
|
|
86
|
+
supported_currencies: getPayPalSupportedCurrencies(),
|
|
87
|
+
client_token,
|
|
88
|
+
intent: paymentAction,
|
|
89
|
+
paypal_enabled: paypalSettings.enabled ?? true,
|
|
90
|
+
paypal_title: paypalSettings.title || "PayPal",
|
|
91
|
+
card_enabled: cardEnabled,
|
|
92
|
+
card_title: advancedCardSettings.title || "Credit or Debit Card",
|
|
93
|
+
card_three_ds: cardThreeDS, // ← added
|
|
94
|
+
button_color: paypalSettings.buttonColor || "gold",
|
|
95
|
+
button_shape: paypalSettings.buttonShape || "rect",
|
|
96
|
+
button_width: paypalSettings.buttonWidth || "responsive",
|
|
97
|
+
button_height: paypalSettings.buttonHeight ?? 45,
|
|
98
|
+
button_label: paypalSettings.buttonLabel || "paypal",
|
|
99
|
+
})
|
|
100
|
+
} catch (e: any) {
|
|
101
|
+
return res.status(500).json({ message: e?.message || "Failed to load PayPal config" })
|
|
102
|
+
}
|
|
103
103
|
}
|
|
@@ -254,13 +254,21 @@ export async function POST(req: MedusaRequest, res: MedusaResponse) {
|
|
|
254
254
|
const discountMajor = Number(cart.discount_total || 0)
|
|
255
255
|
const giftCardMajor = Number(cart.gift_card_total || 0)
|
|
256
256
|
const lineItems = Array.isArray((cart as any).items) ? (cart as any).items : []
|
|
257
|
+
// factor converts MINOR units (cents) → MAJOR units (euros)
|
|
258
|
+
// e.g. item.subtotal=1000, factor=100 → €10.00
|
|
259
|
+
const factor = Math.pow(10, exponent)
|
|
260
|
+
|
|
257
261
|
const purchaseItemsRaw = sendItemDetails
|
|
258
262
|
? lineItems
|
|
259
263
|
.map((item: any) => {
|
|
260
264
|
const quantity = Number(item?.quantity || 0)
|
|
261
|
-
|
|
265
|
+
// item.subtotal is in MINOR units (cents) — divide by factor to get euros
|
|
266
|
+
const lineSubtotalMinor = Number(
|
|
267
|
+
item?.subtotal ?? (Number(item?.unit_price || 0) * quantity)
|
|
268
|
+
)
|
|
269
|
+
const lineSubtotalMajor = lineSubtotalMinor / factor
|
|
262
270
|
const unitAmount =
|
|
263
|
-
quantity > 0 ? parseFloat((
|
|
271
|
+
quantity > 0 ? parseFloat((lineSubtotalMajor / quantity).toFixed(exponent)) : 0
|
|
264
272
|
|
|
265
273
|
if (!quantity || Number.isNaN(quantity) || Number.isNaN(unitAmount)) {
|
|
266
274
|
return null
|
|
@@ -290,7 +298,9 @@ export async function POST(req: MedusaRequest, res: MedusaResponse) {
|
|
|
290
298
|
const diff = parseFloat((subtotalMajor - roundedItemSumFixed).toFixed(exponent))
|
|
291
299
|
|
|
292
300
|
const finalPurchaseItems = purchaseItemsRaw.map((item: any) => item.paypalItem)
|
|
293
|
-
|
|
301
|
+
// adjustedItemTotal stays as subtotalMajor — the offset item absorbs the rounding gap
|
|
302
|
+
// so that sum(unit_amount × quantity) === subtotalMajor exactly
|
|
303
|
+
const adjustedItemTotal = subtotalMajor
|
|
294
304
|
|
|
295
305
|
if (Math.abs(diff) > 0.000001 && sendItemDetails && finalPurchaseItems.length > 0) {
|
|
296
306
|
finalPurchaseItems.push({
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
-
import type PayPalModuleService from "../../../../modules/paypal/service"
|
|
3
|
-
|
|
4
|
-
export async function GET(req: MedusaRequest, res: MedusaResponse) {
|
|
5
|
-
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
6
|
-
try {
|
|
7
|
-
const settings = await paypal.getSettings()
|
|
8
|
-
const data = (settings?.data || {}) as Record<string, any>
|
|
9
|
-
const additionalSettings = (data.additional_settings || {}) as Record<string, any>
|
|
10
|
-
const advancedCard = (data.advanced_card_payments || {}) as Record<string, any>
|
|
11
|
-
|
|
12
|
-
return res.json({
|
|
13
|
-
paymentAction: additionalSettings.paymentAction === "authorize" ? "authorize" : "capture",
|
|
14
|
-
advancedCardEnabled: advancedCard.enabled === true,
|
|
15
|
-
})
|
|
16
|
-
} catch (e: any) {
|
|
17
|
-
return res.status(500).json({ message: e?.message || "Failed to load PayPal settings" })
|
|
18
|
-
}
|
|
19
|
-
}
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import type PayPalModuleService from "../../../../modules/paypal/service"
|
|
3
|
+
|
|
4
|
+
export async function GET(req: MedusaRequest, res: MedusaResponse) {
|
|
5
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
6
|
+
try {
|
|
7
|
+
const settings = await paypal.getSettings()
|
|
8
|
+
const data = (settings?.data || {}) as Record<string, any>
|
|
9
|
+
const additionalSettings = (data.additional_settings || {}) as Record<string, any>
|
|
10
|
+
const advancedCard = (data.advanced_card_payments || {}) as Record<string, any>
|
|
11
|
+
|
|
12
|
+
return res.json({
|
|
13
|
+
paymentAction: additionalSettings.paymentAction === "authorize" ? "authorize" : "capture",
|
|
14
|
+
advancedCardEnabled: advancedCard.enabled === true,
|
|
15
|
+
})
|
|
16
|
+
} catch (e: any) {
|
|
17
|
+
return res.status(500).json({ message: e?.message || "Failed to load PayPal settings" })
|
|
18
|
+
}
|
|
19
|
+
}
|