@easypayment/medusa-paypal 0.6.7 → 0.6.9

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.
@@ -24,7 +24,7 @@ import type {
24
24
  ProviderWebhookPayload,
25
25
  WebhookActionResult,
26
26
  } from "@medusajs/framework/types"
27
- import { formatAmountForPayPal } from "../utils/amounts"
27
+ import { formatAmountForPayPal, getCurrencyExponent } from "../utils/amounts"
28
28
  import {
29
29
  assertPayPalCurrencySupported,
30
30
  normalizeCurrencyCode,
@@ -709,12 +709,13 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
709
709
  data.is_final_capture ??
710
710
  data.final_capture ??
711
711
  undefined
712
+ const captureExponent = getCurrencyExponent(currencyCode || "EUR")
712
713
  const capturePayload =
713
714
  amount > 0
714
715
  ? {
715
716
  amount: {
716
717
  currency_code: currencyCode || "EUR",
717
- value: formatAmountForPayPal(amount, currencyCode || "EUR"),
718
+ value: amount.toFixed(captureExponent),
718
719
  },
719
720
  ...(typeof isFinalCapture === "boolean"
720
721
  ? { is_final_capture: isFinalCapture }
@@ -816,17 +817,36 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
816
817
  }
817
818
 
818
819
  const requestId = this.getIdempotencyKey(input, `refund-${captureId}`)
819
- const { amount, currencyCode } = await this.normalizePaymentData(input)
820
+
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
+ const currencyOverride = await this.resolveCurrencyOverride()
827
+ const currencyCode = normalizeCurrencyCode(
828
+ data.currency_code || currencyOverride || "EUR"
829
+ )
830
+
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
+ const exponent = currencyCode.toUpperCase() === "JPY" ? 0
835
+ : ["BHD", "JOD", "KWD", "OMR", "TND"].includes(currencyCode.toUpperCase()) ? 3
836
+ : 2
837
+ const refundAmount = Number(input.amount ?? 0)
838
+ const refundValue = refundAmount > 0 ? refundAmount.toFixed(exponent) : null
839
+
820
840
  let debugId: string | null = null
821
841
 
822
842
  try {
823
843
  const { accessToken, base } = await this.getPayPalAccessToken()
824
844
  const refundPayload: Record<string, any> =
825
- amount > 0
845
+ refundValue
826
846
  ? {
827
847
  amount: {
828
848
  currency_code: currencyCode || "EUR",
829
- value: formatAmountForPayPal(amount, currencyCode || "EUR"),
849
+ value: refundValue,
830
850
  },
831
851
  }
832
852
  : {}
@@ -870,7 +890,7 @@ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
870
890
  await this.recordPaymentEvent("refund", {
871
891
  capture_id: captureId,
872
892
  refund_id: refund?.id,
873
- amount,
893
+ amount: refundAmount,
874
894
  currency_code: currencyCode,
875
895
  request_id: requestId,
876
896
  reason: refundReason,
@@ -1,15 +1,12 @@
1
- import type { MedusaContainer } from "@medusajs/framework/types"
1
+ import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
2
2
  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
- export default async function paypalOrderInvoiceSubscriber({
6
+ export default async function paypalOrderInvoiceHandler({
7
7
  event,
8
8
  container,
9
- }: {
10
- event: { data: { id: string } }
11
- container: MedusaContainer
12
- }) {
9
+ }: SubscriberArgs<{ id: string }>) {
13
10
  const orderId = event?.data?.id
14
11
  if (!orderId) return
15
12
 
@@ -27,6 +24,7 @@ export default async function paypalOrderInvoiceSubscriber({
27
24
  "payment_collections.payment_sessions.data",
28
25
  "payment_collections.payment_sessions.provider_id",
29
26
  "payment_collections.payment_sessions.status",
27
+ "payment_collections.payment_sessions.created_at",
30
28
  ],
31
29
  filters: { id: orderId },
32
30
  })
@@ -35,21 +33,29 @@ export default async function paypalOrderInvoiceSubscriber({
35
33
  if (!order) return
36
34
 
37
35
  // Find the PayPal session
38
- const sessions = order.payment_collections?.flatMap(
36
+ const sessions = (order.payment_collections || []).flatMap(
39
37
  (pc: any) => pc.payment_sessions || []
40
- ) || []
38
+ )
41
39
 
42
40
  const paypalSession = sessions
43
41
  .filter((s: any) => isPayPalProviderId(s.provider_id))
44
- .sort((a: any, b: any) =>
45
- new Date(b.created_at || 0).getTime() - new Date(a.created_at || 0).getTime()
42
+ .sort(
43
+ (a: any, b: any) =>
44
+ new Date(b.created_at || 0).getTime() -
45
+ new Date(a.created_at || 0).getTime()
46
46
  )[0]
47
47
 
48
- if (!paypalSession) return
48
+ if (!paypalSession) {
49
+ console.info("[PayPal] invoice subscriber: no PayPal session found for order", orderId)
50
+ return
51
+ }
49
52
 
50
- const paypalData = (paypalSession.data?.paypal || {}) as Record<string, any>
53
+ const paypalData = ((paypalSession.data || {}).paypal || {}) as Record<string, any>
51
54
  const paypalOrderId = String(paypalData.order_id || "")
52
- if (!paypalOrderId) return
55
+ if (!paypalOrderId) {
56
+ console.info("[PayPal] invoice subscriber: no PayPal order_id in session for order", orderId)
57
+ return
58
+ }
53
59
 
54
60
  // Get invoice prefix from settings
55
61
  const settings = await paypal.getSettings().catch(() => ({}))
@@ -64,7 +70,7 @@ export default async function paypalOrderInvoiceSubscriber({
64
70
  : ""
65
71
 
66
72
  // Build industry-standard invoice ID: prefix + order display_id
67
- // e.g. "WC-139" or "ORD-139"
73
+ // e.g. "WC-140" or "ORD-140"
68
74
  const displayId = String(order.display_id || "")
69
75
  const invoiceId = `${invoicePrefix}${displayId}`.trim()
70
76
  if (!invoiceId) return
@@ -90,20 +96,20 @@ export default async function paypalOrderInvoiceSubscriber({
90
96
 
91
97
  if (patchResp.ok || patchResp.status === 204) {
92
98
  console.info(
93
- `[PayPal] invoice_id updated to "${invoiceId}" for PayPal order ${paypalOrderId} (Medusa order ${orderId})`
99
+ `[PayPal] invoice_id updated to "${invoiceId}" for PayPal order ${paypalOrderId} (Medusa order #${displayId})`
94
100
  )
95
101
  } else {
96
102
  const text = await patchResp.text().catch(() => "")
97
103
  console.warn(
98
- `[PayPal] invoice_id patch failed (${patchResp.status}) for order ${paypalOrderId}: ${text}`
104
+ `[PayPal] invoice_id patch failed (${patchResp.status}) for PayPal order ${paypalOrderId}: ${text}`
99
105
  )
100
106
  }
101
107
  } catch (e: any) {
102
108
  // Non-fatal — never block order placement
103
- console.warn("[PayPal] paypalOrderInvoiceSubscriber error:", e?.message || e)
109
+ console.warn("[PayPal] paypalOrderInvoiceHandler error:", e?.message || e)
104
110
  }
105
111
  }
106
112
 
107
- export const config = {
113
+ export const config: SubscriberConfig = {
108
114
  event: "order.placed",
109
115
  }