@easypayment/medusa-paypal 0.6.8 → 0.7.0
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/create-order/route.js +1 -1
- package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -1
- 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 +4 -2
- package/.medusa/server/src/modules/paypal/payment-provider/card-service.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 +2 -1
- package/.medusa/server/src/modules/paypal/payment-provider/service.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 +56 -26
- package/.medusa/server/src/subscribers/paypal-order-invoice.js.map +1 -1
- package/README.md +6 -8
- package/package.json +1 -1
- package/src/api/store/paypal/create-order/route.ts +1 -1
- package/src/modules/paypal/payment-provider/card-service.ts +762 -760
- package/src/modules/paypal/payment-provider/service.ts +1051 -1050
- package/src/subscribers/paypal-order-invoice.ts +172 -115
|
@@ -1,115 +1,172 @@
|
|
|
1
|
-
import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
|
|
2
|
-
import type PayPalModuleService from "../modules/paypal/service"
|
|
3
|
-
import { getPayPalAccessToken } from "../modules/paypal/utils/paypal-auth"
|
|
4
|
-
import { isPayPalProviderId } from "../modules/paypal/utils/provider-ids"
|
|
5
|
-
|
|
6
|
-
export default async function paypalOrderInvoiceHandler({
|
|
7
|
-
event,
|
|
8
|
-
container,
|
|
9
|
-
}: SubscriberArgs<{ id: string }>) {
|
|
10
|
-
const orderId = event?.data?.id
|
|
11
|
-
if (!orderId) return
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const query = container.resolve("query") as any
|
|
15
|
-
const paypal = container.resolve<PayPalModuleService>("paypal_onboarding")
|
|
16
|
-
|
|
17
|
-
// Fetch the order with payment session data
|
|
18
|
-
const { data: orders } = await query.graph({
|
|
19
|
-
entity: "order",
|
|
20
|
-
fields: [
|
|
21
|
-
"id",
|
|
22
|
-
"display_id",
|
|
23
|
-
"payment_collections.payment_sessions.id",
|
|
24
|
-
"payment_collections.payment_sessions.data",
|
|
25
|
-
"payment_collections.payment_sessions.provider_id",
|
|
26
|
-
"payment_collections.payment_sessions.status",
|
|
27
|
-
"payment_collections.payment_sessions.created_at",
|
|
28
|
-
],
|
|
29
|
-
filters: { id: orderId },
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
const order = orders?.[0]
|
|
33
|
-
if (!order) return
|
|
34
|
-
|
|
35
|
-
// Find the PayPal session
|
|
36
|
-
const sessions = (order.payment_collections || []).flatMap(
|
|
37
|
-
(pc: any) => pc.payment_sessions || []
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
new Date(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
1
|
+
import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework"
|
|
2
|
+
import type PayPalModuleService from "../modules/paypal/service"
|
|
3
|
+
import { getPayPalAccessToken } from "../modules/paypal/utils/paypal-auth"
|
|
4
|
+
import { isPayPalProviderId } from "../modules/paypal/utils/provider-ids"
|
|
5
|
+
|
|
6
|
+
export default async function paypalOrderInvoiceHandler({
|
|
7
|
+
event,
|
|
8
|
+
container,
|
|
9
|
+
}: SubscriberArgs<{ id: string }>) {
|
|
10
|
+
const orderId = event?.data?.id
|
|
11
|
+
if (!orderId) return
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const query = container.resolve("query") as any
|
|
15
|
+
const paypal = container.resolve<PayPalModuleService>("paypal_onboarding")
|
|
16
|
+
|
|
17
|
+
// Fetch the Medusa order with payment session data
|
|
18
|
+
const { data: orders } = await query.graph({
|
|
19
|
+
entity: "order",
|
|
20
|
+
fields: [
|
|
21
|
+
"id",
|
|
22
|
+
"display_id",
|
|
23
|
+
"payment_collections.payment_sessions.id",
|
|
24
|
+
"payment_collections.payment_sessions.data",
|
|
25
|
+
"payment_collections.payment_sessions.provider_id",
|
|
26
|
+
"payment_collections.payment_sessions.status",
|
|
27
|
+
"payment_collections.payment_sessions.created_at",
|
|
28
|
+
],
|
|
29
|
+
filters: { id: orderId },
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const order = orders?.[0]
|
|
33
|
+
if (!order) return
|
|
34
|
+
|
|
35
|
+
// Find the PayPal payment session
|
|
36
|
+
const sessions = (order.payment_collections || []).flatMap(
|
|
37
|
+
(pc: any) => pc.payment_sessions || []
|
|
38
|
+
)
|
|
39
|
+
const paypalSession = sessions
|
|
40
|
+
.filter((s: any) => isPayPalProviderId(s.provider_id))
|
|
41
|
+
.sort(
|
|
42
|
+
(a: any, b: any) =>
|
|
43
|
+
new Date(b.created_at || 0).getTime() -
|
|
44
|
+
new Date(a.created_at || 0).getTime()
|
|
45
|
+
)[0]
|
|
46
|
+
|
|
47
|
+
if (!paypalSession) {
|
|
48
|
+
console.info(
|
|
49
|
+
"[PayPal] invoice subscriber: no PayPal session for order",
|
|
50
|
+
orderId
|
|
51
|
+
)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const paypalData = (
|
|
56
|
+
(paypalSession.data || {}).paypal || {}
|
|
57
|
+
) as Record<string, any>
|
|
58
|
+
const paypalOrderId = String(paypalData.order_id || "")
|
|
59
|
+
|
|
60
|
+
if (!paypalOrderId) {
|
|
61
|
+
console.info(
|
|
62
|
+
"[PayPal] invoice subscriber: no order_id in session for order",
|
|
63
|
+
orderId
|
|
64
|
+
)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Build invoice_id: invoicePrefix + display_id (industry standard)
|
|
69
|
+
const settings = await paypal.getSettings().catch(() => ({}))
|
|
70
|
+
const settingsData =
|
|
71
|
+
settings && typeof settings === "object" && "data" in settings
|
|
72
|
+
? ((settings as { data?: Record<string, any> }).data ?? {})
|
|
73
|
+
: {}
|
|
74
|
+
const additionalSettings = (
|
|
75
|
+
settingsData.additional_settings || {}
|
|
76
|
+
) as Record<string, any>
|
|
77
|
+
const invoicePrefix =
|
|
78
|
+
typeof additionalSettings.invoicePrefix === "string"
|
|
79
|
+
? additionalSettings.invoicePrefix
|
|
80
|
+
: ""
|
|
81
|
+
const displayId = String(order.display_id || "")
|
|
82
|
+
const invoiceId = `${invoicePrefix}${displayId}`.trim()
|
|
83
|
+
|
|
84
|
+
if (!invoiceId) return
|
|
85
|
+
|
|
86
|
+
// Get PayPal access token
|
|
87
|
+
const creds = await paypal.getActiveCredentials()
|
|
88
|
+
const { accessToken, base } = await getPayPalAccessToken(creds)
|
|
89
|
+
|
|
90
|
+
// ── HIGH-VOLUME GUARD ──────────────────────────────────────────────
|
|
91
|
+
// Check if invoice_id is already correct before sending a PATCH.
|
|
92
|
+
// Prevents redundant API calls and duplicate-key errors at scale.
|
|
93
|
+
try {
|
|
94
|
+
const currentResp = await fetch(
|
|
95
|
+
`${base}/v2/checkout/orders/${paypalOrderId}`,
|
|
96
|
+
{ headers: { Authorization: `Bearer ${accessToken}` } }
|
|
97
|
+
)
|
|
98
|
+
if (currentResp.ok) {
|
|
99
|
+
const currentOrder = await currentResp.json().catch(() => ({}))
|
|
100
|
+
const currentInvoiceId =
|
|
101
|
+
currentOrder?.purchase_units?.[0]?.invoice_id || ""
|
|
102
|
+
if (currentInvoiceId === invoiceId) {
|
|
103
|
+
console.info(
|
|
104
|
+
`[PayPal] invoice_id already "${invoiceId}" — skipping PATCH`
|
|
105
|
+
)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (e: any) {
|
|
110
|
+
// Non-fatal — proceed with PATCH anyway
|
|
111
|
+
console.warn("[PayPal] pre-PATCH order fetch failed:", e?.message)
|
|
112
|
+
}
|
|
113
|
+
// ──────────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
// Try two PATCH paths for robustness:
|
|
116
|
+
// Path 1: standard "default" reference_id (new orders after this fix)
|
|
117
|
+
// Path 2: array index fallback (orders created before this fix)
|
|
118
|
+
const patchPaths = [
|
|
119
|
+
"/purchase_units/@reference_id=='default'/invoice_id",
|
|
120
|
+
"/purchase_units/0/invoice_id",
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
let patched = false
|
|
124
|
+
for (const path of patchPaths) {
|
|
125
|
+
const patchResp = await fetch(
|
|
126
|
+
`${base}/v2/checkout/orders/${paypalOrderId}`,
|
|
127
|
+
{
|
|
128
|
+
method: "PATCH",
|
|
129
|
+
headers: {
|
|
130
|
+
Authorization: `Bearer ${accessToken}`,
|
|
131
|
+
"Content-Type": "application/json",
|
|
132
|
+
},
|
|
133
|
+
body: JSON.stringify([{ op: "replace", path, value: invoiceId }]),
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if (patchResp.ok || patchResp.status === 204) {
|
|
138
|
+
console.info(
|
|
139
|
+
`[PayPal] invoice_id set to "${invoiceId}"`,
|
|
140
|
+
`(PayPal order ${paypalOrderId} / Medusa #${displayId})`,
|
|
141
|
+
`via path: ${path}`
|
|
142
|
+
)
|
|
143
|
+
patched = true
|
|
144
|
+
break
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const errText = await patchResp.text().catch(() => "")
|
|
148
|
+
console.warn("[PayPal] invoice_id PATCH attempt failed", {
|
|
149
|
+
status: patchResp.status,
|
|
150
|
+
path,
|
|
151
|
+
paypalOrderId,
|
|
152
|
+
errText,
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!patched) {
|
|
157
|
+
// Hard error log — failures must never be silently swallowed at scale
|
|
158
|
+
console.error(
|
|
159
|
+
"[PayPal] CRITICAL: invoice_id PATCH failed for ALL paths.",
|
|
160
|
+
{ paypalOrderId, invoiceId, displayId }
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
} catch (e: any) {
|
|
165
|
+
// Non-fatal — never block order placement
|
|
166
|
+
console.warn("[PayPal] paypalOrderInvoiceHandler error:", e?.message || e)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const config: SubscriberConfig = {
|
|
171
|
+
event: "order.placed",
|
|
172
|
+
}
|