@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.
Files changed (70) hide show
  1. package/.medusa/server/src/admin/index.js +7 -7
  2. package/.medusa/server/src/admin/index.mjs +7 -7
  3. package/.medusa/server/src/api/store/paypal/create-order/route.d.ts.map +1 -1
  4. package/.medusa/server/src/api/store/paypal/create-order/route.js +10 -3
  5. package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -1
  6. package/.medusa/server/src/modules/paypal/migrations/20260115120000_create_paypal_connection.js +22 -22
  7. package/.medusa/server/src/modules/paypal/migrations/20260123090000_create_paypal_settings.js +11 -11
  8. package/.medusa/server/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.js +18 -18
  9. package/.medusa/server/src/modules/paypal/migrations/20260401090000_create_paypal_metric.js +16 -16
  10. package/.medusa/server/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.js +20 -20
  11. package/.medusa/server/src/modules/paypal/migrations/20261101090000_remove_paypal_reconciliation_status.js +14 -14
  12. package/.medusa/server/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.js +15 -15
  13. package/README.md +142 -142
  14. package/package.json +75 -75
  15. package/src/admin/index.ts +7 -7
  16. package/src/admin/routes/settings/paypal/_components/Tabs.tsx +52 -52
  17. package/src/admin/routes/settings/paypal/_components/Toast.tsx +51 -51
  18. package/src/admin/routes/settings/paypal/additional-settings/page.tsx +200 -200
  19. package/src/admin/routes/settings/paypal/advanced-card-payments/page.tsx +183 -183
  20. package/src/admin/routes/settings/paypal/apple-pay/page.tsx +5 -5
  21. package/src/admin/routes/settings/paypal/connection/page.tsx +754 -754
  22. package/src/admin/routes/settings/paypal/google-pay/page.tsx +5 -5
  23. package/src/admin/routes/settings/paypal/pay-later-messaging/page.tsx +5 -5
  24. package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +376 -376
  25. package/src/api/admin/payment-collections/[id]/payment-sessions/route.ts +24 -24
  26. package/src/api/admin/paypal/disconnect/route.ts +8 -8
  27. package/src/api/admin/paypal/environment/route.ts +25 -25
  28. package/src/api/admin/paypal/onboard-complete/route.ts +44 -44
  29. package/src/api/admin/paypal/onboarding-link/route.ts +45 -45
  30. package/src/api/admin/paypal/onboarding-status/route.ts +18 -18
  31. package/src/api/admin/paypal/rotate-credentials/route.ts +8 -8
  32. package/src/api/admin/paypal/save-credentials/route.ts +14 -14
  33. package/src/api/admin/paypal/settings/route.ts +14 -14
  34. package/src/api/admin/paypal/status/route.ts +12 -12
  35. package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +65 -65
  36. package/src/api/store/paypal/capture-order/route.ts +276 -276
  37. package/src/api/store/paypal/config/route.ts +102 -102
  38. package/src/api/store/paypal/create-order/route.ts +13 -3
  39. package/src/api/store/paypal/settings/route.ts +19 -19
  40. package/src/api/store/paypal/webhook/route.ts +246 -246
  41. package/src/api/store/paypal-complete/route.ts +75 -75
  42. package/src/jobs/paypal-reconcile.ts +112 -112
  43. package/src/jobs/paypal-webhook-retry.ts +85 -85
  44. package/src/modules/paypal/clients/paypal-seller.client.ts +59 -59
  45. package/src/modules/paypal/index.ts +8 -8
  46. package/src/modules/paypal/migrations/20260115120000_create_paypal_connection.ts +33 -33
  47. package/src/modules/paypal/migrations/20260123090000_create_paypal_settings.ts +22 -22
  48. package/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.ts +29 -29
  49. package/src/modules/paypal/migrations/20260401090000_create_paypal_metric.ts +27 -27
  50. package/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.ts +31 -31
  51. package/src/modules/paypal/migrations/20261101090000_remove_paypal_reconciliation_status.ts +25 -25
  52. package/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.ts +26 -26
  53. package/src/modules/paypal/migrations/20270101090000_set_paypal_environment_default_live.ts +11 -11
  54. package/src/modules/paypal/models/paypal_connection.ts +21 -21
  55. package/src/modules/paypal/models/paypal_metric.ts +9 -9
  56. package/src/modules/paypal/models/paypal_settings.ts +8 -8
  57. package/src/modules/paypal/models/paypal_webhook_event.ts +19 -19
  58. package/src/modules/paypal/payment-provider/README.md +22 -22
  59. package/src/modules/paypal/payment-provider/card-service.ts +760 -760
  60. package/src/modules/paypal/payment-provider/index.ts +19 -19
  61. package/src/modules/paypal/payment-provider/service.ts +1121 -1121
  62. package/src/modules/paypal/payment-provider/webhook-utils.ts +88 -88
  63. package/src/modules/paypal/service.ts +1247 -1247
  64. package/src/modules/paypal/types/config.ts +47 -47
  65. package/src/modules/paypal/utils/amounts.ts +41 -41
  66. package/src/modules/paypal/utils/crypto.ts +51 -51
  67. package/src/modules/paypal/utils/currencies.ts +84 -84
  68. package/src/modules/paypal/utils/paypal-auth.ts +32 -32
  69. package/src/modules/paypal/utils/provider-ids.ts +15 -15
  70. 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
- const lineSubtotal = Number(item?.subtotal ?? (Number(item?.unit_price || 0) * quantity))
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((lineSubtotal / quantity).toFixed(exponent)) : 0
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
- let adjustedItemTotal = subtotalMajor
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
+ }