@easypayment/medusa-payment-paypal 0.9.17 → 0.9.19
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 +326 -130
- package/.medusa/server/src/admin/index.mjs +326 -130
- package/.medusa/server/src/admin/routes/settings/paypal/connection/page.d.ts +4 -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 +434 -113
- package/.medusa/server/src/admin/routes/settings/paypal/connection/page.js.map +1 -1
- package/.medusa/server/src/api/admin/paypal/onboarding-link/route.d.ts.map +1 -1
- package/.medusa/server/src/api/admin/paypal/onboarding-link/route.js +4 -0
- package/.medusa/server/src/api/admin/paypal/onboarding-link/route.js.map +1 -1
- package/.medusa/server/src/api/middlewares.d.ts.map +1 -1
- package/.medusa/server/src/api/middlewares.js +9 -0
- package/.medusa/server/src/api/middlewares.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 +13 -8
- package/.medusa/server/src/api/store/paypal/webhook/route.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 +54 -31
- package/.medusa/server/src/modules/paypal/payment-provider/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/payment-provider/status-utils.d.ts +32 -0
- package/.medusa/server/src/modules/paypal/payment-provider/status-utils.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/payment-provider/status-utils.js +65 -0
- package/.medusa/server/src/modules/paypal/payment-provider/status-utils.js.map +1 -0
- package/.medusa/server/src/modules/paypal/service.d.ts +13 -1
- package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/service.js +19 -4
- package/.medusa/server/src/modules/paypal/service.js.map +1 -1
- package/.medusa/server/src/modules/paypal/utils/secret-crypto.d.ts +2 -1
- package/.medusa/server/src/modules/paypal/utils/secret-crypto.d.ts.map +1 -1
- package/.medusa/server/src/modules/paypal/utils/secret-crypto.js +82 -26
- package/.medusa/server/src/modules/paypal/utils/secret-crypto.js.map +1 -1
- package/.medusa/server/src/modules/paypal/utils/webhook-verify.d.ts +28 -0
- package/.medusa/server/src/modules/paypal/utils/webhook-verify.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/webhook-verify.js +58 -0
- package/.medusa/server/src/modules/paypal/utils/webhook-verify.js.map +1 -0
- package/package.json +1 -1
- package/src/admin/routes/settings/paypal/connection/page.tsx +471 -127
- package/src/api/admin/paypal/onboarding-link/route.ts +6 -0
- package/src/api/middlewares.ts +9 -0
- package/src/api/store/paypal/webhook/route.ts +22 -8
- package/src/modules/paypal/payment-provider/service.ts +73 -29
- package/src/modules/paypal/payment-provider/status-utils.ts +59 -0
- package/src/modules/paypal/service.ts +24 -5
- package/src/modules/paypal/utils/secret-crypto.ts +90 -27
- package/src/modules/paypal/utils/webhook-verify.ts +61 -0
|
@@ -311,6 +311,9 @@ function AdvancedCardPaymentsTab() {
|
|
|
311
311
|
function PayPalApplePayPage() {
|
|
312
312
|
return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: "/settings/paypal/connection", replace: true });
|
|
313
313
|
}
|
|
314
|
+
function PayPalGooglePayPage() {
|
|
315
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: "/settings/paypal/connection", replace: true });
|
|
316
|
+
}
|
|
314
317
|
const config = adminSdk.defineRouteConfig({
|
|
315
318
|
label: "PayPal Connection"
|
|
316
319
|
});
|
|
@@ -319,37 +322,97 @@ const PARTNER_JS_URLS = {
|
|
|
319
322
|
live: "https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js"
|
|
320
323
|
};
|
|
321
324
|
const SERVICE_URL = "/admin/paypal/onboarding-link";
|
|
322
|
-
const
|
|
325
|
+
const POPUP_NAME = "PPMiniBrowser";
|
|
326
|
+
const CACHE_PREFIX = "pp_onboard_cache";
|
|
323
327
|
const CACHE_EXPIRY = 10 * 60 * 1e3;
|
|
324
328
|
const ONBOARDING_COMPLETE_ENDPOINT = "/admin/paypal/onboard-complete";
|
|
325
329
|
const STATUS_ENDPOINT = "/admin/paypal/status";
|
|
326
330
|
const SAVE_CREDENTIALS_ENDPOINT = "/admin/paypal/save-credentials";
|
|
327
331
|
const DISCONNECT_ENDPOINT = "/admin/paypal/disconnect";
|
|
328
|
-
|
|
329
|
-
|
|
332
|
+
const ENVIRONMENT_ENDPOINT = "/admin/paypal/environment";
|
|
333
|
+
const cacheKeyFor = (env) => `${CACHE_PREFIX}_${env}`;
|
|
334
|
+
function readCachedUrl(env) {
|
|
335
|
+
if (typeof window === "undefined") return null;
|
|
330
336
|
try {
|
|
331
|
-
const
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
337
|
+
const raw = localStorage.getItem(cacheKeyFor(env));
|
|
338
|
+
if (!raw) return null;
|
|
339
|
+
const data = JSON.parse(raw);
|
|
340
|
+
if (data && typeof data.url === "string" && Date.now() - (Number(data.ts) || 0) < CACHE_EXPIRY) {
|
|
341
|
+
return data.url;
|
|
342
|
+
}
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
function writeCachedUrl(env, url) {
|
|
348
|
+
try {
|
|
349
|
+
localStorage.setItem(cacheKeyFor(env), JSON.stringify({ url, ts: Date.now() }));
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function clearCachedUrl(env) {
|
|
354
|
+
try {
|
|
355
|
+
if (env) {
|
|
356
|
+
localStorage.removeItem(cacheKeyFor(env));
|
|
357
|
+
} else {
|
|
358
|
+
localStorage.removeItem(cacheKeyFor("sandbox"));
|
|
359
|
+
localStorage.removeItem(cacheKeyFor("live"));
|
|
360
|
+
}
|
|
361
|
+
} catch {
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function isPayPalOrigin(origin) {
|
|
365
|
+
try {
|
|
366
|
+
const host = new URL(origin).hostname.toLowerCase();
|
|
367
|
+
return host === "www.paypal.com" || host === "www.sandbox.paypal.com" || host.endsWith(".paypal.com") || host.endsWith(".paypalobjects.com");
|
|
368
|
+
} catch {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function extractAuth(data) {
|
|
373
|
+
if (!data) return {};
|
|
374
|
+
if (typeof data === "object") {
|
|
375
|
+
const obj = data;
|
|
376
|
+
const authCode = obj.authCode ?? obj.auth_code ?? obj.authcode;
|
|
377
|
+
const sharedId = obj.sharedId ?? obj.shared_id ?? obj.sharedid;
|
|
378
|
+
if (authCode && sharedId) {
|
|
379
|
+
return { authCode: String(authCode), sharedId: String(sharedId) };
|
|
380
|
+
}
|
|
381
|
+
for (const key of ["data", "payload", "message", "detail", "body"]) {
|
|
382
|
+
if (obj[key] && typeof obj[key] === "object") {
|
|
383
|
+
const inner = extractAuth(obj[key]);
|
|
384
|
+
if (inner.authCode && inner.sharedId) return inner;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return {};
|
|
388
|
+
}
|
|
389
|
+
if (typeof data === "string") {
|
|
390
|
+
const s = data.trim();
|
|
391
|
+
if (!s) return {};
|
|
392
|
+
if (s.startsWith("{")) {
|
|
393
|
+
try {
|
|
394
|
+
return extractAuth(JSON.parse(s));
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (/auth_?code/i.test(s) && /shared_?id/i.test(s)) {
|
|
399
|
+
try {
|
|
400
|
+
const sp = new URLSearchParams(s.replace(/^[?#]/, ""));
|
|
401
|
+
const authCode = sp.get("authCode") || sp.get("auth_code") || void 0;
|
|
402
|
+
const sharedId = sp.get("sharedId") || sp.get("shared_id") || void 0;
|
|
403
|
+
if (authCode && sharedId) return { authCode, sharedId };
|
|
404
|
+
} catch {
|
|
336
405
|
}
|
|
337
406
|
}
|
|
338
|
-
} catch (e) {
|
|
339
|
-
console.error("Cache read error:", e);
|
|
340
407
|
}
|
|
408
|
+
return {};
|
|
341
409
|
}
|
|
342
410
|
function PayPalConnectionPage() {
|
|
343
411
|
const [env, setEnv] = react.useState("live");
|
|
344
|
-
react.
|
|
345
|
-
fetch("/admin/paypal/environment", { method: "GET", credentials: "include" }).then((r) => r.json()).then((d) => {
|
|
346
|
-
const v = (d == null ? void 0 : d.environment) === "sandbox" ? "sandbox" : "live";
|
|
347
|
-
setEnv(v);
|
|
348
|
-
}).catch(() => {
|
|
349
|
-
});
|
|
350
|
-
}, []);
|
|
412
|
+
const [envReady, setEnvReady] = react.useState(false);
|
|
351
413
|
const [connState, setConnState] = react.useState("loading");
|
|
352
414
|
const [error, setError] = react.useState(null);
|
|
415
|
+
const [popupBlocked, setPopupBlocked] = react.useState(false);
|
|
353
416
|
const [finalUrl, setFinalUrl] = react.useState("");
|
|
354
417
|
const [showManual, setShowManual] = react.useState(false);
|
|
355
418
|
const [clientId, setClientId] = react.useState("");
|
|
@@ -361,6 +424,17 @@ function PayPalConnectionPage() {
|
|
|
361
424
|
const errorLogRef = react.useRef(null);
|
|
362
425
|
const runIdRef = react.useRef(0);
|
|
363
426
|
const currentRunId = react.useRef(0);
|
|
427
|
+
const finalUrlRef = react.useRef(finalUrl);
|
|
428
|
+
finalUrlRef.current = finalUrl;
|
|
429
|
+
const connStateRef = react.useRef(connState);
|
|
430
|
+
connStateRef.current = connState;
|
|
431
|
+
const inProgressRef = react.useRef(onboardingInProgress);
|
|
432
|
+
inProgressRef.current = onboardingInProgress;
|
|
433
|
+
const envRef = react.useRef(env);
|
|
434
|
+
envRef.current = env;
|
|
435
|
+
const popupRef = react.useRef(null);
|
|
436
|
+
const popupPollRef = react.useRef(null);
|
|
437
|
+
const completedRef = react.useRef(false);
|
|
364
438
|
const ppBtnMeasureRef = react.useRef(null);
|
|
365
439
|
const [ppBtnWidth, setPpBtnWidth] = react.useState(null);
|
|
366
440
|
const canSaveManual = react.useMemo(() => {
|
|
@@ -370,8 +444,124 @@ function PayPalConnectionPage() {
|
|
|
370
444
|
setConnState("error");
|
|
371
445
|
setError(msg);
|
|
372
446
|
}, []);
|
|
447
|
+
const stopPopupPoll = react.useCallback(() => {
|
|
448
|
+
if (popupPollRef.current) {
|
|
449
|
+
clearInterval(popupPollRef.current);
|
|
450
|
+
popupPollRef.current = null;
|
|
451
|
+
}
|
|
452
|
+
}, []);
|
|
453
|
+
const openOnboardingPopup = react.useCallback(
|
|
454
|
+
(url) => {
|
|
455
|
+
const w = 450;
|
|
456
|
+
const h = 600;
|
|
457
|
+
const baseLeft = typeof window.screenX === "number" ? window.screenX : window.screenLeft || 0;
|
|
458
|
+
const baseTop = typeof window.screenY === "number" ? window.screenY : window.screenTop || 0;
|
|
459
|
+
const outerW = window.outerWidth || document.documentElement.clientWidth || 1024;
|
|
460
|
+
const outerH = window.outerHeight || document.documentElement.clientHeight || 768;
|
|
461
|
+
const left = Math.round(baseLeft + Math.max(0, (outerW - w) / 2));
|
|
462
|
+
const top = Math.round(baseTop + Math.max(0, (outerH - h) / 2));
|
|
463
|
+
const features = `popup=yes,width=${w},height=${h},left=${left},top=${top},scrollbars=yes,resizable=yes`;
|
|
464
|
+
let popup = null;
|
|
465
|
+
try {
|
|
466
|
+
popup = window.open(url, POPUP_NAME, features);
|
|
467
|
+
} catch {
|
|
468
|
+
popup = null;
|
|
469
|
+
}
|
|
470
|
+
if (popup) {
|
|
471
|
+
popupRef.current = popup;
|
|
472
|
+
try {
|
|
473
|
+
popup.focus();
|
|
474
|
+
} catch {
|
|
475
|
+
}
|
|
476
|
+
stopPopupPoll();
|
|
477
|
+
popupPollRef.current = setInterval(() => {
|
|
478
|
+
if (!popupRef.current || popupRef.current.closed) {
|
|
479
|
+
stopPopupPoll();
|
|
480
|
+
}
|
|
481
|
+
}, 1e3);
|
|
482
|
+
}
|
|
483
|
+
return popup;
|
|
484
|
+
},
|
|
485
|
+
[stopPopupPoll]
|
|
486
|
+
);
|
|
487
|
+
const openConnect = react.useCallback(() => {
|
|
488
|
+
if (connStateRef.current !== "ready" || !finalUrlRef.current || inProgressRef.current) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
completedRef.current = false;
|
|
492
|
+
const popup = openOnboardingPopup(finalUrlRef.current);
|
|
493
|
+
if (!popup) {
|
|
494
|
+
setPopupBlocked(true);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
setPopupBlocked(false);
|
|
498
|
+
}, [openOnboardingPopup]);
|
|
499
|
+
const completeOnboarding = react.useCallback(
|
|
500
|
+
async (authCode, sharedId) => {
|
|
501
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
502
|
+
if (!authCode || !sharedId) return;
|
|
503
|
+
if (completedRef.current) return;
|
|
504
|
+
completedRef.current = true;
|
|
505
|
+
try {
|
|
506
|
+
window.onbeforeunload = null;
|
|
507
|
+
} catch {
|
|
508
|
+
}
|
|
509
|
+
const activeEnv = envRef.current === "sandbox" ? "sandbox" : "live";
|
|
510
|
+
setOnboardingInProgress(true);
|
|
511
|
+
setConnState("loading");
|
|
512
|
+
setError(null);
|
|
513
|
+
setPopupBlocked(false);
|
|
514
|
+
try {
|
|
515
|
+
const res = await fetch(ONBOARDING_COMPLETE_ENDPOINT, {
|
|
516
|
+
method: "POST",
|
|
517
|
+
headers: { "content-type": "application/json" },
|
|
518
|
+
credentials: "include",
|
|
519
|
+
body: JSON.stringify({ authCode, sharedId, env: activeEnv })
|
|
520
|
+
});
|
|
521
|
+
if (!res.ok) {
|
|
522
|
+
const txt = await res.text().catch(() => "");
|
|
523
|
+
throw new Error(txt || `Onboarding exchange failed (${res.status})`);
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
(_a = popupRef.current) == null ? void 0 : _a.close();
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
stopPopupPoll();
|
|
530
|
+
try {
|
|
531
|
+
const close1 = (_e = (_d = (_c = (_b = window.PAYPAL) == null ? void 0 : _b.apps) == null ? void 0 : _c.Signup) == null ? void 0 : _d.MiniBrowser) == null ? void 0 : _e.closeFlow;
|
|
532
|
+
if (typeof close1 === "function") close1();
|
|
533
|
+
} catch {
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
const close2 = ((_h = (_g = (_f = window.PAYPAL) == null ? void 0 : _f.apps) == null ? void 0 : _g.Signup) == null ? void 0 : _h.miniBrowser) && window.PAYPAL.apps.Signup.miniBrowser.closeFlow;
|
|
537
|
+
if (typeof close2 === "function") close2();
|
|
538
|
+
} catch {
|
|
539
|
+
}
|
|
540
|
+
clearCachedUrl(activeEnv);
|
|
541
|
+
try {
|
|
542
|
+
const statusRes = await fetch(
|
|
543
|
+
`${STATUS_ENDPOINT}?environment=${activeEnv}`,
|
|
544
|
+
{ method: "GET", credentials: "include" }
|
|
545
|
+
);
|
|
546
|
+
const refreshedStatus = await statusRes.json().catch(() => ({}));
|
|
547
|
+
setStatusInfo(refreshedStatus || null);
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
setConnState("connected");
|
|
551
|
+
setShowManual(false);
|
|
552
|
+
} catch (e) {
|
|
553
|
+
completedRef.current = false;
|
|
554
|
+
console.error(e);
|
|
555
|
+
setConnState("error");
|
|
556
|
+
setError((e == null ? void 0 : e.message) || "Exchange failed while saving credentials.");
|
|
557
|
+
} finally {
|
|
558
|
+
setOnboardingInProgress(false);
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
[stopPopupPoll]
|
|
562
|
+
);
|
|
373
563
|
const fetchFreshLink = react.useCallback(
|
|
374
|
-
(runId) => {
|
|
564
|
+
(runId, targetEnv) => {
|
|
375
565
|
if (initLoaderRef.current) {
|
|
376
566
|
const loaderText = initLoaderRef.current.querySelector("#loader-text");
|
|
377
567
|
if (loaderText)
|
|
@@ -382,7 +572,10 @@ function PayPalConnectionPage() {
|
|
|
382
572
|
headers: { "content-type": "application/json" },
|
|
383
573
|
credentials: "include",
|
|
384
574
|
body: JSON.stringify({
|
|
385
|
-
products: ["PPCP"]
|
|
575
|
+
products: ["PPCP"],
|
|
576
|
+
// Generate the link for the explicitly selected environment so it can
|
|
577
|
+
// never depend on a racing POST /environment having landed yet.
|
|
578
|
+
environment: targetEnv
|
|
386
579
|
})
|
|
387
580
|
}).then((r) => {
|
|
388
581
|
if (!r.ok) throw new Error(`Service returned ${r.status}`);
|
|
@@ -395,10 +588,7 @@ function PayPalConnectionPage() {
|
|
|
395
588
|
return;
|
|
396
589
|
}
|
|
397
590
|
const url = href + (href.includes("?") ? "&" : "?") + "displayMode=minibrowser";
|
|
398
|
-
|
|
399
|
-
CACHE_KEY,
|
|
400
|
-
JSON.stringify({ url, ts: Date.now() })
|
|
401
|
-
);
|
|
591
|
+
writeCachedUrl(targetEnv, url);
|
|
402
592
|
setFinalUrl(url);
|
|
403
593
|
setConnState("ready");
|
|
404
594
|
}).catch(() => {
|
|
@@ -406,37 +596,104 @@ function PayPalConnectionPage() {
|
|
|
406
596
|
showError("Unable to connect to service.");
|
|
407
597
|
});
|
|
408
598
|
},
|
|
409
|
-
[
|
|
599
|
+
[showError]
|
|
410
600
|
);
|
|
601
|
+
react.useEffect(() => {
|
|
602
|
+
let alive = true;
|
|
603
|
+
fetch(ENVIRONMENT_ENDPOINT, { method: "GET", credentials: "include" }).then((r) => r.json()).then((d) => {
|
|
604
|
+
if (!alive) return;
|
|
605
|
+
setEnv((d == null ? void 0 : d.environment) === "sandbox" ? "sandbox" : "live");
|
|
606
|
+
}).catch(() => {
|
|
607
|
+
}).finally(() => {
|
|
608
|
+
if (alive) setEnvReady(true);
|
|
609
|
+
});
|
|
610
|
+
return () => {
|
|
611
|
+
alive = false;
|
|
612
|
+
};
|
|
613
|
+
}, []);
|
|
411
614
|
react.useEffect(() => {
|
|
412
615
|
var _a, _b, _c;
|
|
413
616
|
if (connState !== "ready" || !finalUrl) return;
|
|
414
617
|
const scriptUrl = PARTNER_JS_URLS[env];
|
|
618
|
+
let cancelled = false;
|
|
619
|
+
let pollTimer;
|
|
620
|
+
const initPartner = (attempt = 0) => {
|
|
621
|
+
var _a2, _b2;
|
|
622
|
+
if (cancelled) return;
|
|
623
|
+
const signup = (_b2 = (_a2 = window.PAYPAL) == null ? void 0 : _a2.apps) == null ? void 0 : _b2.Signup;
|
|
624
|
+
const target = (signup == null ? void 0 : signup.miniBrowser) && typeof signup.miniBrowser.init === "function" ? signup.miniBrowser : (signup == null ? void 0 : signup.MiniBrowser) && typeof signup.MiniBrowser.init === "function" ? signup.MiniBrowser : null;
|
|
625
|
+
if (target == null ? void 0 : target.init) {
|
|
626
|
+
try {
|
|
627
|
+
target.init();
|
|
628
|
+
} catch (e) {
|
|
629
|
+
console.error("[paypal] partner.js init failed:", e);
|
|
630
|
+
}
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (typeof (signup == null ? void 0 : signup.render) === "function") {
|
|
634
|
+
try {
|
|
635
|
+
signup.render();
|
|
636
|
+
} catch (e) {
|
|
637
|
+
console.error("[paypal] partner.js render failed:", e);
|
|
638
|
+
}
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (attempt < 40) {
|
|
642
|
+
pollTimer = setTimeout(() => initPartner(attempt + 1), 50);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
try {
|
|
646
|
+
document.dispatchEvent(new Event("DOMContentLoaded"));
|
|
647
|
+
} catch {
|
|
648
|
+
}
|
|
649
|
+
};
|
|
415
650
|
const existingScript = document.getElementById("paypal-partner-js");
|
|
416
651
|
if (existingScript) {
|
|
417
652
|
(_a = existingScript.parentNode) == null ? void 0 : _a.removeChild(existingScript);
|
|
418
653
|
}
|
|
419
654
|
if ((_c = (_b = window.PAYPAL) == null ? void 0 : _b.apps) == null ? void 0 : _c.Signup) {
|
|
420
|
-
|
|
655
|
+
try {
|
|
656
|
+
delete window.PAYPAL.apps.Signup;
|
|
657
|
+
} catch {
|
|
658
|
+
}
|
|
421
659
|
}
|
|
422
660
|
const ppScript = document.createElement("script");
|
|
423
661
|
ppScript.id = "paypal-partner-js";
|
|
424
662
|
ppScript.src = scriptUrl;
|
|
425
663
|
ppScript.async = true;
|
|
664
|
+
ppScript.onload = () => initPartner();
|
|
426
665
|
document.body.appendChild(ppScript);
|
|
427
666
|
return () => {
|
|
667
|
+
cancelled = true;
|
|
668
|
+
if (pollTimer) {
|
|
669
|
+
clearTimeout(pollTimer);
|
|
670
|
+
}
|
|
428
671
|
if (ppScript.parentNode) {
|
|
429
672
|
ppScript.parentNode.removeChild(ppScript);
|
|
430
673
|
}
|
|
431
674
|
};
|
|
432
675
|
}, [connState, finalUrl, env]);
|
|
433
676
|
react.useEffect(() => {
|
|
677
|
+
const onMessage = (ev) => {
|
|
678
|
+
if (!isPayPalOrigin(ev.origin)) return;
|
|
679
|
+
const { authCode, sharedId } = extractAuth(ev.data);
|
|
680
|
+
if (authCode && sharedId) {
|
|
681
|
+
completeOnboarding(authCode, sharedId);
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
window.addEventListener("message", onMessage);
|
|
685
|
+
return () => window.removeEventListener("message", onMessage);
|
|
686
|
+
}, [completeOnboarding]);
|
|
687
|
+
react.useEffect(() => {
|
|
688
|
+
if (!envReady) return;
|
|
434
689
|
currentRunId.current = ++runIdRef.current;
|
|
435
690
|
const runId = currentRunId.current;
|
|
436
691
|
let cancelled = false;
|
|
692
|
+
completedRef.current = false;
|
|
437
693
|
const run = async () => {
|
|
438
694
|
setConnState("loading");
|
|
439
695
|
setError(null);
|
|
696
|
+
setPopupBlocked(false);
|
|
440
697
|
setFinalUrl("");
|
|
441
698
|
try {
|
|
442
699
|
const r = await fetch(`${STATUS_ENDPOINT}?environment=${env}`, {
|
|
@@ -455,84 +712,46 @@ function PayPalConnectionPage() {
|
|
|
455
712
|
} catch (e) {
|
|
456
713
|
console.error(e);
|
|
457
714
|
}
|
|
458
|
-
if (
|
|
459
|
-
|
|
715
|
+
if (cancelled || runId !== currentRunId.current) return;
|
|
716
|
+
const cached = readCachedUrl(env);
|
|
717
|
+
if (cached) {
|
|
718
|
+
setFinalUrl(cached);
|
|
460
719
|
setConnState("ready");
|
|
461
720
|
} else {
|
|
462
|
-
fetchFreshLink(runId);
|
|
721
|
+
fetchFreshLink(runId, env);
|
|
463
722
|
}
|
|
464
723
|
};
|
|
465
724
|
run();
|
|
466
725
|
return () => {
|
|
467
726
|
cancelled = true;
|
|
468
|
-
currentRunId.current = 0;
|
|
469
727
|
};
|
|
470
|
-
}, [env, fetchFreshLink]);
|
|
728
|
+
}, [env, envReady, fetchFreshLink]);
|
|
471
729
|
react.useLayoutEffect(() => {
|
|
472
|
-
window.onboardingCallback =
|
|
473
|
-
|
|
474
|
-
try {
|
|
475
|
-
window.onbeforeunload = null;
|
|
476
|
-
} catch {
|
|
477
|
-
}
|
|
478
|
-
setOnboardingInProgress(true);
|
|
479
|
-
setConnState("loading");
|
|
480
|
-
setError(null);
|
|
481
|
-
const payload = {
|
|
482
|
-
authCode,
|
|
483
|
-
sharedId,
|
|
484
|
-
env: env === "sandbox" ? "sandbox" : "live"
|
|
485
|
-
};
|
|
486
|
-
try {
|
|
487
|
-
const res = await fetch(ONBOARDING_COMPLETE_ENDPOINT, {
|
|
488
|
-
method: "POST",
|
|
489
|
-
headers: { "content-type": "application/json" },
|
|
490
|
-
credentials: "include",
|
|
491
|
-
body: JSON.stringify(payload)
|
|
492
|
-
});
|
|
493
|
-
if (!res.ok) {
|
|
494
|
-
const txt = await res.text().catch(() => "");
|
|
495
|
-
throw new Error(txt || `Onboarding exchange failed (${res.status})`);
|
|
496
|
-
}
|
|
497
|
-
try {
|
|
498
|
-
const close1 = (_d = (_c = (_b = (_a = window.PAYPAL) == null ? void 0 : _a.apps) == null ? void 0 : _b.Signup) == null ? void 0 : _c.MiniBrowser) == null ? void 0 : _d.closeFlow;
|
|
499
|
-
if (typeof close1 === "function") close1();
|
|
500
|
-
} catch {
|
|
501
|
-
}
|
|
502
|
-
try {
|
|
503
|
-
const close2 = ((_g = (_f = (_e = window.PAYPAL) == null ? void 0 : _e.apps) == null ? void 0 : _f.Signup) == null ? void 0 : _g.miniBrowser) && window.PAYPAL.apps.Signup.miniBrowser.closeFlow;
|
|
504
|
-
if (typeof close2 === "function") close2();
|
|
505
|
-
} catch {
|
|
506
|
-
}
|
|
507
|
-
try {
|
|
508
|
-
localStorage.removeItem(CACHE_KEY);
|
|
509
|
-
} catch {
|
|
510
|
-
}
|
|
511
|
-
try {
|
|
512
|
-
const statusRes = await fetch(`${STATUS_ENDPOINT}?environment=${env}`, {
|
|
513
|
-
method: "GET",
|
|
514
|
-
credentials: "include"
|
|
515
|
-
});
|
|
516
|
-
const refreshedStatus = await statusRes.json().catch(() => ({}));
|
|
517
|
-
setStatusInfo(refreshedStatus || null);
|
|
518
|
-
setConnState("connected");
|
|
519
|
-
setShowManual(false);
|
|
520
|
-
} catch {
|
|
521
|
-
setConnState("connected");
|
|
522
|
-
setShowManual(false);
|
|
523
|
-
}
|
|
524
|
-
setOnboardingInProgress(false);
|
|
525
|
-
} catch (e) {
|
|
526
|
-
console.error(e);
|
|
527
|
-
setConnState("error");
|
|
528
|
-
setError((e == null ? void 0 : e.message) || "Exchange failed while saving credentials.");
|
|
529
|
-
setOnboardingInProgress(false);
|
|
530
|
-
}
|
|
730
|
+
window.onboardingCallback = (authCode, sharedId) => {
|
|
731
|
+
completeOnboarding(authCode, sharedId);
|
|
531
732
|
};
|
|
532
733
|
return () => {
|
|
533
734
|
window.onboardingCallback = void 0;
|
|
534
735
|
};
|
|
535
|
-
}, [
|
|
736
|
+
}, [completeOnboarding]);
|
|
737
|
+
react.useEffect(() => {
|
|
738
|
+
return () => {
|
|
739
|
+
stopPopupPoll();
|
|
740
|
+
};
|
|
741
|
+
}, [stopPopupPoll]);
|
|
742
|
+
react.useEffect(() => {
|
|
743
|
+
const onCaptureClick = (e) => {
|
|
744
|
+
const btn = paypalButtonRef.current;
|
|
745
|
+
if (!btn) return;
|
|
746
|
+
const target = e.target;
|
|
747
|
+
if (!target || !btn.contains(target)) return;
|
|
748
|
+
e.preventDefault();
|
|
749
|
+
e.stopImmediatePropagation();
|
|
750
|
+
openConnect();
|
|
751
|
+
};
|
|
752
|
+
document.addEventListener("click", onCaptureClick, true);
|
|
753
|
+
return () => document.removeEventListener("click", onCaptureClick, true);
|
|
754
|
+
}, [openConnect]);
|
|
536
755
|
react.useLayoutEffect(() => {
|
|
537
756
|
const el = ppBtnMeasureRef.current;
|
|
538
757
|
if (!el) return;
|
|
@@ -554,9 +773,8 @@ function PayPalConnectionPage() {
|
|
|
554
773
|
};
|
|
555
774
|
}, [connState, env, finalUrl]);
|
|
556
775
|
const handleConnectClick = (e) => {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
776
|
+
e.preventDefault();
|
|
777
|
+
openConnect();
|
|
560
778
|
};
|
|
561
779
|
const handleSaveManual = async () => {
|
|
562
780
|
if (!canSaveManual || onboardingInProgress) return;
|
|
@@ -586,10 +804,7 @@ function PayPalConnectionPage() {
|
|
|
586
804
|
setConnState("connected");
|
|
587
805
|
setStatusInfo(refreshedStatus || null);
|
|
588
806
|
setShowManual(false);
|
|
589
|
-
|
|
590
|
-
localStorage.removeItem(CACHE_KEY);
|
|
591
|
-
} catch {
|
|
592
|
-
}
|
|
807
|
+
clearCachedUrl(env);
|
|
593
808
|
} catch (e) {
|
|
594
809
|
console.error(e);
|
|
595
810
|
setConnState("error");
|
|
@@ -618,13 +833,11 @@ function PayPalConnectionPage() {
|
|
|
618
833
|
const t = await res.text().catch(() => "");
|
|
619
834
|
throw new Error(t || `Disconnect failed (${res.status})`);
|
|
620
835
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
} catch {
|
|
624
|
-
}
|
|
836
|
+
clearCachedUrl(env);
|
|
837
|
+
completedRef.current = false;
|
|
625
838
|
currentRunId.current = ++runIdRef.current;
|
|
626
839
|
const runId = currentRunId.current;
|
|
627
|
-
fetchFreshLink(runId);
|
|
840
|
+
fetchFreshLink(runId, env);
|
|
628
841
|
} catch (e) {
|
|
629
842
|
console.error(e);
|
|
630
843
|
setConnState("error");
|
|
@@ -635,10 +848,12 @@ function PayPalConnectionPage() {
|
|
|
635
848
|
};
|
|
636
849
|
const handleEnvChange = async (e) => {
|
|
637
850
|
const next = e.target.value;
|
|
851
|
+
completedRef.current = false;
|
|
852
|
+
setPopupBlocked(false);
|
|
853
|
+
clearCachedUrl();
|
|
638
854
|
setEnv(next);
|
|
639
|
-
cachedUrl = null;
|
|
640
855
|
try {
|
|
641
|
-
await fetch(
|
|
856
|
+
await fetch(ENVIRONMENT_ENDPOINT, {
|
|
642
857
|
method: "POST",
|
|
643
858
|
headers: { "content-type": "application/json" },
|
|
644
859
|
credentials: "include",
|
|
@@ -646,10 +861,6 @@ function PayPalConnectionPage() {
|
|
|
646
861
|
});
|
|
647
862
|
} catch {
|
|
648
863
|
}
|
|
649
|
-
try {
|
|
650
|
-
localStorage.removeItem(CACHE_KEY);
|
|
651
|
-
} catch {
|
|
652
|
-
}
|
|
653
864
|
};
|
|
654
865
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6", children: [
|
|
655
866
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-6", children: [
|
|
@@ -672,20 +883,7 @@ function PayPalConnectionPage() {
|
|
|
672
883
|
) }),
|
|
673
884
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium pt-2", children: env === "sandbox" ? "Connect to PayPal (Sandbox)" : "Connect to PayPal" }),
|
|
674
885
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-xl", children: connState === "connected" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
675
|
-
/* @__PURE__ */ jsxRuntime.
|
|
676
|
-
"✅ Successfully connected to PayPal!",
|
|
677
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
678
|
-
"a",
|
|
679
|
-
{
|
|
680
|
-
target: "_blank",
|
|
681
|
-
"data-paypal-button": "true",
|
|
682
|
-
"data-paypal-onboard-complete": "onboardingCallback",
|
|
683
|
-
href: "#",
|
|
684
|
-
style: { display: "none" },
|
|
685
|
-
children: "PayPal"
|
|
686
|
-
}
|
|
687
|
-
)
|
|
688
|
-
] }),
|
|
886
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-green-600 bg-green-50 p-3 rounded border border-green-200", children: "✅ Successfully connected to PayPal!" }),
|
|
689
887
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 rounded-md border border-ui-border-base bg-ui-bg-subtle p-3 text-xs text-ui-fg-subtle", children: [
|
|
690
888
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium text-ui-fg-base", children: "Connected PayPal account" }),
|
|
691
889
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1", children: [
|
|
@@ -726,7 +924,7 @@ function PayPalConnectionPage() {
|
|
|
726
924
|
ppBtnMeasureRef.current = node;
|
|
727
925
|
},
|
|
728
926
|
id: "paypal-button",
|
|
729
|
-
target:
|
|
927
|
+
target: POPUP_NAME,
|
|
730
928
|
"data-paypal-button": "true",
|
|
731
929
|
href: finalUrl || "#",
|
|
732
930
|
"data-paypal-onboard-complete": "onboardingCallback",
|
|
@@ -740,6 +938,7 @@ function PayPalConnectionPage() {
|
|
|
740
938
|
children: "Connect to PayPal"
|
|
741
939
|
}
|
|
742
940
|
),
|
|
941
|
+
popupBlocked && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 text-left text-xs bg-amber-50 text-amber-700 p-3 border border-amber-200 rounded", children: "Your browser blocked the PayPal popup. Please allow popups for this site, then click “Connect to PayPal” again." }),
|
|
743
942
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
744
943
|
"div",
|
|
745
944
|
{
|
|
@@ -895,9 +1094,6 @@ function PayPalConnectionPage() {
|
|
|
895
1094
|
` })
|
|
896
1095
|
] });
|
|
897
1096
|
}
|
|
898
|
-
function PayPalGooglePayPage() {
|
|
899
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: "/settings/paypal/connection", replace: true });
|
|
900
|
-
}
|
|
901
1097
|
function PayPalPayLaterMessagingPage() {
|
|
902
1098
|
return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: "/settings/paypal/connection", replace: true });
|
|
903
1099
|
}
|
|
@@ -1127,14 +1323,14 @@ const routeModule = {
|
|
|
1127
1323
|
Component: PayPalApplePayPage,
|
|
1128
1324
|
path: "/settings/paypal/apple-pay"
|
|
1129
1325
|
},
|
|
1130
|
-
{
|
|
1131
|
-
Component: PayPalConnectionPage,
|
|
1132
|
-
path: "/settings/paypal/connection"
|
|
1133
|
-
},
|
|
1134
1326
|
{
|
|
1135
1327
|
Component: PayPalGooglePayPage,
|
|
1136
1328
|
path: "/settings/paypal/google-pay"
|
|
1137
1329
|
},
|
|
1330
|
+
{
|
|
1331
|
+
Component: PayPalConnectionPage,
|
|
1332
|
+
path: "/settings/paypal/connection"
|
|
1333
|
+
},
|
|
1138
1334
|
{
|
|
1139
1335
|
Component: PayPalPayLaterMessagingPage,
|
|
1140
1336
|
path: "/settings/paypal/pay-later-messaging"
|