@easypayment/medusa-payment-paypal 0.9.25 → 0.9.26
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 +179 -293
- package/.medusa/server/src/admin/index.mjs +179 -293
- package/.medusa/server/src/admin/routes/settings/paypal/connection/page.d.ts +0 -1
- package/.medusa/server/src/admin/routes/settings/paypal/connection/page.d.ts.map +1 -1
- package/.medusa/server/src/admin/routes/settings/paypal/connection/page.js +204 -368
- package/.medusa/server/src/admin/routes/settings/paypal/connection/page.js.map +1 -1
- package/.medusa/server/src/api/store/paypal/onboard-return/route.d.ts +11 -9
- package/.medusa/server/src/api/store/paypal/onboard-return/route.d.ts.map +1 -1
- package/.medusa/server/src/api/store/paypal/onboard-return/route.js +28 -9
- package/.medusa/server/src/api/store/paypal/onboard-return/route.js.map +1 -1
- package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/service.js +32 -6
- package/.medusa/server/src/modules/paypal/service.js.map +1 -1
- package/package.json +1 -1
- package/src/admin/routes/settings/paypal/connection/page.tsx +230 -389
- package/src/api/store/paypal/onboard-return/route.ts +34 -9
- package/src/modules/paypal/service.ts +34 -7
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import type PayPalModuleService from "../../../../modules/paypal/service"
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Public PayPal onboarding `return_url`.
|
|
@@ -9,16 +10,18 @@ import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
|
9
10
|
* than `/admin/*`. Leaving the popup on a raw JSON / 401 / 405 page is exactly
|
|
10
11
|
* what made the popup appear to "never close".
|
|
11
12
|
*
|
|
12
|
-
* This route
|
|
13
|
-
* 1.
|
|
14
|
-
* (
|
|
15
|
-
*
|
|
13
|
+
* This route is the authoritative, partner.js-independent completion path. It:
|
|
14
|
+
* 1. exchanges PayPal's `authCode` + `sharedId` for seller credentials
|
|
15
|
+
* server-side (idempotent — safe even if partner.js's onboardingCallback or
|
|
16
|
+
* admin status polling also completes the exchange),
|
|
17
|
+
* 2. relays whatever query params PayPal appended back to the opener (the
|
|
18
|
+
* Medusa Admin connection page) via postMessage so it can refresh
|
|
19
|
+
* immediately, and
|
|
20
|
+
* 3. closes the popup.
|
|
16
21
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* guarantees the popup closes and the admin page re-checks status even if the
|
|
21
|
-
* in-flight PayPal completion message was missed.
|
|
22
|
+
* Because the exchange no longer depends on the opener receiving an in-flight
|
|
23
|
+
* postMessage, onboarding completes (and the admin page flips to "connected" via
|
|
24
|
+
* its status poll) even when partner.js never fires its callback.
|
|
22
25
|
*/
|
|
23
26
|
export async function GET(req: MedusaRequest, res: MedusaResponse) {
|
|
24
27
|
const query = (req.query || {}) as Record<string, unknown>
|
|
@@ -28,6 +31,28 @@ export async function GET(req: MedusaRequest, res: MedusaResponse) {
|
|
|
28
31
|
params[k] = Array.isArray(v) ? String(v[0]) : String(v)
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
// PayPal appends the seller's authorization code + shared id to the return_url
|
|
35
|
+
// (plus the env we pinned when generating the link). When present, complete the
|
|
36
|
+
// exchange right here so credentials are saved even if no other path runs.
|
|
37
|
+
const authCode = params.authCode || params.auth_code
|
|
38
|
+
const sharedId = params.sharedId || params.shared_id
|
|
39
|
+
const env =
|
|
40
|
+
params.env === "sandbox" || params.env === "live" ? params.env : undefined
|
|
41
|
+
|
|
42
|
+
if (authCode && sharedId) {
|
|
43
|
+
try {
|
|
44
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
45
|
+
await paypal.exchangeAndSaveSellerCredentials({ authCode, sharedId, env })
|
|
46
|
+
} catch (e: unknown) {
|
|
47
|
+
// Don't break the popup close on failure — relay the params so the opener
|
|
48
|
+
// (partner.js / admin polling) can still attempt to complete.
|
|
49
|
+
console.error(
|
|
50
|
+
"[PayPal] onboard-return exchange failed:",
|
|
51
|
+
e instanceof Error ? e.message : e
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
31
56
|
// JSON-encode then escape the characters that could break out of the inline
|
|
32
57
|
// <script> string/tag (including the U+2028/U+2029 separators that are legal in
|
|
33
58
|
// JSON but illegal in a JS string literal), so the relayed params embed safely.
|
|
@@ -495,17 +495,20 @@ class PayPalModuleService extends MedusaService({
|
|
|
495
495
|
|
|
496
496
|
async createOnboardingLink(input?: { email?: string; products?: string[]; env?: Environment }) {
|
|
497
497
|
const { onboarding } = await this.ensureSettingsDefaults()
|
|
498
|
-
// The popup lands here after onboarding, as a plain top-level navigation with
|
|
499
|
-
// no auth token — so it must be the PUBLIC store bridge route, not an
|
|
500
|
-
// /admin/* route (which would 401 and leave the popup stuck). The bridge
|
|
501
|
-
// relays the result to the opener and closes the popup.
|
|
502
|
-
const return_url = `${String(onboarding.backend_url || "").replace(/\/$/, "")}/store/paypal/onboard-return`
|
|
503
498
|
// Honor an explicit environment from the caller so the generated link can't
|
|
504
499
|
// depend on a racing "set environment" request having landed first.
|
|
505
500
|
const env =
|
|
506
501
|
input?.env === "sandbox" || input?.env === "live"
|
|
507
502
|
? input.env
|
|
508
503
|
: await this.getCurrentEnvironment()
|
|
504
|
+
// The popup lands here after onboarding, as a plain top-level navigation with
|
|
505
|
+
// no auth token — so it must be the PUBLIC store bridge route, not an
|
|
506
|
+
// /admin/* route (which would 401 and leave the popup stuck). The bridge
|
|
507
|
+
// exchanges the auth code for seller credentials server-side, relays the
|
|
508
|
+
// result to the opener, and closes the popup. We pin the environment in the
|
|
509
|
+
// URL so the bridge saves credentials under the correct env even though
|
|
510
|
+
// PayPal's redirect carries no auth/session.
|
|
511
|
+
const return_url = `${String(onboarding.backend_url || "").replace(/\/$/, "")}/store/paypal/onboard-return?env=${env}`
|
|
509
512
|
const partner_merchant_id = await this.getPartnerMerchantId(env)
|
|
510
513
|
|
|
511
514
|
const email = (input?.email || "").trim()
|
|
@@ -653,9 +656,23 @@ class PayPalModuleService extends MedusaService({
|
|
|
653
656
|
sharedId: string
|
|
654
657
|
env?: "sandbox" | "live"
|
|
655
658
|
}) {
|
|
656
|
-
await this.saveOnboardCallback({ authCode: input.authCode, sharedId: input.sharedId })
|
|
657
|
-
|
|
658
659
|
const env = (input.env || (await this.getCurrentEnvironment())) as Environment
|
|
660
|
+
|
|
661
|
+
// Onboarding completion can now arrive via several redundant paths
|
|
662
|
+
// (partner.js's onboardingCallback in the opener, the return_url bridge that
|
|
663
|
+
// runs inside the popup, and admin status polling). PayPal's authorization
|
|
664
|
+
// code is single-use, so a second exchange of the same code would fail with
|
|
665
|
+
// invalid_grant. If we already exchanged this exact code and hold seller
|
|
666
|
+
// credentials for this environment, treat the duplicate as a success no-op.
|
|
667
|
+
const existingRow = await this.getCurrentRow()
|
|
668
|
+
if (existingRow && existingRow.auth_code === input.authCode) {
|
|
669
|
+
const existingCreds = this.getEnvCreds(existingRow, env)
|
|
670
|
+
if (existingCreds.clientId && existingCreds.clientSecret) {
|
|
671
|
+
return
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
await this.saveOnboardCallback({ authCode: input.authCode, sharedId: input.sharedId })
|
|
659
676
|
const baseUrl = env === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com"
|
|
660
677
|
|
|
661
678
|
const { onboarding } = await this.ensureSettingsDefaults()
|
|
@@ -690,6 +707,16 @@ class PayPalModuleService extends MedusaService({
|
|
|
690
707
|
}
|
|
691
708
|
|
|
692
709
|
if (!tokenRes.ok) {
|
|
710
|
+
// Two redundant completion paths can race to exchange the same single-use
|
|
711
|
+
// code; the loser gets invalid_grant. If credentials were saved in the
|
|
712
|
+
// meantime (the other path won), the onboarding actually succeeded.
|
|
713
|
+
const racedRow = await this.getCurrentRow()
|
|
714
|
+
if (racedRow) {
|
|
715
|
+
const racedCreds = this.getEnvCreds(racedRow, env)
|
|
716
|
+
if (racedCreds.clientId && racedCreds.clientSecret) {
|
|
717
|
+
return
|
|
718
|
+
}
|
|
719
|
+
}
|
|
693
720
|
throw new Error(
|
|
694
721
|
`PayPal authorization_code token exchange failed (${tokenRes.status}): ${tokenText || JSON.stringify(tokenJson)}`
|
|
695
722
|
)
|