@easypayment/medusa-paypal 0.6.3 → 0.6.4
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 +12 -15
- package/.medusa/server/src/admin/index.mjs +12 -15
- 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.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 +1 -11
- package/.medusa/server/src/api/store/paypal/capture-order/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 +0 -9
- package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/webhook/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/webhook/route.js +162 -115
- package/.medusa/server/src/api/store/paypal/webhook/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 -6
- package/.medusa/server/src/api/store/paypal-complete/route.js.map +1 -1
- package/.medusa/server/src/jobs/paypal-webhook-retry.d.ts.map +1 -1
- package/.medusa/server/src/jobs/paypal-webhook-retry.js +97 -43
- package/.medusa/server/src/jobs/paypal-webhook-retry.js.map +1 -1
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.js +20 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.js.map +1 -0
- package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/service.js +0 -42
- package/.medusa/server/src/modules/paypal/payment-provider/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/service.d.ts +0 -8
- package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/service.js +6 -114
- package/.medusa/server/src/modules/paypal/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/types/config.d.ts +0 -2
- package/.medusa/server/src/modules/paypal/types/config.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/types/config.js +0 -9
- package/.medusa/server/src/modules/paypal/types/config.js.map +1 -1
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts +21 -17
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/webhook-processor.js +195 -99
- package/.medusa/server/src/modules/paypal/webhook-processor.js.map +1 -1
- package/README.md +156 -152
- package/package.json +1 -1
- package/src/admin/routes/settings/paypal/_components/Tabs.tsx +48 -52
- package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +0 -23
- package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +56 -65
- package/src/api/store/paypal/capture-order/route.ts +266 -276
- package/src/api/store/paypal/create-order/route.ts +0 -9
- package/src/api/store/paypal/webhook/route.ts +325 -246
- package/src/api/store/paypal-complete/route.ts +69 -75
- package/src/jobs/paypal-webhook-retry.ts +149 -85
- package/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.ts +17 -0
- package/src/modules/paypal/payment-provider/service.ts +1079 -1121
- package/src/modules/paypal/service.ts +6 -127
- package/src/modules/paypal/types/config.ts +33 -47
- package/src/modules/paypal/webhook-processor.ts +377 -215
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.d.ts +0 -3
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.d.ts.map +0 -1
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.js +0 -9
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.js.map +0 -1
- package/.medusa/server/src/jobs/paypal-reconcile.d.ts +0 -7
- package/.medusa/server/src/jobs/paypal-reconcile.d.ts.map +0 -1
- package/.medusa/server/src/jobs/paypal-reconcile.js +0 -109
- package/.medusa/server/src/jobs/paypal-reconcile.js.map +0 -1
- package/.medusa/server/src/modules/paypal/utils/crypto.d.ts +0 -4
- package/.medusa/server/src/modules/paypal/utils/crypto.d.ts.map +0 -1
- package/.medusa/server/src/modules/paypal/utils/crypto.js +0 -47
- package/.medusa/server/src/modules/paypal/utils/crypto.js.map +0 -1
- package/src/api/admin/paypal/rotate-credentials/route.ts +0 -8
- package/src/jobs/paypal-reconcile.ts +0 -113
- package/src/modules/paypal/utils/crypto.ts +0 -51
|
@@ -1,215 +1,377 @@
|
|
|
1
|
-
import type { MedusaContainer } from "@medusajs/framework/types"
|
|
2
|
-
import { Modules } from "@medusajs/framework/utils"
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export const EVENT_STATUS_MAP: Record<
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"PAYMENT.CAPTURE.
|
|
14
|
-
"PAYMENT.
|
|
15
|
-
"PAYMENT.
|
|
16
|
-
"PAYMENT.
|
|
17
|
-
"PAYMENT.
|
|
18
|
-
"PAYMENT.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"PAYMENT.
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
1
|
+
import type { MedusaContainer } from "@medusajs/framework/types"
|
|
2
|
+
import { Modules } from "@medusajs/framework/utils"
|
|
3
|
+
import { isPayPalProviderId } from "./utils/provider-ids"
|
|
4
|
+
|
|
5
|
+
// ─── Event → Medusa status mapping ───────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export const EVENT_STATUS_MAP: Record<
|
|
8
|
+
string,
|
|
9
|
+
"authorized" | "captured" | "canceled" | "error"
|
|
10
|
+
> = {
|
|
11
|
+
"CHECKOUT.ORDER.APPROVED": "authorized",
|
|
12
|
+
"CHECKOUT.ORDER.CANCELLED": "canceled",
|
|
13
|
+
"PAYMENT.CAPTURE.COMPLETED": "captured",
|
|
14
|
+
"PAYMENT.CAPTURE.DENIED": "error",
|
|
15
|
+
"PAYMENT.CAPTURE.PENDING": "authorized",
|
|
16
|
+
"PAYMENT.CAPTURE.REFUNDED": "canceled",
|
|
17
|
+
"PAYMENT.CAPTURE.REVERSED": "canceled",
|
|
18
|
+
"PAYMENT.AUTHORIZATION.CREATED": "authorized",
|
|
19
|
+
"PAYMENT.AUTHORIZATION.VOIDED": "canceled",
|
|
20
|
+
"PAYMENT.AUTHORIZATION.DENIED": "error",
|
|
21
|
+
"PAYMENT.AUTHORIZATION.EXPIRED": "canceled",
|
|
22
|
+
"PAYMENT.REFUND.COMPLETED": "canceled",
|
|
23
|
+
"PAYMENT.REFUND.DENIED": "error",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── Status transition guard ──────────────────────────────────────────────────
|
|
27
|
+
// Only allow forward/meaningful moves.
|
|
28
|
+
// Prevents a late-arriving webhook from downgrading an already-captured payment.
|
|
29
|
+
|
|
30
|
+
const ALLOWED_TRANSITIONS: Record<string, Set<string>> = {
|
|
31
|
+
pending: new Set(["authorized", "captured", "canceled", "error"]),
|
|
32
|
+
authorized: new Set(["captured", "canceled", "error"]),
|
|
33
|
+
captured: new Set(["canceled"]),
|
|
34
|
+
canceled: new Set([]),
|
|
35
|
+
error: new Set(["authorized", "captured", "canceled"]),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isTransitionAllowed(from: string, to: string): boolean {
|
|
39
|
+
return ALLOWED_TRANSITIONS[from]?.has(to) ?? false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Event type helpers ───────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
export const SUPPORTED_EVENT_PREFIXES = [
|
|
45
|
+
"PAYMENT.CAPTURE.",
|
|
46
|
+
"CHECKOUT.ORDER.",
|
|
47
|
+
"PAYMENT.AUTHORIZATION.",
|
|
48
|
+
"PAYMENT.REFUND.",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
export function isAllowedEventType(eventType: string): boolean {
|
|
52
|
+
return SUPPORTED_EVENT_PREFIXES.some((prefix) => eventType.startsWith(prefix))
|
|
53
|
+
}
|
|
54
|
+
|
|
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
|
+
|
|
59
|
+
const NON_RETRYABLE_PATTERNS = [
|
|
60
|
+
"payment collection not found",
|
|
61
|
+
"no paypal session",
|
|
62
|
+
"session not found",
|
|
63
|
+
"cart not found",
|
|
64
|
+
"no payment collection",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
export function isRetryableError(error: unknown): boolean {
|
|
68
|
+
const message = String(
|
|
69
|
+
error instanceof Error ? error.message : error ?? ""
|
|
70
|
+
).toLowerCase()
|
|
71
|
+
return !NON_RETRYABLE_PATTERNS.some((p) => message.includes(p))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ─── Retry schedule ───────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
const RETRY_SCHEDULE_MINUTES = [2, 10, 30, 60, 120]
|
|
77
|
+
export const MAX_WEBHOOK_ATTEMPTS = RETRY_SCHEDULE_MINUTES.length + 1
|
|
78
|
+
|
|
79
|
+
export function computeNextRetryAt(attemptCount: number): Date | null {
|
|
80
|
+
const idx = attemptCount - 1
|
|
81
|
+
const delayMinutes = RETRY_SCHEDULE_MINUTES[idx]
|
|
82
|
+
if (delayMinutes === undefined || attemptCount <= 0) return null
|
|
83
|
+
return new Date(Date.now() + delayMinutes * 60 * 1000)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── Payload normalisation ────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
export function normalizeResource(payload: Record<string, any>): Record<string, any> {
|
|
89
|
+
const resource = payload?.resource
|
|
90
|
+
if (!resource) return {}
|
|
91
|
+
if (typeof resource === "string") {
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(resource)
|
|
94
|
+
} catch {
|
|
95
|
+
return {}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return resource as Record<string, any>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function normalizeEventVersion(payload: Record<string, any>): string | null {
|
|
102
|
+
const raw =
|
|
103
|
+
payload?.event_version ??
|
|
104
|
+
payload?.resource_version ??
|
|
105
|
+
payload?.resource?.resource_version ??
|
|
106
|
+
payload?.resource?.version ??
|
|
107
|
+
null
|
|
108
|
+
if (!raw) return null
|
|
109
|
+
return String(raw).trim().replace(/^v/i, "")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─── Identifier extraction ────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
export interface ExtractedIdentifiers {
|
|
115
|
+
orderId: string | null
|
|
116
|
+
captureId: string | null
|
|
117
|
+
refundId: string | null
|
|
118
|
+
cartId: string | null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function extractIdentifiers(
|
|
122
|
+
resource: Record<string, any>,
|
|
123
|
+
eventType: string
|
|
124
|
+
): ExtractedIdentifiers {
|
|
125
|
+
const related = resource?.supplementary_data?.related_ids || {}
|
|
126
|
+
const isOrder = eventType.startsWith("CHECKOUT.ORDER.")
|
|
127
|
+
const isCapture = eventType.startsWith("PAYMENT.CAPTURE.")
|
|
128
|
+
const isAuthorization = eventType.startsWith("PAYMENT.AUTHORIZATION.")
|
|
129
|
+
const isRefund = eventType.startsWith("PAYMENT.REFUND.")
|
|
130
|
+
|
|
131
|
+
let orderId: string | null = null
|
|
132
|
+
let captureId: string | null = null
|
|
133
|
+
let refundId: string | null = null
|
|
134
|
+
let cartId: string | null = null
|
|
135
|
+
|
|
136
|
+
if (isOrder) {
|
|
137
|
+
orderId = String(resource?.id || "").trim() || null
|
|
138
|
+
cartId =
|
|
139
|
+
String(
|
|
140
|
+
resource?.purchase_units?.[0]?.custom_id || resource?.custom_id || ""
|
|
141
|
+
).trim() || null
|
|
142
|
+
captureId =
|
|
143
|
+
String(
|
|
144
|
+
resource?.purchase_units?.[0]?.payments?.captures?.[0]?.id || ""
|
|
145
|
+
).trim() || null
|
|
146
|
+
} else if (isCapture) {
|
|
147
|
+
captureId = String(resource?.id || "").trim() || null
|
|
148
|
+
orderId = String(related?.order_id || "").trim() || null
|
|
149
|
+
cartId = String(resource?.custom_id || "").trim() || null
|
|
150
|
+
} else if (isAuthorization) {
|
|
151
|
+
orderId = String(related?.order_id || "").trim() || null
|
|
152
|
+
cartId = String(resource?.custom_id || "").trim() || null
|
|
153
|
+
} else if (isRefund) {
|
|
154
|
+
refundId = String(resource?.id || "").trim() || null
|
|
155
|
+
orderId = String(related?.order_id || "").trim() || null
|
|
156
|
+
captureId = String(related?.capture_id || "").trim() || null
|
|
157
|
+
cartId = null
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { orderId, captureId, refundId, cartId }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─── Session lookup ───────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
interface ResolvedSession {
|
|
166
|
+
sessionId: string
|
|
167
|
+
sessionData: Record<string, any>
|
|
168
|
+
sessionStatus: string
|
|
169
|
+
collectionId: string
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function findPayPalSession(
|
|
173
|
+
container: MedusaContainer,
|
|
174
|
+
cartId: string
|
|
175
|
+
): Promise<ResolvedSession | null> {
|
|
176
|
+
const paymentModule = container.resolve(Modules.PAYMENT) as any
|
|
177
|
+
|
|
178
|
+
let collections: any[]
|
|
179
|
+
try {
|
|
180
|
+
collections = await paymentModule.listPaymentCollections(
|
|
181
|
+
{ cart_id: [cartId] },
|
|
182
|
+
{ take: 1 }
|
|
183
|
+
)
|
|
184
|
+
} catch (e: any) {
|
|
185
|
+
throw new Error(`payment collection not found for cart ${cartId}: ${e?.message}`)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const collection = collections?.[0]
|
|
189
|
+
if (!collection?.id) {
|
|
190
|
+
throw new Error(`payment collection not found for cart ${cartId}`)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const sessions = await paymentModule.listPaymentSessions({
|
|
194
|
+
payment_collection_id: collection.id,
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
const paypalSession = (sessions || [])
|
|
198
|
+
.filter((s: any) => isPayPalProviderId(s.provider_id))
|
|
199
|
+
.sort(
|
|
200
|
+
(a: any, b: any) =>
|
|
201
|
+
new Date(b.created_at || 0).getTime() -
|
|
202
|
+
new Date(a.created_at || 0).getTime()
|
|
203
|
+
)[0]
|
|
204
|
+
|
|
205
|
+
if (!paypalSession) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`no paypal session found in collection ${collection.id} for cart ${cartId}`
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
sessionId: paypalSession.id,
|
|
213
|
+
sessionData: (paypalSession.data || {}) as Record<string, any>,
|
|
214
|
+
sessionStatus: String(paypalSession.status || "pending"),
|
|
215
|
+
collectionId: collection.id,
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ─── Session update ───────────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
function mergeRefunds(existing: any[], incoming: any[]): any[] {
|
|
222
|
+
const seen = new Set<string>()
|
|
223
|
+
const merged: any[] = []
|
|
224
|
+
for (const refund of [...existing, ...incoming]) {
|
|
225
|
+
const id = String(refund?.id || "")
|
|
226
|
+
if (id && seen.has(id)) continue
|
|
227
|
+
if (id) seen.add(id)
|
|
228
|
+
merged.push(refund)
|
|
229
|
+
}
|
|
230
|
+
return merged
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function applyStatusToSession(
|
|
234
|
+
container: MedusaContainer,
|
|
235
|
+
resolved: ResolvedSession,
|
|
236
|
+
status: string,
|
|
237
|
+
patch: Record<string, unknown>
|
|
238
|
+
): Promise<void> {
|
|
239
|
+
const paymentModule = container.resolve(Modules.PAYMENT) as any
|
|
240
|
+
|
|
241
|
+
if (!isTransitionAllowed(resolved.sessionStatus, status)) {
|
|
242
|
+
console.info(
|
|
243
|
+
`[PayPal] webhook: skipping disallowed transition ${resolved.sessionStatus} → ${status} for session ${resolved.sessionId}`
|
|
244
|
+
)
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const existingPaypal = (resolved.sessionData.paypal || {}) as Record<string, any>
|
|
249
|
+
const existingRefunds = Array.isArray(existingPaypal.refunds)
|
|
250
|
+
? existingPaypal.refunds
|
|
251
|
+
: []
|
|
252
|
+
const incomingRefunds = Array.isArray(patch.refunds)
|
|
253
|
+
? (patch.refunds as any[])
|
|
254
|
+
: null
|
|
255
|
+
const nextRefunds = incomingRefunds
|
|
256
|
+
? mergeRefunds(existingRefunds, incomingRefunds)
|
|
257
|
+
: existingRefunds
|
|
258
|
+
|
|
259
|
+
await paymentModule.updatePaymentSession({
|
|
260
|
+
id: resolved.sessionId,
|
|
261
|
+
status,
|
|
262
|
+
data: {
|
|
263
|
+
...resolved.sessionData,
|
|
264
|
+
paypal: {
|
|
265
|
+
...existingPaypal,
|
|
266
|
+
...patch,
|
|
267
|
+
refunds: nextRefunds,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ─── Main event processor ─────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
export interface ProcessResult {
|
|
276
|
+
orderId: string | null
|
|
277
|
+
captureId: string | null
|
|
278
|
+
refundId: string | null
|
|
279
|
+
cartId: string | null
|
|
280
|
+
sessionUpdated: boolean
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export async function processPayPalWebhookEvent(
|
|
284
|
+
container: MedusaContainer,
|
|
285
|
+
input: {
|
|
286
|
+
eventType: string
|
|
287
|
+
payload: Record<string, any>
|
|
288
|
+
}
|
|
289
|
+
): Promise<ProcessResult> {
|
|
290
|
+
const resource = normalizeResource(input.payload)
|
|
291
|
+
const { orderId, captureId, refundId, cartId: rawCartId } = extractIdentifiers(
|
|
292
|
+
resource,
|
|
293
|
+
input.eventType
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
const refundReason =
|
|
297
|
+
String(
|
|
298
|
+
resource?.note_to_payer || resource?.reason || resource?.seller_note || ""
|
|
299
|
+
).trim() || undefined
|
|
300
|
+
const refundReasonCode =
|
|
301
|
+
String(resource?.reason_code || resource?.reasonCode || "").trim() ||
|
|
302
|
+
undefined
|
|
303
|
+
|
|
304
|
+
const targetStatus = EVENT_STATUS_MAP[input.eventType]
|
|
305
|
+
if (!targetStatus) {
|
|
306
|
+
return { orderId, captureId, refundId, cartId: rawCartId, sessionUpdated: false }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let cartId = rawCartId
|
|
310
|
+
|
|
311
|
+
if (!cartId) {
|
|
312
|
+
try {
|
|
313
|
+
const paymentModule = container.resolve(Modules.PAYMENT) as any
|
|
314
|
+
const allSessions = await paymentModule.listPaymentSessions({
|
|
315
|
+
provider_id: ["pp_paypal_paypal", "pp_paypal_card_paypal_card"],
|
|
316
|
+
})
|
|
317
|
+
const matchedSession = (allSessions || []).find((s: any) => {
|
|
318
|
+
const pp = ((s.data || {}) as Record<string, any>).paypal || {}
|
|
319
|
+
if (orderId && pp.order_id === orderId) return true
|
|
320
|
+
if (captureId && pp.capture_id === captureId) return true
|
|
321
|
+
return false
|
|
322
|
+
})
|
|
323
|
+
if (matchedSession?.payment_collection_id) {
|
|
324
|
+
const colls = await paymentModule.listPaymentCollections(
|
|
325
|
+
{ id: [matchedSession.payment_collection_id] },
|
|
326
|
+
{ take: 1 }
|
|
327
|
+
)
|
|
328
|
+
cartId = String(colls?.[0]?.cart_id || "").trim() || null
|
|
329
|
+
}
|
|
330
|
+
} catch (e: any) {
|
|
331
|
+
console.warn(
|
|
332
|
+
`[PayPal] webhook: cartId fallback lookup failed for ${input.eventType}:`,
|
|
333
|
+
e?.message
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let sessionUpdated = false
|
|
339
|
+
|
|
340
|
+
if (cartId) {
|
|
341
|
+
const resolved = await findPayPalSession(container, cartId)
|
|
342
|
+
if (resolved) {
|
|
343
|
+
const refundEntry = refundId
|
|
344
|
+
? [
|
|
345
|
+
{
|
|
346
|
+
id: refundId,
|
|
347
|
+
status: resource?.status,
|
|
348
|
+
reason: refundReason,
|
|
349
|
+
reason_code: refundReasonCode,
|
|
350
|
+
amount: resource?.amount,
|
|
351
|
+
raw: resource,
|
|
352
|
+
},
|
|
353
|
+
]
|
|
354
|
+
: null
|
|
355
|
+
|
|
356
|
+
await applyStatusToSession(container, resolved, targetStatus, {
|
|
357
|
+
order_id: orderId,
|
|
358
|
+
capture_id: captureId ?? resolved.sessionData.paypal?.capture_id ?? undefined,
|
|
359
|
+
refund_id: refundId,
|
|
360
|
+
refund_status: refundId ? resource?.status : undefined,
|
|
361
|
+
refund_reason: refundReason,
|
|
362
|
+
refund_reason_code: refundReasonCode,
|
|
363
|
+
...(refundEntry ? { refunds: refundEntry } : {}),
|
|
364
|
+
webhook_event_type: input.eventType,
|
|
365
|
+
last_webhook_at: new Date().toISOString(),
|
|
366
|
+
})
|
|
367
|
+
sessionUpdated = true
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
console.warn(
|
|
371
|
+
`[PayPal] webhook: could not resolve cartId for event ${input.eventType}`,
|
|
372
|
+
{ orderId, captureId, refundId }
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return { orderId, captureId, refundId, cartId, sessionUpdated }
|
|
377
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/api/admin/paypal/rotate-credentials/route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAG7E,wBAAsB,IAAI,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,cAAc,2BAIjE"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.POST = POST;
|
|
4
|
-
async function POST(req, res) {
|
|
5
|
-
const paypal = req.scope.resolve("paypal_onboarding");
|
|
6
|
-
const result = await paypal.rotateCredentialEncryptionKey();
|
|
7
|
-
return res.json(result);
|
|
8
|
-
}
|
|
9
|
-
//# sourceMappingURL=route.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"route.js","sourceRoot":"","sources":["../../../../../../../src/api/admin/paypal/rotate-credentials/route.ts"],"names":[],"mappings":";;AAGA,oBAIC;AAJM,KAAK,UAAU,IAAI,CAAC,GAAkB,EAAE,GAAmB;IAChE,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAsB,mBAAmB,CAAC,CAAA;IAC1E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,6BAA6B,EAAE,CAAA;IAC3D,OAAO,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;AACzB,CAAC"}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { MedusaContainer } from "@medusajs/framework/types";
|
|
2
|
-
export default function paypalReconcile(container: MedusaContainer): Promise<void>;
|
|
3
|
-
export declare const config: {
|
|
4
|
-
name: string;
|
|
5
|
-
schedule: string;
|
|
6
|
-
};
|
|
7
|
-
//# sourceMappingURL=paypal-reconcile.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"paypal-reconcile.d.ts","sourceRoot":"","sources":["../../../../src/jobs/paypal-reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAchE,wBAA8B,eAAe,CAAC,SAAS,EAAE,eAAe,iBA6FvE;AAED,eAAO,MAAM,MAAM;;;CAGlB,CAAA"}
|