@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
|
@@ -12,15 +12,16 @@ const Tabs_1 = __importDefault(require("../_components/Tabs"));
|
|
|
12
12
|
exports.config = (0, admin_sdk_1.defineRouteConfig)({
|
|
13
13
|
label: "PayPal Connection",
|
|
14
14
|
});
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
// Let partner.js own the entire onboarding flow: it opens PayPal's mini-browser
|
|
16
|
+
// itself (synchronously inside the click, so no new tab / no popup block), it
|
|
17
|
+
// receives PayPal's completion postMessage, and it invokes the callback named in
|
|
18
|
+
// `data-paypal-onboard-complete` with (authCode, sharedId). This is PayPal's
|
|
19
|
+
// documented integration and matches the official @easypayment WooCommerce
|
|
20
|
+
// plugin. Opening the popup ourselves (a previous attempt at fixing the "opens a
|
|
21
|
+
// new tab" issue) stopped partner.js from receiving completion, which left the
|
|
22
|
+
// popup stranded on PayPal's "isuDone" page and never saved credentials.
|
|
23
|
+
const PARTNER_JS_URL = "https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js";
|
|
19
24
|
const SERVICE_URL = "/admin/paypal/onboarding-link";
|
|
20
|
-
// PayPal's conventional mini-browser window name (also the anchor target).
|
|
21
|
-
const POPUP_NAME = "PPFrame";
|
|
22
|
-
// The onboarding link is cached in the browser for 6 hours so it is always
|
|
23
|
-
// present for the mini-browser flow without regenerating on every load.
|
|
24
25
|
const CACHE_PREFIX = "pp_onboard_cache";
|
|
25
26
|
const CACHE_EXPIRY = 6 * 60 * 60 * 1000; // 6 hours
|
|
26
27
|
const ONBOARDING_COMPLETE_ENDPOINT = "/admin/paypal/onboard-complete";
|
|
@@ -51,10 +52,9 @@ function readCachedUrl(env) {
|
|
|
51
52
|
function writeCachedUrl(env, url) {
|
|
52
53
|
try {
|
|
53
54
|
localStorage.setItem(cacheKeyFor(env), JSON.stringify({ url, ts: Date.now() }));
|
|
54
|
-
return readCachedUrl(env) === url;
|
|
55
55
|
}
|
|
56
56
|
catch {
|
|
57
|
-
|
|
57
|
+
/* ignore */
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
function clearCachedUrl(env) {
|
|
@@ -71,79 +71,11 @@ function clearCachedUrl(env) {
|
|
|
71
71
|
/* ignore */
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
function isPayPalOrigin(origin) {
|
|
75
|
-
try {
|
|
76
|
-
const host = new URL(origin).hostname.toLowerCase();
|
|
77
|
-
return (host === "www.paypal.com" ||
|
|
78
|
-
host === "www.sandbox.paypal.com" ||
|
|
79
|
-
host.endsWith(".paypal.com") ||
|
|
80
|
-
host.endsWith(".paypalobjects.com"));
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// Defensively pull authCode/sharedId out of whatever shape PayPal posts back
|
|
87
|
-
// (object, JSON string, or query string; possibly nested). We only act when both
|
|
88
|
-
// are present, which ignores the many unrelated intermediate messages PayPal
|
|
89
|
-
// emits.
|
|
90
|
-
function extractAuth(data) {
|
|
91
|
-
if (!data)
|
|
92
|
-
return {};
|
|
93
|
-
if (typeof data === "object") {
|
|
94
|
-
const obj = data;
|
|
95
|
-
const authCode = obj.authCode ?? obj.auth_code ?? obj.authcode ?? obj.code;
|
|
96
|
-
const sharedId = obj.sharedId ?? obj.shared_id ?? obj.sharedid;
|
|
97
|
-
if (authCode && sharedId) {
|
|
98
|
-
return { authCode: String(authCode), sharedId: String(sharedId) };
|
|
99
|
-
}
|
|
100
|
-
for (const key of ["data", "payload", "message", "detail", "body", "params"]) {
|
|
101
|
-
if (obj[key] && typeof obj[key] === "object") {
|
|
102
|
-
const inner = extractAuth(obj[key]);
|
|
103
|
-
if (inner.authCode && inner.sharedId)
|
|
104
|
-
return inner;
|
|
105
|
-
}
|
|
106
|
-
if (typeof obj[key] === "string") {
|
|
107
|
-
const inner = extractAuth(obj[key]);
|
|
108
|
-
if (inner.authCode && inner.sharedId)
|
|
109
|
-
return inner;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return {};
|
|
113
|
-
}
|
|
114
|
-
if (typeof data === "string") {
|
|
115
|
-
const s = data.trim();
|
|
116
|
-
if (!s)
|
|
117
|
-
return {};
|
|
118
|
-
if (s.startsWith("{") || s.startsWith("[")) {
|
|
119
|
-
try {
|
|
120
|
-
return extractAuth(JSON.parse(s));
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
/* not JSON */
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (/auth_?code/i.test(s) && /shared_?id/i.test(s)) {
|
|
127
|
-
try {
|
|
128
|
-
const sp = new URLSearchParams(s.replace(/^[?#]/, ""));
|
|
129
|
-
const authCode = sp.get("authCode") || sp.get("auth_code") || undefined;
|
|
130
|
-
const sharedId = sp.get("sharedId") || sp.get("shared_id") || undefined;
|
|
131
|
-
if (authCode && sharedId)
|
|
132
|
-
return { authCode, sharedId };
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
/* not a query string */
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return {};
|
|
140
|
-
}
|
|
141
74
|
function PayPalConnectionPage() {
|
|
142
75
|
const [env, setEnv] = (0, react_1.useState)("live");
|
|
143
76
|
const [envReady, setEnvReady] = (0, react_1.useState)(false);
|
|
144
77
|
const [connState, setConnState] = (0, react_1.useState)("loading");
|
|
145
78
|
const [error, setError] = (0, react_1.useState)(null);
|
|
146
|
-
const [popupBlocked, setPopupBlocked] = (0, react_1.useState)(false);
|
|
147
79
|
const [finalUrl, setFinalUrl] = (0, react_1.useState)("");
|
|
148
80
|
const [showManual, setShowManual] = (0, react_1.useState)(false);
|
|
149
81
|
const [clientId, setClientId] = (0, react_1.useState)("");
|
|
@@ -155,17 +87,13 @@ function PayPalConnectionPage() {
|
|
|
155
87
|
const errorLogRef = (0, react_1.useRef)(null);
|
|
156
88
|
const runIdRef = (0, react_1.useRef)(0);
|
|
157
89
|
const currentRunId = (0, react_1.useRef)(0);
|
|
90
|
+
const completedRef = (0, react_1.useRef)(false);
|
|
91
|
+
// Long-lived listeners/timers read the current env through a ref so they never
|
|
92
|
+
// capture a stale value from the render that registered them.
|
|
158
93
|
const envRef = (0, react_1.useRef)(env);
|
|
159
94
|
envRef.current = env;
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
const connStateRef = (0, react_1.useRef)(connState);
|
|
163
|
-
connStateRef.current = connState;
|
|
164
|
-
const inProgressRef = (0, react_1.useRef)(onboardingInProgress);
|
|
165
|
-
inProgressRef.current = onboardingInProgress;
|
|
166
|
-
const popupRef = (0, react_1.useRef)(null);
|
|
167
|
-
const pollRef = (0, react_1.useRef)(null);
|
|
168
|
-
const completedRef = (0, react_1.useRef)(false);
|
|
95
|
+
const pollTimerRef = (0, react_1.useRef)(null);
|
|
96
|
+
const pollAttemptsRef = (0, react_1.useRef)(0);
|
|
169
97
|
const ppBtnMeasureRef = (0, react_1.useRef)(null);
|
|
170
98
|
const [ppBtnWidth, setPpBtnWidth] = (0, react_1.useState)(null);
|
|
171
99
|
const canSaveManual = (0, react_1.useMemo)(() => {
|
|
@@ -175,54 +103,87 @@ function PayPalConnectionPage() {
|
|
|
175
103
|
setConnState("error");
|
|
176
104
|
setError(msg);
|
|
177
105
|
}, []);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
106
|
+
// Close PayPal's mini-browser via partner.js's API (works under both the
|
|
107
|
+
// `miniBrowser` and `MiniBrowser` casings PayPal has shipped over time).
|
|
108
|
+
const closeMiniBrowser = (0, react_1.useCallback)(() => {
|
|
109
|
+
try {
|
|
110
|
+
const close1 = window.PAYPAL?.apps?.Signup?.MiniBrowser?.closeFlow;
|
|
111
|
+
if (typeof close1 === "function")
|
|
112
|
+
close1();
|
|
182
113
|
}
|
|
114
|
+
catch { }
|
|
115
|
+
try {
|
|
116
|
+
const close2 = window.PAYPAL?.apps?.Signup?.miniBrowser?.closeFlow;
|
|
117
|
+
if (typeof close2 === "function")
|
|
118
|
+
close2();
|
|
119
|
+
}
|
|
120
|
+
catch { }
|
|
183
121
|
}, []);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const outerH = window.outerHeight || document.documentElement.clientHeight || 768;
|
|
195
|
-
const left = Math.round(baseLeft + Math.max(0, (outerW - w) / 2));
|
|
196
|
-
const top = Math.round(baseTop + Math.max(0, (outerH - h) / 2));
|
|
197
|
-
const features = `popup=yes,width=${w},height=${h},left=${left},top=${top},scrollbars=yes,resizable=yes`;
|
|
198
|
-
let popup = null;
|
|
122
|
+
const stopStatusPolling = (0, react_1.useCallback)(() => {
|
|
123
|
+
if (pollTimerRef.current) {
|
|
124
|
+
clearTimeout(pollTimerRef.current);
|
|
125
|
+
pollTimerRef.current = null;
|
|
126
|
+
}
|
|
127
|
+
pollAttemptsRef.current = 0;
|
|
128
|
+
}, []);
|
|
129
|
+
// Fetch status for an env and, if the seller credentials are present, flip the
|
|
130
|
+
// UI to "connected" and close the PayPal popup. Returns true once connected.
|
|
131
|
+
const refreshStatusAndMaybeConnect = (0, react_1.useCallback)(async (activeEnv) => {
|
|
199
132
|
try {
|
|
200
|
-
|
|
133
|
+
const res = await fetch(`${STATUS_ENDPOINT}?environment=${activeEnv}`, {
|
|
134
|
+
method: "GET",
|
|
135
|
+
credentials: "include",
|
|
136
|
+
});
|
|
137
|
+
const st = await res.json().catch(() => ({}));
|
|
138
|
+
const connected = st?.status === "connected" && st?.seller_client_id_present === true;
|
|
139
|
+
if (connected) {
|
|
140
|
+
completedRef.current = true;
|
|
141
|
+
setStatusInfo(st);
|
|
142
|
+
setConnState("connected");
|
|
143
|
+
setShowManual(false);
|
|
144
|
+
setOnboardingInProgress(false);
|
|
145
|
+
clearCachedUrl(activeEnv);
|
|
146
|
+
closeMiniBrowser();
|
|
147
|
+
stopStatusPolling();
|
|
148
|
+
}
|
|
149
|
+
return connected;
|
|
201
150
|
}
|
|
202
151
|
catch {
|
|
203
|
-
|
|
152
|
+
return false;
|
|
204
153
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
154
|
+
}, [closeMiniBrowser, stopStatusPolling]);
|
|
155
|
+
// While a connect attempt is in flight, the seller may complete onboarding via
|
|
156
|
+
// a path that never reaches the opener (e.g. partner.js fails to fire its
|
|
157
|
+
// callback, or the popup's completion message is blocked). The return_url
|
|
158
|
+
// bridge still saves credentials server-side, so poll status as the reliable
|
|
159
|
+
// fallback: the moment credentials land, this flips to "connected" and closes
|
|
160
|
+
// the stranded popup.
|
|
161
|
+
const startStatusPolling = (0, react_1.useCallback)(() => {
|
|
162
|
+
stopStatusPolling();
|
|
163
|
+
const MAX_ATTEMPTS = 100; // ~5 min at 3s intervals
|
|
164
|
+
const tick = async () => {
|
|
165
|
+
pollAttemptsRef.current += 1;
|
|
166
|
+
const connected = await refreshStatusAndMaybeConnect(envRef.current);
|
|
167
|
+
if (connected || completedRef.current)
|
|
168
|
+
return;
|
|
169
|
+
if (pollAttemptsRef.current >= MAX_ATTEMPTS) {
|
|
170
|
+
stopStatusPolling();
|
|
171
|
+
return;
|
|
212
172
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// completion
|
|
218
|
-
//
|
|
173
|
+
pollTimerRef.current = setTimeout(tick, 3000);
|
|
174
|
+
};
|
|
175
|
+
pollTimerRef.current = setTimeout(tick, 3000);
|
|
176
|
+
}, [refreshStatusAndMaybeConnect, stopStatusPolling]);
|
|
177
|
+
// Shared completion: exchange authCode/sharedId for seller credentials, then
|
|
178
|
+
// close the popup and refresh status. Invoked by partner.js's onboardingCallback
|
|
179
|
+
// and by the return_url bridge's postMessage. Guarded + server-idempotent so
|
|
180
|
+
// redundant invocations are harmless.
|
|
219
181
|
const completeOnboarding = (0, react_1.useCallback)(async (authCode, sharedId) => {
|
|
220
182
|
if (!authCode || !sharedId)
|
|
221
183
|
return;
|
|
222
184
|
if (completedRef.current)
|
|
223
185
|
return;
|
|
224
186
|
completedRef.current = true;
|
|
225
|
-
stopPoll();
|
|
226
187
|
try {
|
|
227
188
|
window.onbeforeunload = null;
|
|
228
189
|
}
|
|
@@ -231,7 +192,6 @@ function PayPalConnectionPage() {
|
|
|
231
192
|
setOnboardingInProgress(true);
|
|
232
193
|
setConnState("loading");
|
|
233
194
|
setError(null);
|
|
234
|
-
setPopupBlocked(false);
|
|
235
195
|
try {
|
|
236
196
|
const res = await fetch(ONBOARDING_COMPLETE_ENDPOINT, {
|
|
237
197
|
method: "POST",
|
|
@@ -243,84 +203,99 @@ function PayPalConnectionPage() {
|
|
|
243
203
|
const txt = await res.text().catch(() => "");
|
|
244
204
|
throw new Error(txt || `Onboarding exchange failed (${res.status})`);
|
|
245
205
|
}
|
|
246
|
-
|
|
247
|
-
popupRef.current?.close();
|
|
248
|
-
}
|
|
249
|
-
catch { }
|
|
250
|
-
try {
|
|
251
|
-
const close1 = window.PAYPAL?.apps?.Signup?.MiniBrowser?.closeFlow;
|
|
252
|
-
if (typeof close1 === "function")
|
|
253
|
-
close1();
|
|
254
|
-
}
|
|
255
|
-
catch { }
|
|
256
|
-
try {
|
|
257
|
-
const close2 = window.PAYPAL?.apps?.Signup?.miniBrowser &&
|
|
258
|
-
window.PAYPAL.apps.Signup.miniBrowser.closeFlow;
|
|
259
|
-
if (typeof close2 === "function")
|
|
260
|
-
close2();
|
|
261
|
-
}
|
|
262
|
-
catch { }
|
|
206
|
+
closeMiniBrowser();
|
|
263
207
|
clearCachedUrl(activeEnv);
|
|
264
208
|
try {
|
|
265
|
-
const statusRes = await fetch(`${STATUS_ENDPOINT}?environment=${activeEnv}`, {
|
|
266
|
-
method: "GET",
|
|
267
|
-
credentials: "include",
|
|
268
|
-
});
|
|
209
|
+
const statusRes = await fetch(`${STATUS_ENDPOINT}?environment=${activeEnv}`, { method: "GET", credentials: "include" });
|
|
269
210
|
const refreshedStatus = await statusRes.json().catch(() => ({}));
|
|
270
211
|
setStatusInfo(refreshedStatus || null);
|
|
271
212
|
}
|
|
272
213
|
catch { }
|
|
273
214
|
setConnState("connected");
|
|
274
215
|
setShowManual(false);
|
|
216
|
+
stopStatusPolling();
|
|
275
217
|
}
|
|
276
218
|
catch (e) {
|
|
277
219
|
completedRef.current = false;
|
|
278
220
|
console.error(e);
|
|
221
|
+
// The bridge may have already saved credentials server-side; if so the
|
|
222
|
+
// poll will surface "connected". Don't strand the user on an error.
|
|
279
223
|
setConnState("error");
|
|
280
224
|
setError(e?.message || "Exchange failed while saving credentials.");
|
|
281
225
|
}
|
|
282
226
|
finally {
|
|
283
227
|
setOnboardingInProgress(false);
|
|
284
228
|
}
|
|
285
|
-
}, [
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
pollRef.current = setInterval(async () => {
|
|
293
|
-
ticks += 1;
|
|
294
|
-
if (completedRef.current || ticks > 150 /* ~5 min */) {
|
|
295
|
-
stopPoll();
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
229
|
+
}, [closeMiniBrowser, stopStatusPolling]);
|
|
230
|
+
// Bind partner.js to the connect button once it has a real onboarding URL.
|
|
231
|
+
const initPartner = (0, react_1.useCallback)(() => {
|
|
232
|
+
const signup = window.PAYPAL?.apps?.Signup;
|
|
233
|
+
const init = (signup?.miniBrowser && signup.miniBrowser.init) ||
|
|
234
|
+
(signup?.MiniBrowser && signup.MiniBrowser.init);
|
|
235
|
+
if (typeof init === "function") {
|
|
298
236
|
try {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
237
|
+
init();
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
console.error("[paypal] partner.js init failed:", e);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}, []);
|
|
244
|
+
// Load partner.js once for the lifetime of the page.
|
|
245
|
+
(0, react_1.useEffect)(() => {
|
|
246
|
+
const existingScript = document.getElementById("paypal-partner-js");
|
|
247
|
+
if (existingScript)
|
|
248
|
+
return;
|
|
249
|
+
const preloadHref = PARTNER_JS_URL;
|
|
250
|
+
let preloadLink = null;
|
|
251
|
+
if (!document.head.querySelector(`link[rel="preload"][href="${preloadHref}"]`)) {
|
|
252
|
+
preloadLink = document.createElement("link");
|
|
253
|
+
preloadLink.rel = "preload";
|
|
254
|
+
preloadLink.href = preloadHref;
|
|
255
|
+
preloadLink.as = "script";
|
|
256
|
+
document.head.appendChild(preloadLink);
|
|
257
|
+
}
|
|
258
|
+
const ppScript = document.createElement("script");
|
|
259
|
+
ppScript.id = "paypal-partner-js";
|
|
260
|
+
ppScript.src = preloadHref;
|
|
261
|
+
ppScript.async = true;
|
|
262
|
+
document.head.appendChild(ppScript);
|
|
263
|
+
return () => {
|
|
264
|
+
if (preloadLink?.parentNode)
|
|
265
|
+
preloadLink.parentNode.removeChild(preloadLink);
|
|
266
|
+
if (ppScript.parentNode)
|
|
267
|
+
ppScript.parentNode.removeChild(ppScript);
|
|
268
|
+
};
|
|
269
|
+
}, []);
|
|
270
|
+
// Put the (cached or freshly generated) onboarding URL on the button, then wait
|
|
271
|
+
// for partner.js to be present before showing the button + initializing it. The
|
|
272
|
+
// button is only revealed once partner.js is ready, which is what guarantees
|
|
273
|
+
// the first click opens the mini-browser popup (never a new tab).
|
|
274
|
+
const activatePayPal = (0, react_1.useCallback)((url, runId) => {
|
|
275
|
+
if (paypalButtonRef.current) {
|
|
276
|
+
paypalButtonRef.current.href = url;
|
|
277
|
+
}
|
|
278
|
+
setFinalUrl(url);
|
|
279
|
+
let attempts = 0;
|
|
280
|
+
const MAX_ATTEMPTS = 200; // ~10s
|
|
281
|
+
const tryInit = () => {
|
|
282
|
+
if (runId !== currentRunId.current)
|
|
283
|
+
return;
|
|
284
|
+
if (window.PAYPAL?.apps?.Signup) {
|
|
285
|
+
initPartner();
|
|
286
|
+
setConnState("ready");
|
|
287
|
+
return;
|
|
317
288
|
}
|
|
318
|
-
|
|
319
|
-
|
|
289
|
+
attempts++;
|
|
290
|
+
if (attempts >= MAX_ATTEMPTS) {
|
|
291
|
+
showError("PayPal partner script failed to load. Please refresh and try again.");
|
|
292
|
+
return;
|
|
320
293
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
294
|
+
setTimeout(tryInit, 50);
|
|
295
|
+
};
|
|
296
|
+
tryInit();
|
|
297
|
+
}, [initPartner, showError]);
|
|
298
|
+
const fetchFreshLink = (0, react_1.useCallback)(async (targetEnv, runId) => {
|
|
324
299
|
if (initLoaderRef.current) {
|
|
325
300
|
const loaderText = initLoaderRef.current.querySelector("#loader-text");
|
|
326
301
|
if (loaderText)
|
|
@@ -344,20 +319,15 @@ function PayPalConnectionPage() {
|
|
|
344
319
|
return;
|
|
345
320
|
}
|
|
346
321
|
const url = href + (href.includes("?") ? "&" : "?") + "displayMode=minibrowser";
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
window.location.reload();
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
setFinalUrl(url);
|
|
353
|
-
setConnState("ready");
|
|
322
|
+
writeCachedUrl(targetEnv, url);
|
|
323
|
+
activatePayPal(url, runId);
|
|
354
324
|
}
|
|
355
325
|
catch {
|
|
356
326
|
if (runId !== currentRunId.current)
|
|
357
327
|
return;
|
|
358
328
|
showError("Unable to connect to service.");
|
|
359
329
|
}
|
|
360
|
-
}, [showError]);
|
|
330
|
+
}, [activatePayPal, showError]);
|
|
361
331
|
// Resolve the server environment first.
|
|
362
332
|
(0, react_1.useEffect)(() => {
|
|
363
333
|
let alive = true;
|
|
@@ -377,7 +347,7 @@ function PayPalConnectionPage() {
|
|
|
377
347
|
alive = false;
|
|
378
348
|
};
|
|
379
349
|
}, []);
|
|
380
|
-
// Page-load flow: connected? -> cached link? -> else generate
|
|
350
|
+
// Page-load flow: connected? -> cached link? -> else generate a fresh link.
|
|
381
351
|
(0, react_1.useEffect)(() => {
|
|
382
352
|
if (!envReady)
|
|
383
353
|
return;
|
|
@@ -388,7 +358,6 @@ function PayPalConnectionPage() {
|
|
|
388
358
|
const run = async () => {
|
|
389
359
|
setConnState("loading");
|
|
390
360
|
setError(null);
|
|
391
|
-
setPopupBlocked(false);
|
|
392
361
|
setFinalUrl("");
|
|
393
362
|
try {
|
|
394
363
|
const r = await fetch(`${STATUS_ENDPOINT}?environment=${env}`, {
|
|
@@ -413,193 +382,52 @@ function PayPalConnectionPage() {
|
|
|
413
382
|
return;
|
|
414
383
|
const cached = readCachedUrl(env);
|
|
415
384
|
if (cached) {
|
|
416
|
-
|
|
417
|
-
setConnState("ready");
|
|
385
|
+
activatePayPal(cached, runId);
|
|
418
386
|
return;
|
|
419
387
|
}
|
|
420
|
-
await
|
|
388
|
+
await fetchFreshLink(env, runId);
|
|
421
389
|
};
|
|
422
390
|
run();
|
|
423
391
|
return () => {
|
|
424
392
|
cancelled = true;
|
|
425
393
|
};
|
|
426
|
-
}, [env, envReady,
|
|
427
|
-
//
|
|
428
|
-
//
|
|
429
|
-
// popup ourselves, so partner.js is NOT relied on to open the window.
|
|
430
|
-
(0, react_1.useEffect)(() => {
|
|
431
|
-
if (connState !== "ready" || !finalUrl)
|
|
432
|
-
return;
|
|
433
|
-
const scriptUrl = PARTNER_JS_URLS[env];
|
|
434
|
-
let cancelled = false;
|
|
435
|
-
let pollTimer;
|
|
436
|
-
const initPartner = (attempt = 0) => {
|
|
437
|
-
if (cancelled)
|
|
438
|
-
return;
|
|
439
|
-
const signup = window.PAYPAL?.apps?.Signup;
|
|
440
|
-
const target = signup?.miniBrowser && typeof signup.miniBrowser.init === "function"
|
|
441
|
-
? signup.miniBrowser
|
|
442
|
-
: signup?.MiniBrowser && typeof signup.MiniBrowser.init === "function"
|
|
443
|
-
? signup.MiniBrowser
|
|
444
|
-
: null;
|
|
445
|
-
if (target?.init) {
|
|
446
|
-
try {
|
|
447
|
-
target.init();
|
|
448
|
-
}
|
|
449
|
-
catch (e) {
|
|
450
|
-
console.error("[paypal] partner.js init failed:", e);
|
|
451
|
-
}
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
if (typeof signup?.render === "function") {
|
|
455
|
-
try {
|
|
456
|
-
signup.render();
|
|
457
|
-
}
|
|
458
|
-
catch (e) {
|
|
459
|
-
console.error("[paypal] partner.js render failed:", e);
|
|
460
|
-
}
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
if (attempt < 40) {
|
|
464
|
-
pollTimer = setTimeout(() => initPartner(attempt + 1), 50);
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
try {
|
|
468
|
-
document.dispatchEvent(new Event("DOMContentLoaded"));
|
|
469
|
-
}
|
|
470
|
-
catch { }
|
|
471
|
-
};
|
|
472
|
-
const existingScript = document.getElementById("paypal-partner-js");
|
|
473
|
-
if (existingScript) {
|
|
474
|
-
existingScript.parentNode?.removeChild(existingScript);
|
|
475
|
-
}
|
|
476
|
-
if (window.PAYPAL?.apps?.Signup) {
|
|
477
|
-
try {
|
|
478
|
-
delete window.PAYPAL.apps.Signup;
|
|
479
|
-
}
|
|
480
|
-
catch { }
|
|
481
|
-
}
|
|
482
|
-
const ppScript = document.createElement("script");
|
|
483
|
-
ppScript.id = "paypal-partner-js";
|
|
484
|
-
ppScript.src = scriptUrl;
|
|
485
|
-
ppScript.async = true;
|
|
486
|
-
ppScript.onload = () => initPartner();
|
|
487
|
-
document.body.appendChild(ppScript);
|
|
488
|
-
return () => {
|
|
489
|
-
cancelled = true;
|
|
490
|
-
if (pollTimer)
|
|
491
|
-
clearTimeout(pollTimer);
|
|
492
|
-
if (ppScript.parentNode)
|
|
493
|
-
ppScript.parentNode.removeChild(ppScript);
|
|
494
|
-
};
|
|
495
|
-
}, [connState, finalUrl, env]);
|
|
496
|
-
// PRIMARY completion path: receive PayPal's onboarding postMessage ourselves.
|
|
497
|
-
// The diagnostic log lets us confirm the exact origin/shape PayPal sends in a
|
|
498
|
-
// real run if a tweak to the parser is ever needed.
|
|
499
|
-
(0, react_1.useEffect)(() => {
|
|
500
|
-
const onMessage = (ev) => {
|
|
501
|
-
let serialized = "";
|
|
502
|
-
try {
|
|
503
|
-
serialized =
|
|
504
|
-
typeof ev.data === "string" ? ev.data : JSON.stringify(ev.data);
|
|
505
|
-
}
|
|
506
|
-
catch {
|
|
507
|
-
serialized = String(ev.data);
|
|
508
|
-
}
|
|
509
|
-
const looksRelevant = isPayPalOrigin(ev.origin) ||
|
|
510
|
-
/auth_?code|shared_?id|onboard|merchantId/i.test(serialized || "");
|
|
511
|
-
if (looksRelevant) {
|
|
512
|
-
// eslint-disable-next-line no-console
|
|
513
|
-
console.info("[PayPal onboarding] message from", ev.origin, "·", (serialized || "").slice(0, 500));
|
|
514
|
-
}
|
|
515
|
-
// TERTIARY completion path: our own return_url bridge page (served by
|
|
516
|
-
// GET /store/paypal/onboard-return) posts this from inside the popup when
|
|
517
|
-
// PayPal redirects there at the end of onboarding. It runs on our backend
|
|
518
|
-
// origin (not paypal.com), so it is handled before the PayPal-origin gate
|
|
519
|
-
// below. It guarantees the popup is closed and the status is re-checked even
|
|
520
|
-
// if every PayPal postMessage was missed.
|
|
521
|
-
const d = ev.data;
|
|
522
|
-
if (d && typeof d === "object" && d.source === "paypal-onboarding-return") {
|
|
523
|
-
try {
|
|
524
|
-
popupRef.current?.close();
|
|
525
|
-
}
|
|
526
|
-
catch { }
|
|
527
|
-
const fromReturn = extractAuth(d.params);
|
|
528
|
-
if (fromReturn.authCode && fromReturn.sharedId) {
|
|
529
|
-
completeOnboarding(fromReturn.authCode, fromReturn.sharedId);
|
|
530
|
-
}
|
|
531
|
-
else if (!completedRef.current) {
|
|
532
|
-
// No authCode/sharedId on the return URL (the usual case): credentials
|
|
533
|
-
// are exchanged via the postMessage/onboardingCallback path. Poll the
|
|
534
|
-
// server so the UI flips to "connected" as soon as that lands.
|
|
535
|
-
startStatusPoll();
|
|
536
|
-
}
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
if (!isPayPalOrigin(ev.origin))
|
|
540
|
-
return;
|
|
541
|
-
const { authCode, sharedId } = extractAuth(ev.data);
|
|
542
|
-
if (authCode && sharedId) {
|
|
543
|
-
completeOnboarding(authCode, sharedId);
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
window.addEventListener("message", onMessage);
|
|
547
|
-
return () => window.removeEventListener("message", onMessage);
|
|
548
|
-
}, [completeOnboarding, startStatusPoll]);
|
|
549
|
-
// SECONDARY completion path: partner.js's bridge calls this by name.
|
|
394
|
+
}, [env, envReady, fetchFreshLink, activatePayPal]);
|
|
395
|
+
// partner.js invokes this by name (data-paypal-onboard-complete) once PayPal
|
|
396
|
+
// returns the seller's authCode + sharedId.
|
|
550
397
|
(0, react_1.useLayoutEffect)(() => {
|
|
551
398
|
window.onboardingCallback = (authCode, sharedId) => {
|
|
552
|
-
completeOnboarding(authCode, sharedId);
|
|
399
|
+
void completeOnboarding(authCode, sharedId);
|
|
553
400
|
};
|
|
554
401
|
return () => {
|
|
555
402
|
window.onboardingCallback = undefined;
|
|
556
403
|
};
|
|
557
404
|
}, [completeOnboarding]);
|
|
558
|
-
//
|
|
559
|
-
//
|
|
560
|
-
//
|
|
561
|
-
//
|
|
562
|
-
//
|
|
563
|
-
// this very window instead of opening a second one. That adoption is what lets
|
|
564
|
-
// partner.js receive PayPal's completion postMessage, invoke onboardingCallback
|
|
565
|
-
// and auto-close the popup.
|
|
566
|
-
//
|
|
567
|
-
// IMPORTANT: we only preventDefault() (to stop the native anchor from opening a
|
|
568
|
-
// new tab) and deliberately DO NOT stopImmediatePropagation(): stopping it was
|
|
569
|
-
// what previously prevented partner.js from tracking the window, which broke
|
|
570
|
-
// onboarding completion (the popup never closed and credentials were never
|
|
571
|
-
// exchanged).
|
|
405
|
+
// The return_url bridge (/store/paypal/onboard-return), which runs inside the
|
|
406
|
+
// popup after PayPal redirects to it, posts its params back here. The bridge has
|
|
407
|
+
// already exchanged credentials server-side, so we just refresh status to flip
|
|
408
|
+
// to "connected" and close the popup. If for some reason credentials aren't yet
|
|
409
|
+
// saved but PayPal forwarded an authCode/sharedId, complete it from here too.
|
|
572
410
|
(0, react_1.useEffect)(() => {
|
|
573
|
-
const
|
|
574
|
-
const
|
|
575
|
-
if (!
|
|
576
|
-
return;
|
|
577
|
-
const target = e.target;
|
|
578
|
-
if (!target || !btn.contains(target))
|
|
579
|
-
return;
|
|
580
|
-
// Stop the native anchor navigation only. Let the event keep propagating so
|
|
581
|
-
// partner.js's own click handler still runs and adopts our popup window.
|
|
582
|
-
e.preventDefault();
|
|
583
|
-
if (connStateRef.current !== "ready" ||
|
|
584
|
-
!finalUrlRef.current ||
|
|
585
|
-
inProgressRef.current) {
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
completedRef.current = false;
|
|
589
|
-
const popup = openOnboardingPopup(finalUrlRef.current);
|
|
590
|
-
if (!popup) {
|
|
591
|
-
setPopupBlocked(true);
|
|
411
|
+
const onMessage = (event) => {
|
|
412
|
+
const data = event?.data;
|
|
413
|
+
if (!data || data.source !== "paypal-onboarding-return")
|
|
592
414
|
return;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
|
|
415
|
+
const params = data.params || {};
|
|
416
|
+
const authCode = params.authCode || params.auth_code;
|
|
417
|
+
const sharedId = params.sharedId || params.shared_id;
|
|
418
|
+
const activeEnv = envRef.current;
|
|
419
|
+
void (async () => {
|
|
420
|
+
const connected = await refreshStatusAndMaybeConnect(activeEnv);
|
|
421
|
+
if (!connected && authCode && sharedId) {
|
|
422
|
+
await completeOnboarding(authCode, sharedId);
|
|
423
|
+
}
|
|
424
|
+
})();
|
|
596
425
|
};
|
|
597
|
-
|
|
598
|
-
return () =>
|
|
599
|
-
}, [
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
}, [stopPoll]);
|
|
426
|
+
window.addEventListener("message", onMessage);
|
|
427
|
+
return () => window.removeEventListener("message", onMessage);
|
|
428
|
+
}, [completeOnboarding, refreshStatusAndMaybeConnect]);
|
|
429
|
+
// Always stop polling when the component unmounts.
|
|
430
|
+
(0, react_1.useEffect)(() => stopStatusPolling, [stopStatusPolling]);
|
|
603
431
|
(0, react_1.useLayoutEffect)(() => {
|
|
604
432
|
const el = ppBtnMeasureRef.current;
|
|
605
433
|
if (!el)
|
|
@@ -625,10 +453,17 @@ function PayPalConnectionPage() {
|
|
|
625
453
|
window.removeEventListener("resize", update);
|
|
626
454
|
};
|
|
627
455
|
}, [connState, env, finalUrl]);
|
|
628
|
-
//
|
|
629
|
-
//
|
|
456
|
+
// Only block the click while we are not ready; when ready, let the native click
|
|
457
|
+
// through so partner.js can open the mini-browser. Start polling status so we
|
|
458
|
+
// detect completion (and close the popup) even if no completion message reaches
|
|
459
|
+
// this page.
|
|
630
460
|
const handleConnectClick = (e) => {
|
|
631
|
-
|
|
461
|
+
if (connState !== "ready" || !finalUrl || onboardingInProgress) {
|
|
462
|
+
e.preventDefault();
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
completedRef.current = false;
|
|
466
|
+
startStatusPolling();
|
|
632
467
|
};
|
|
633
468
|
const handleSaveManual = async () => {
|
|
634
469
|
if (!canSaveManual || onboardingInProgress)
|
|
@@ -675,6 +510,7 @@ function PayPalConnectionPage() {
|
|
|
675
510
|
return;
|
|
676
511
|
if (!window.confirm("Disconnect PayPal for this environment?"))
|
|
677
512
|
return;
|
|
513
|
+
stopStatusPolling();
|
|
678
514
|
setOnboardingInProgress(true);
|
|
679
515
|
setConnState("loading");
|
|
680
516
|
setError(null);
|
|
@@ -695,7 +531,7 @@ function PayPalConnectionPage() {
|
|
|
695
531
|
clearCachedUrl(env);
|
|
696
532
|
completedRef.current = false;
|
|
697
533
|
currentRunId.current = ++runIdRef.current;
|
|
698
|
-
await
|
|
534
|
+
await fetchFreshLink(env, currentRunId.current);
|
|
699
535
|
}
|
|
700
536
|
catch (e) {
|
|
701
537
|
console.error(e);
|
|
@@ -710,9 +546,9 @@ function PayPalConnectionPage() {
|
|
|
710
546
|
const next = e.target.value;
|
|
711
547
|
if (next === env || onboardingInProgress)
|
|
712
548
|
return;
|
|
549
|
+
stopStatusPolling();
|
|
713
550
|
completedRef.current = false;
|
|
714
551
|
clearCachedUrl();
|
|
715
|
-
setPopupBlocked(false);
|
|
716
552
|
setConnState("loading");
|
|
717
553
|
try {
|
|
718
554
|
await fetch(ENVIRONMENT_ENDPOINT, {
|
|
@@ -725,16 +561,16 @@ function PayPalConnectionPage() {
|
|
|
725
561
|
catch { }
|
|
726
562
|
setEnv(next);
|
|
727
563
|
};
|
|
728
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "p-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-6", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex items-start justify-between gap-4", children: (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("h1", { className: "text-xl font-semibold text-ui-fg-base", children: "PayPal Gateway By Easy Payment" }) }) }), (0, jsx_runtime_1.jsx)(Tabs_1.default, {}), (0, jsx_runtime_1.jsx)("div", { className: "rounded-xl border border-ui-border-base bg-ui-bg-base shadow-sm", children: (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 gap-y-6 p-4 md:grid-cols-[260px_1fr] md:items-start", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-sm font-medium pt-2", children: "Environment" }), (0, jsx_runtime_1.jsx)("div", { className: "max-w-xl", children: (0, jsx_runtime_1.jsxs)("select", { value: env, onChange: handleEnvChange, disabled: onboardingInProgress, className: "w-full rounded-md border border-ui-border-base bg-transparent px-3 py-2 text-sm", children: [(0, jsx_runtime_1.jsx)("option", { value: "sandbox", children: "Sandbox (Test Mode)" }), (0, jsx_runtime_1.jsx)("option", { value: "live", children: "Live (Production)" })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm font-medium pt-2", children: env === "sandbox" ? "Connect to PayPal (Sandbox)" : "Connect to PayPal" }), (0, jsx_runtime_1.jsx)("div", { className: "max-w-xl", children: connState === "connected" ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.
|
|
564
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "p-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-6", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex items-start justify-between gap-4", children: (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("h1", { className: "text-xl font-semibold text-ui-fg-base", children: "PayPal Gateway By Easy Payment" }) }) }), (0, jsx_runtime_1.jsx)(Tabs_1.default, {}), (0, jsx_runtime_1.jsx)("div", { className: "rounded-xl border border-ui-border-base bg-ui-bg-base shadow-sm", children: (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 gap-y-6 p-4 md:grid-cols-[260px_1fr] md:items-start", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-sm font-medium pt-2", children: "Environment" }), (0, jsx_runtime_1.jsx)("div", { className: "max-w-xl", children: (0, jsx_runtime_1.jsxs)("select", { value: env, onChange: handleEnvChange, disabled: onboardingInProgress, className: "w-full rounded-md border border-ui-border-base bg-transparent px-3 py-2 text-sm", children: [(0, jsx_runtime_1.jsx)("option", { value: "sandbox", children: "Sandbox (Test Mode)" }), (0, jsx_runtime_1.jsx)("option", { value: "live", children: "Live (Production)" })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm font-medium pt-2", children: env === "sandbox" ? "Connect to PayPal (Sandbox)" : "Connect to PayPal" }), (0, jsx_runtime_1.jsx)("div", { className: "max-w-xl", children: connState === "connected" ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-sm text-green-600 bg-green-50 p-3 rounded border border-green-200", children: ["\u2705 Successfully connected to PayPal!", (0, jsx_runtime_1.jsx)("a", { "data-paypal-button": "true", "data-paypal-onboard-complete": "onboardingCallback", href: "#", style: { display: "none" }, children: "PayPal" })] }), (0, jsx_runtime_1.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: [(0, jsx_runtime_1.jsx)("div", { className: "font-medium text-ui-fg-base", children: "Connected PayPal account" }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-1", children: ["Email:", " ", (0, jsx_runtime_1.jsx)("span", { className: "font-mono text-ui-fg-base", children: statusInfo?.seller_email || "Unavailable" })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "mt-3 flex items-center gap-2", children: (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: handleDisconnect, disabled: onboardingInProgress, className: "transition-fg relative inline-flex w-fit items-center justify-center overflow-hidden rounded-md outline-none shadow-buttons-neutral text-ui-fg-base bg-ui-button-neutral after:transition-fg after:absolute after:inset-0 after:content-[''] after:button-neutral-gradient hover:bg-ui-button-neutral-hover hover:after:button-neutral-hover-gradient active:bg-ui-button-neutral-pressed active:after:button-neutral-pressed-gradient focus-visible:shadow-buttons-neutral-focus disabled:bg-ui-bg-disabled disabled:border-ui-border-base disabled:text-ui-fg-disabled disabled:shadow-buttons-neutral disabled:after:hidden txt-compact-small-plus px-3 py-1.5", children: "Disconnect" }) })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { ref: initLoaderRef, id: "init-loader", className: `status-msg mb-4 ${connState !== "loading" ? "hidden" : "block"}`, children: [(0, jsx_runtime_1.jsx)("div", { className: "loader inline-block align-middle mr-2" }), (0, jsx_runtime_1.jsx)("span", { id: "loader-text", className: "text-sm", children: onboardingInProgress
|
|
729
565
|
? "Configuring connection to PayPal…"
|
|
730
566
|
: "Checking connection..." })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${connState === "ready" ? "block" : "hidden"}`, children: [(0, jsx_runtime_1.jsx)("a", { ref: (node) => {
|
|
731
567
|
paypalButtonRef.current = node;
|
|
732
568
|
ppBtnMeasureRef.current = node;
|
|
733
|
-
}, id: "paypal-button",
|
|
569
|
+
}, id: "paypal-button", "data-paypal-button": "true", href: finalUrl || "#", "data-paypal-onboard-complete": "onboardingCallback", onClick: handleConnectClick, className: "transition-fg relative inline-flex w-fit items-center justify-center overflow-hidden rounded-md outline-none no-underline shadow-buttons-neutral text-ui-fg-base bg-ui-button-neutral after:transition-fg after:absolute after:inset-0 after:content-[''] after:button-neutral-gradient hover:bg-ui-button-neutral-hover hover:after:button-neutral-hover-gradient active:bg-ui-button-neutral-pressed active:after:button-neutral-pressed-gradient focus-visible:shadow-buttons-neutral-focus disabled:bg-ui-bg-disabled disabled:border-ui-border-base disabled:text-ui-fg-disabled disabled:shadow-buttons-neutral disabled:after:hidden txt-compact-small-plus px-3 py-1.5", style: {
|
|
734
570
|
cursor: onboardingInProgress ? "not-allowed" : "pointer",
|
|
735
571
|
opacity: onboardingInProgress ? 0.6 : 1,
|
|
736
572
|
pointerEvents: onboardingInProgress ? "none" : "auto",
|
|
737
|
-
}, children: "Connect to PayPal" }),
|
|
573
|
+
}, children: "Connect to PayPal" }), (0, jsx_runtime_1.jsx)("div", { className: "mt-2", style: {
|
|
738
574
|
width: ppBtnWidth ? `${ppBtnWidth}px` : "auto",
|
|
739
575
|
marginTop: "20px",
|
|
740
576
|
marginBottom: "10px",
|