@easypayment/medusa-paypal 0.2.7 → 0.2.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 +752 -997
- package/.medusa/server/src/admin/index.mjs +752 -997
- 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 +1 -0
- 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 +61 -74
- 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 +3 -24
- package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/settings/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/settings/route.js +7 -1
- package/.medusa/server/src/api/store/paypal/settings/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 +46 -24
- package/.medusa/server/src/api/store/paypal-complete/route.js.map +1 -1
- package/.medusa/server/src/jobs/paypal-reconcile.d.ts.map +1 -1
- package/.medusa/server/src/jobs/paypal-reconcile.js +19 -5
- package/.medusa/server/src/jobs/paypal-reconcile.js.map +1 -1
- package/.medusa/server/src/modules/paypal/service.d.ts +67 -61
- package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/service.js +34 -4
- package/.medusa/server/src/modules/paypal/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.d.ts +14 -0
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.js +32 -0
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.js.map +1 -0
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts +9 -9
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/webhook-processor.js +20 -7
- package/.medusa/server/src/modules/paypal/webhook-processor.js.map +1 -1
- package/package.json +1 -1
- package/src/admin/routes/settings/paypal/additional-settings/page.tsx +226 -346
- package/src/admin/routes/settings/paypal/advanced-card-payments/page.tsx +227 -381
- package/src/admin/routes/settings/paypal/audit-logs/page.tsx +127 -131
- package/src/admin/routes/settings/paypal/disputes/page.tsx +186 -259
- package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +599 -557
- package/src/admin/routes/settings/paypal/reconciliation-status/page.tsx +120 -165
- package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +12 -1
- package/src/api/store/paypal/capture-order/route.ts +276 -284
- package/src/api/store/paypal/create-order/route.ts +2 -32
- package/src/api/store/paypal/settings/route.ts +8 -1
- package/src/api/store/paypal-complete/route.ts +75 -45
- package/src/jobs/paypal-reconcile.ts +21 -6
- package/src/modules/paypal/service.ts +39 -4
- package/src/modules/paypal/utils/paypal-auth.ts +32 -0
- package/src/modules/paypal/webhook-processor.ts +22 -8
- package/tsconfig.json +1 -1
|
@@ -1,46 +1,76 @@
|
|
|
1
|
-
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import type { IPaymentModuleService } from "@medusajs/framework/types"
|
|
3
|
+
import { Modules } from "@medusajs/framework/utils"
|
|
4
|
+
import type PayPalModuleService from "../../../modules/paypal/service"
|
|
5
|
+
|
|
6
|
+
export async function POST(req: MedusaRequest, res: MedusaResponse) {
|
|
7
|
+
const { cart_id } = req.body as { cart_id: string }
|
|
8
|
+
|
|
9
|
+
if (!cart_id) {
|
|
10
|
+
return res.status(400).json({ error: "cart_id is required" })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
// Step 1 — read paymentAction from DB settings (same pattern as capture-order)
|
|
15
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
16
|
+
const settings = await paypal.getSettings().catch(() => ({}))
|
|
17
|
+
const settingsData =
|
|
18
|
+
settings && typeof settings === "object" && "data" in settings
|
|
19
|
+
? ((settings as { data?: Record<string, any> }).data ?? {})
|
|
20
|
+
: {}
|
|
21
|
+
const additionalSettings = (settingsData.additional_settings || {}) as Record<string, any>
|
|
22
|
+
const paymentAction =
|
|
23
|
+
typeof additionalSettings.paymentAction === "string"
|
|
24
|
+
? additionalSettings.paymentAction
|
|
25
|
+
: "capture"
|
|
26
|
+
|
|
27
|
+
// "authorize" mode → session status = "authorized"
|
|
28
|
+
// "capture" mode → session status = "captured"
|
|
29
|
+
const sessionStatus = paymentAction === "authorize" ? "authorized" : "captured"
|
|
30
|
+
const timestampKey = paymentAction === "authorize" ? "authorized_at" : "captured_at"
|
|
31
|
+
|
|
32
|
+
// Step 2 — find the PayPal session for this cart
|
|
33
|
+
const query = req.scope.resolve("query")
|
|
34
|
+
const { data: carts } = await query.graph({
|
|
35
|
+
entity: "cart",
|
|
36
|
+
fields: [
|
|
37
|
+
"id",
|
|
38
|
+
"payment_collection.payment_sessions.id",
|
|
39
|
+
"payment_collection.payment_sessions.data",
|
|
40
|
+
"payment_collection.payment_sessions.provider_id",
|
|
41
|
+
"payment_collection.payment_sessions.created_at",
|
|
42
|
+
"payment_collection.payment_sessions.amount",
|
|
43
|
+
"payment_collection.payment_sessions.currency_code",
|
|
44
|
+
],
|
|
45
|
+
filters: { id: cart_id },
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const sessions = carts?.[0]?.payment_collection?.payment_sessions || []
|
|
49
|
+
const session = sessions
|
|
50
|
+
.filter((s: any) => String(s.provider_id || "").includes("paypal"))
|
|
51
|
+
.sort(
|
|
52
|
+
(a: any, b: any) =>
|
|
53
|
+
new Date(b.created_at || 0).getTime() - new Date(a.created_at || 0).getTime()
|
|
54
|
+
)[0]
|
|
55
|
+
|
|
56
|
+
if (!session) {
|
|
57
|
+
return res.status(400).json({ error: "No PayPal payment session found for cart" })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Step 3 — update session with correct status + amount
|
|
61
|
+
const paymentModule = req.scope.resolve(Modules.PAYMENT) as IPaymentModuleService
|
|
62
|
+
await (paymentModule as any).updatePaymentSession({
|
|
63
|
+
id: session.id,
|
|
64
|
+
data: { ...(session.data || {}), [timestampKey]: new Date().toISOString() },
|
|
65
|
+
status: sessionStatus as any,
|
|
66
|
+
amount: session.amount,
|
|
67
|
+
currency_code: session.currency_code,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
console.log(`[paypal-complete] session ${sessionStatus}:`, session.id)
|
|
71
|
+
return res.json({ success: true, session_id: session.id })
|
|
72
|
+
} catch (e: any) {
|
|
73
|
+
console.error("[paypal-complete] error:", e?.message || e)
|
|
74
|
+
return res.status(500).json({ error: e?.message || "Internal error" })
|
|
75
|
+
}
|
|
46
76
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { MedusaContainer } from "@medusajs/framework/types"
|
|
2
|
+
import { Modules } from "@medusajs/framework/utils"
|
|
2
3
|
import type PayPalModuleService from "../modules/paypal/service"
|
|
4
|
+
import { PAYPAL_MODULE } from "../modules/paypal"
|
|
5
|
+
import { PAYPAL_PROVIDER_IDS } from "../modules/paypal/utils/provider-ids"
|
|
3
6
|
|
|
4
7
|
const STATUS_MAP: Record<string, "pending" | "authorized" | "captured" | "canceled"> = {
|
|
5
8
|
CREATED: "pending",
|
|
@@ -10,11 +13,20 @@ const STATUS_MAP: Record<string, "pending" | "authorized" | "captured" | "cancel
|
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
export default async function paypalReconcile(container: MedusaContainer) {
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
// FIX 3a: was container.resolve("payment_session") as any
|
|
17
|
+
// Modules.PAYMENT is the official typed constant for the Medusa payment module.
|
|
18
|
+
// listPaymentSessions and updatePaymentSession are methods on the payment module service.
|
|
19
|
+
const paymentModule = container.resolve(Modules.PAYMENT) as any
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
// FIX 3b: was container.resolve<PayPalModuleService>("paypal_onboarding")
|
|
22
|
+
// PAYPAL_MODULE is the exported constant from modules/paypal/index.ts
|
|
23
|
+
const paypal = container.resolve<PayPalModuleService>(PAYPAL_MODULE)
|
|
24
|
+
|
|
25
|
+
// FIX 3c: was ["pp_paypal_paypal", "pp_paypal_card_paypal_card"]
|
|
26
|
+
// PAYPAL_PROVIDER_IDS is the exported array from modules/paypal/utils/provider-ids.ts
|
|
27
|
+
// If the provider IDs ever change in one place, this file updates automatically.
|
|
28
|
+
const sessions = await paymentModule.listPaymentSessions({
|
|
29
|
+
provider_id: [...PAYPAL_PROVIDER_IDS],
|
|
18
30
|
status: ["pending", "authorized"],
|
|
19
31
|
})
|
|
20
32
|
|
|
@@ -66,8 +78,11 @@ export default async function paypalReconcile(container: MedusaContainer) {
|
|
|
66
78
|
})
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
|
|
81
|
+
// FIX 3d: was paymentSessionService.update(session.id, { ... })
|
|
82
|
+
// updatePaymentSession is the correct method name on the payment module service.
|
|
83
|
+
await paymentModule.updatePaymentSession(session.id, {
|
|
70
84
|
status,
|
|
85
|
+
amount: session.amount,
|
|
71
86
|
data: {
|
|
72
87
|
...(session.data || {}),
|
|
73
88
|
paypal: {
|
|
@@ -132,4 +147,4 @@ export default async function paypalReconcile(container: MedusaContainer) {
|
|
|
132
147
|
export const config = {
|
|
133
148
|
name: "paypal-reconcile",
|
|
134
149
|
schedule: "*/15 * * * *",
|
|
135
|
-
}
|
|
150
|
+
}
|
|
@@ -236,7 +236,9 @@ class PayPalModuleService extends MedusaService({
|
|
|
236
236
|
let json: any = {}
|
|
237
237
|
try {
|
|
238
238
|
json = text ? JSON.parse(text) : {}
|
|
239
|
-
} catch {
|
|
239
|
+
} catch (e: any) {
|
|
240
|
+
console.warn("[PayPal] Failed to parse response JSON — using empty object:", e?.message)
|
|
241
|
+
}
|
|
240
242
|
|
|
241
243
|
if (!resp.ok) {
|
|
242
244
|
throw new Error(
|
|
@@ -548,7 +550,9 @@ class PayPalModuleService extends MedusaService({
|
|
|
548
550
|
let tokenJson: any = {}
|
|
549
551
|
try {
|
|
550
552
|
tokenJson = tokenText ? JSON.parse(tokenText) : {}
|
|
551
|
-
} catch {
|
|
553
|
+
} catch (e: any) {
|
|
554
|
+
console.warn("[PayPal] Failed to parse token response JSON:", e?.message)
|
|
555
|
+
}
|
|
552
556
|
|
|
553
557
|
if (!tokenRes.ok) {
|
|
554
558
|
throw new Error(
|
|
@@ -583,7 +587,9 @@ class PayPalModuleService extends MedusaService({
|
|
|
583
587
|
let credJson: any = {}
|
|
584
588
|
try {
|
|
585
589
|
credJson = credText ? JSON.parse(credText) : {}
|
|
586
|
-
} catch {
|
|
590
|
+
} catch (e: any) {
|
|
591
|
+
console.warn("[PayPal] Failed to parse token response JSON:", e?.message)
|
|
592
|
+
}
|
|
587
593
|
|
|
588
594
|
if (!credRes.ok) {
|
|
589
595
|
throw new Error(
|
|
@@ -1032,12 +1038,41 @@ class PayPalModuleService extends MedusaService({
|
|
|
1032
1038
|
return { data: (row?.data || {}) as Record<string, any> }
|
|
1033
1039
|
}
|
|
1034
1040
|
|
|
1041
|
+
/**
|
|
1042
|
+
* Deep-merge patch into current settings.
|
|
1043
|
+
* Nested objects (additional_settings, api_details, etc.) are merged,
|
|
1044
|
+
* not replaced.
|
|
1045
|
+
*/
|
|
1046
|
+
private deepMerge(
|
|
1047
|
+
target: Record<string, any>,
|
|
1048
|
+
source: Record<string, any>
|
|
1049
|
+
): Record<string, any> {
|
|
1050
|
+
const result = { ...target }
|
|
1051
|
+
for (const key of Object.keys(source)) {
|
|
1052
|
+
const sv = source[key]
|
|
1053
|
+
const tv = target[key]
|
|
1054
|
+
if (
|
|
1055
|
+
sv !== null &&
|
|
1056
|
+
typeof sv === "object" &&
|
|
1057
|
+
!Array.isArray(sv) &&
|
|
1058
|
+
tv !== null &&
|
|
1059
|
+
typeof tv === "object" &&
|
|
1060
|
+
!Array.isArray(tv)
|
|
1061
|
+
) {
|
|
1062
|
+
result[key] = this.deepMerge(tv, sv)
|
|
1063
|
+
} else {
|
|
1064
|
+
result[key] = sv
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return result
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1035
1070
|
async saveSettings(patch: Record<string, any>) {
|
|
1036
1071
|
const rows = await this.listPayPalSettings({})
|
|
1037
1072
|
const row = rows?.[0]
|
|
1038
1073
|
const current = (row?.data || {}) as Record<string, any>
|
|
1039
1074
|
|
|
1040
|
-
const next =
|
|
1075
|
+
const next = this.deepMerge(current, patch)
|
|
1041
1076
|
|
|
1042
1077
|
if (!row) {
|
|
1043
1078
|
const created = await this.createPayPalSettings({ data: next })
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared PayPal API authentication helpers.
|
|
3
|
+
* Import these instead of copying into each route.
|
|
4
|
+
*/
|
|
5
|
+
export function getPayPalApiBase(environment: string): string {
|
|
6
|
+
return environment === "live"
|
|
7
|
+
? "https://api-m.paypal.com"
|
|
8
|
+
: "https://api-m.sandbox.paypal.com"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function getPayPalAccessToken(opts: {
|
|
12
|
+
environment: string
|
|
13
|
+
client_id: string
|
|
14
|
+
client_secret: string
|
|
15
|
+
}): Promise<{ accessToken: string; base: string }> {
|
|
16
|
+
const base = getPayPalApiBase(opts.environment)
|
|
17
|
+
const auth = Buffer.from(`${opts.client_id}:${opts.client_secret}`).toString("base64")
|
|
18
|
+
const resp = await fetch(`${base}/v1/oauth2/token`, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: {
|
|
21
|
+
Authorization: `Basic ${auth}`,
|
|
22
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
23
|
+
},
|
|
24
|
+
body: "grant_type=client_credentials",
|
|
25
|
+
})
|
|
26
|
+
const text = await resp.text()
|
|
27
|
+
if (!resp.ok) {
|
|
28
|
+
throw new Error(`PayPal token error (${resp.status}): ${text}`)
|
|
29
|
+
}
|
|
30
|
+
const json = JSON.parse(text)
|
|
31
|
+
return { accessToken: String(json.access_token), base }
|
|
32
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { MedusaContainer } from "@medusajs/framework/types"
|
|
2
|
+
import { ContainerRegistrationKeys, Modules } from "@medusajs/framework/utils"
|
|
2
3
|
import type PayPalModuleService from "./service"
|
|
3
4
|
import { isPayPalProviderId } from "./utils/provider-ids"
|
|
5
|
+
import { PAYPAL_MODULE } from "./index"
|
|
4
6
|
|
|
5
7
|
export const EVENT_STATUS_MAP: Record<string, "authorized" | "captured" | "canceled" | "error"> = {
|
|
6
8
|
"CHECKOUT.ORDER.APPROVED": "authorized",
|
|
@@ -125,7 +127,9 @@ async function resolveDisputeOrderId(
|
|
|
125
127
|
orderId?: string | null,
|
|
126
128
|
cartId?: string | null
|
|
127
129
|
) {
|
|
128
|
-
|
|
130
|
+
// FIX 4a: was container.resolve("query") as any
|
|
131
|
+
// ContainerRegistrationKeys.QUERY is the official typed constant for the Medusa query helper
|
|
132
|
+
const query = container.resolve(ContainerRegistrationKeys.QUERY)
|
|
129
133
|
const cleanedOrderId = orderId?.trim()
|
|
130
134
|
const cleanedCartId = cartId?.trim()
|
|
131
135
|
|
|
@@ -188,15 +192,22 @@ async function updatePaymentSession(
|
|
|
188
192
|
status: string,
|
|
189
193
|
data: Record<string, unknown>
|
|
190
194
|
) {
|
|
191
|
-
|
|
192
|
-
|
|
195
|
+
// FIX 4b: was container.resolve("payment_collection") as any
|
|
196
|
+
// and container.resolve("payment_session") as any
|
|
197
|
+
// Modules.PAYMENT is the official typed constant that resolves the Medusa payment module.
|
|
198
|
+
// It gives access to both payment collections and payment sessions through one service.
|
|
199
|
+
const paymentModule = container.resolve(Modules.PAYMENT) as any
|
|
200
|
+
|
|
201
|
+
const pc = await paymentModule.retrievePaymentCollectionByCartId?.(cartId).catch(() => null)
|
|
202
|
+
?? await paymentModule.listPaymentCollections({ cart_id: cartId })
|
|
203
|
+
.then((r: any[]) => r?.[0] ?? null)
|
|
204
|
+
.catch(() => null)
|
|
193
205
|
|
|
194
|
-
const pc = await paymentCollectionService.retrieveByCartId(cartId).catch(() => null)
|
|
195
206
|
if (!pc?.id) {
|
|
196
207
|
return
|
|
197
208
|
}
|
|
198
209
|
|
|
199
|
-
const sessions = await
|
|
210
|
+
const sessions = await paymentModule.listPaymentSessions({ payment_collection_id: pc.id })
|
|
200
211
|
const paypalSession = sessions?.find((s: any) => isPayPalProviderId(s.provider_id))
|
|
201
212
|
if (!paypalSession) {
|
|
202
213
|
return
|
|
@@ -210,7 +221,7 @@ async function updatePaymentSession(
|
|
|
210
221
|
? mergeRefunds(existingRefunds, incomingRefunds)
|
|
211
222
|
: existingRefunds
|
|
212
223
|
|
|
213
|
-
await
|
|
224
|
+
await paymentModule.updatePaymentSession(paypalSession.id, {
|
|
214
225
|
status,
|
|
215
226
|
data: {
|
|
216
227
|
...existingData,
|
|
@@ -239,7 +250,10 @@ export async function processPayPalWebhookEvent(
|
|
|
239
250
|
payload: Record<string, any>
|
|
240
251
|
}
|
|
241
252
|
) {
|
|
242
|
-
|
|
253
|
+
// FIX 4c: was container.resolve<PayPalModuleService>("paypal_onboarding")
|
|
254
|
+
// PAYPAL_MODULE is the exported constant from ./index.ts — value is "paypal_onboarding"
|
|
255
|
+
// Using the constant means if the key ever changes, this file updates automatically.
|
|
256
|
+
const paypal = container.resolve<PayPalModuleService>(PAYPAL_MODULE)
|
|
243
257
|
const resource = normalizeResource(input.payload)
|
|
244
258
|
const { orderId, captureId, refundId, cartId } = extractIdentifiers(resource)
|
|
245
259
|
const disputeDetails =
|
|
@@ -310,4 +324,4 @@ export async function processPayPalWebhookEvent(
|
|
|
310
324
|
disputeId: disputeDetails?.disputeId,
|
|
311
325
|
resource,
|
|
312
326
|
}
|
|
313
|
-
}
|
|
327
|
+
}
|