@easypayment/medusa-payment-paypal 0.9.21 → 0.9.22

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.
@@ -133,22 +133,20 @@ async function adminFetch(path, opts = {}) {
133
133
  }
134
134
  }
135
135
  const DEFAULT_FORM$1 = {
136
- enabled: true,
137
- title: "Credit or Debit Card",
138
- threeDS: "when_required"
136
+ paymentAction: "capture",
137
+ brandName: "PayPal",
138
+ landingPage: "no_preference",
139
+ requireInstantPayment: false,
140
+ sendItemDetails: true,
141
+ invoicePrefix: "PP-",
142
+ creditCardStatementName: "PayPal"
139
143
  };
140
144
  function mergeWithDefaults$1(saved) {
141
145
  if (!saved) return { ...DEFAULT_FORM$1 };
142
146
  const entries = Object.entries(saved).filter(([, value]) => value !== void 0);
143
147
  return { ...DEFAULT_FORM$1, ...Object.fromEntries(entries) };
144
148
  }
145
- const THREE_DS_OPTIONS = [
146
- { value: "when_required", label: "3D Secure when required", hint: "Triggers 3DS only when the card / issuer requires it." },
147
- { value: "sli", label: "3D Secure (SCA) / liability shift (recommended)", hint: "Attempts to optimize for liability shift while remaining compliant." },
148
- { value: "always", label: "Always request 3D Secure", hint: "Forces 3DS challenge whenever possible (may reduce conversion)." }
149
- ];
150
- function AdvancedCardPaymentsTab() {
151
- var _a, _b;
149
+ function AdditionalSettingsTab() {
152
150
  const [form, setForm] = useState(() => ({ ...DEFAULT_FORM$1 }));
153
151
  const [loading, setLoading] = useState(false);
154
152
  const [saving, setSaving] = useState(false);
@@ -163,7 +161,7 @@ function AdvancedCardPaymentsTab() {
163
161
  setLoading(true);
164
162
  const json = await adminFetch("/admin/paypal/settings");
165
163
  const payload = (json == null ? void 0 : json.data) ?? json;
166
- const saved = payload == null ? void 0 : payload.advanced_card_payments;
164
+ const saved = payload == null ? void 0 : payload.additional_settings;
167
165
  if (saved && typeof saved === "object") setForm(mergeWithDefaults$1(saved));
168
166
  } catch {
169
167
  } finally {
@@ -174,16 +172,14 @@ function AdvancedCardPaymentsTab() {
174
172
  async function onSave() {
175
173
  try {
176
174
  setSaving(true);
177
- const json = await adminFetch("/admin/paypal/settings", {
178
- method: "POST",
179
- body: { advanced_card_payments: form }
180
- });
175
+ setToast(null);
176
+ const json = await adminFetch("/admin/paypal/settings", { method: "POST", body: { additional_settings: form } });
181
177
  const payload = (json == null ? void 0 : json.data) ?? json;
182
- const saved = payload == null ? void 0 : payload.advanced_card_payments;
178
+ const saved = payload == null ? void 0 : payload.additional_settings;
183
179
  if (saved && typeof saved === "object") setForm(mergeWithDefaults$1(saved));
184
180
  setToast({ kind: "success", message: "Settings saved" });
185
181
  } catch (e) {
186
- setToast({ kind: "error", message: (e instanceof Error ? e.message : "") || "Failed to save settings." });
182
+ setToast({ kind: "error", message: e instanceof Error ? e.message : "Failed to save settings" });
187
183
  } finally {
188
184
  setSaving(false);
189
185
  }
@@ -195,42 +191,55 @@ function AdvancedCardPaymentsTab() {
195
191
  /* @__PURE__ */ jsx(
196
192
  SectionCard,
197
193
  {
198
- title: "Advanced Card Payments",
199
- description: "Control card checkout settings and 3D Secure behavior.",
194
+ title: "Additional Settings",
195
+ description: "These settings control checkout behavior and PayPal experience.",
200
196
  right: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
201
197
  /* @__PURE__ */ jsx("button", { type: "button", onClick: onSave, disabled: saving || loading, 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: saving ? "Saving..." : "Save settings" }),
202
198
  loading ? /* @__PURE__ */ jsx("span", { className: "text-sm text-ui-fg-subtle", children: "Loading…" }) : null
203
199
  ] }),
204
200
  children: /* @__PURE__ */ jsxs("div", { className: "divide-y divide-ui-border-base", children: [
205
- /* @__PURE__ */ jsx(FieldRow, { label: "Enable/Disable", children: /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2", children: [
206
- /* @__PURE__ */ jsx("input", { type: "checkbox", checked: form.enabled, onChange: (e) => setForm((p) => ({ ...p, enabled: e.target.checked })), className: "h-4 w-4 rounded border-ui-border-base" }),
207
- /* @__PURE__ */ jsx("span", { className: "text-sm text-ui-fg-base", children: "Enable Advanced Credit/Debit Card" })
201
+ /* @__PURE__ */ jsx(FieldRow, { label: "Payment action", htmlFor: "as-payment-action", children: /* @__PURE__ */ jsxs("select", { id: "as-payment-action", value: form.paymentAction, onChange: (e) => setForm((p) => ({ ...p, paymentAction: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", children: [
202
+ /* @__PURE__ */ jsx("option", { value: "capture", children: "Capture" }),
203
+ /* @__PURE__ */ jsx("option", { value: "authorize", children: "Authorize" })
208
204
  ] }) }),
209
- /* @__PURE__ */ jsx(FieldRow, { label: "Title", htmlFor: "acp-title", children: /* @__PURE__ */ jsx("input", { id: "acp-title", value: form.title, onChange: (e) => setForm((p) => ({ ...p, title: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", placeholder: "Credit or Debit Card" }) }),
210
- /* @__PURE__ */ jsx(FieldRow, { label: "Contingency for 3D Secure", hint: "Choose when 3D Secure should be triggered during card payments.", htmlFor: "acp-threeds", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
211
- /* @__PURE__ */ jsx("select", { id: "acp-threeds", value: form.threeDS, onChange: (e) => setForm((p) => ({ ...p, threeDS: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", children: THREE_DS_OPTIONS.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value)) }),
212
- ((_a = THREE_DS_OPTIONS.find((o) => o.value === form.threeDS)) == null ? void 0 : _a.hint) ? /* @__PURE__ */ jsx("div", { className: "text-xs text-ui-fg-subtle", children: (_b = THREE_DS_OPTIONS.find((o) => o.value === form.threeDS)) == null ? void 0 : _b.hint }) : null
213
- ] }) })
205
+ /* @__PURE__ */ jsx(FieldRow, { label: "Brand Name", htmlFor: "as-brand-name", children: /* @__PURE__ */ jsx("input", { id: "as-brand-name", value: form.brandName, onChange: (e) => setForm((p) => ({ ...p, brandName: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", placeholder: "PayPal" }) }),
206
+ /* @__PURE__ */ jsx(FieldRow, { label: "Landing Page", htmlFor: "as-landing-page", children: /* @__PURE__ */ jsxs("select", { id: "as-landing-page", value: form.landingPage, onChange: (e) => setForm((p) => ({ ...p, landingPage: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", children: [
207
+ /* @__PURE__ */ jsx("option", { value: "no_preference", children: "No Preference" }),
208
+ /* @__PURE__ */ jsx("option", { value: "login", children: "Login" }),
209
+ /* @__PURE__ */ jsx("option", { value: "billing", children: "Billing" })
210
+ ] }) }),
211
+ /* @__PURE__ */ jsx(FieldRow, { label: "Instant Payments", children: /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2", children: [
212
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: form.requireInstantPayment, onChange: (e) => setForm((p) => ({ ...p, requireInstantPayment: e.target.checked })), className: "h-4 w-4 rounded border-ui-border-base" }),
213
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-ui-fg-base", children: "Require Instant Payment" })
214
+ ] }) }),
215
+ /* @__PURE__ */ jsx(FieldRow, { label: "Send Item Details", hint: "Include all line item details in the payment request to PayPal so that they can be seen from the PayPal transaction details page.", children: /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2", children: [
216
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: form.sendItemDetails, onChange: (e) => setForm((p) => ({ ...p, sendItemDetails: e.target.checked })), className: "h-4 w-4 rounded border-ui-border-base" }),
217
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-ui-fg-base", children: "Send line item details to PayPal" })
218
+ ] }) }),
219
+ /* @__PURE__ */ jsx(FieldRow, { label: "Invoice prefix", htmlFor: "as-invoice-prefix", children: /* @__PURE__ */ jsx("input", { id: "as-invoice-prefix", value: form.invoicePrefix, onChange: (e) => setForm((p) => ({ ...p, invoicePrefix: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", placeholder: "PP-" }) }),
220
+ /* @__PURE__ */ jsx(FieldRow, { label: "Credit Card Statement Name", htmlFor: "as-cc-statement", children: /* @__PURE__ */ jsx("input", { id: "as-cc-statement", value: form.creditCardStatementName, onChange: (e) => setForm((p) => ({ ...p, creditCardStatementName: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", placeholder: "PayPal" }) })
214
221
  ] })
215
222
  }
216
223
  )
217
224
  ] }) });
218
225
  }
219
226
  const DEFAULT_FORM = {
220
- paymentAction: "capture",
221
- brandName: "PayPal",
222
- landingPage: "no_preference",
223
- requireInstantPayment: false,
224
- sendItemDetails: true,
225
- invoicePrefix: "PP-",
226
- creditCardStatementName: "PayPal"
227
+ enabled: true,
228
+ title: "Credit or Debit Card",
229
+ threeDS: "when_required"
227
230
  };
228
231
  function mergeWithDefaults(saved) {
229
232
  if (!saved) return { ...DEFAULT_FORM };
230
233
  const entries = Object.entries(saved).filter(([, value]) => value !== void 0);
231
234
  return { ...DEFAULT_FORM, ...Object.fromEntries(entries) };
232
235
  }
233
- function AdditionalSettingsTab() {
236
+ const THREE_DS_OPTIONS = [
237
+ { value: "when_required", label: "3D Secure when required", hint: "Triggers 3DS only when the card / issuer requires it." },
238
+ { value: "sli", label: "3D Secure (SCA) / liability shift (recommended)", hint: "Attempts to optimize for liability shift while remaining compliant." },
239
+ { value: "always", label: "Always request 3D Secure", hint: "Forces 3DS challenge whenever possible (may reduce conversion)." }
240
+ ];
241
+ function AdvancedCardPaymentsTab() {
242
+ var _a, _b;
234
243
  const [form, setForm] = useState(() => ({ ...DEFAULT_FORM }));
235
244
  const [loading, setLoading] = useState(false);
236
245
  const [saving, setSaving] = useState(false);
@@ -245,7 +254,7 @@ function AdditionalSettingsTab() {
245
254
  setLoading(true);
246
255
  const json = await adminFetch("/admin/paypal/settings");
247
256
  const payload = (json == null ? void 0 : json.data) ?? json;
248
- const saved = payload == null ? void 0 : payload.additional_settings;
257
+ const saved = payload == null ? void 0 : payload.advanced_card_payments;
249
258
  if (saved && typeof saved === "object") setForm(mergeWithDefaults(saved));
250
259
  } catch {
251
260
  } finally {
@@ -256,14 +265,16 @@ function AdditionalSettingsTab() {
256
265
  async function onSave() {
257
266
  try {
258
267
  setSaving(true);
259
- setToast(null);
260
- const json = await adminFetch("/admin/paypal/settings", { method: "POST", body: { additional_settings: form } });
268
+ const json = await adminFetch("/admin/paypal/settings", {
269
+ method: "POST",
270
+ body: { advanced_card_payments: form }
271
+ });
261
272
  const payload = (json == null ? void 0 : json.data) ?? json;
262
- const saved = payload == null ? void 0 : payload.additional_settings;
273
+ const saved = payload == null ? void 0 : payload.advanced_card_payments;
263
274
  if (saved && typeof saved === "object") setForm(mergeWithDefaults(saved));
264
275
  setToast({ kind: "success", message: "Settings saved" });
265
276
  } catch (e) {
266
- setToast({ kind: "error", message: e instanceof Error ? e.message : "Failed to save settings" });
277
+ setToast({ kind: "error", message: (e instanceof Error ? e.message : "") || "Failed to save settings." });
267
278
  } finally {
268
279
  setSaving(false);
269
280
  }
@@ -275,33 +286,22 @@ function AdditionalSettingsTab() {
275
286
  /* @__PURE__ */ jsx(
276
287
  SectionCard,
277
288
  {
278
- title: "Additional Settings",
279
- description: "These settings control checkout behavior and PayPal experience.",
289
+ title: "Advanced Card Payments",
290
+ description: "Control card checkout settings and 3D Secure behavior.",
280
291
  right: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
281
292
  /* @__PURE__ */ jsx("button", { type: "button", onClick: onSave, disabled: saving || loading, 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: saving ? "Saving..." : "Save settings" }),
282
293
  loading ? /* @__PURE__ */ jsx("span", { className: "text-sm text-ui-fg-subtle", children: "Loading…" }) : null
283
294
  ] }),
284
295
  children: /* @__PURE__ */ jsxs("div", { className: "divide-y divide-ui-border-base", children: [
285
- /* @__PURE__ */ jsx(FieldRow, { label: "Payment action", htmlFor: "as-payment-action", children: /* @__PURE__ */ jsxs("select", { id: "as-payment-action", value: form.paymentAction, onChange: (e) => setForm((p) => ({ ...p, paymentAction: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", children: [
286
- /* @__PURE__ */ jsx("option", { value: "capture", children: "Capture" }),
287
- /* @__PURE__ */ jsx("option", { value: "authorize", children: "Authorize" })
288
- ] }) }),
289
- /* @__PURE__ */ jsx(FieldRow, { label: "Brand Name", htmlFor: "as-brand-name", children: /* @__PURE__ */ jsx("input", { id: "as-brand-name", value: form.brandName, onChange: (e) => setForm((p) => ({ ...p, brandName: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", placeholder: "PayPal" }) }),
290
- /* @__PURE__ */ jsx(FieldRow, { label: "Landing Page", htmlFor: "as-landing-page", children: /* @__PURE__ */ jsxs("select", { id: "as-landing-page", value: form.landingPage, onChange: (e) => setForm((p) => ({ ...p, landingPage: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", children: [
291
- /* @__PURE__ */ jsx("option", { value: "no_preference", children: "No Preference" }),
292
- /* @__PURE__ */ jsx("option", { value: "login", children: "Login" }),
293
- /* @__PURE__ */ jsx("option", { value: "billing", children: "Billing" })
294
- ] }) }),
295
- /* @__PURE__ */ jsx(FieldRow, { label: "Instant Payments", children: /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2", children: [
296
- /* @__PURE__ */ jsx("input", { type: "checkbox", checked: form.requireInstantPayment, onChange: (e) => setForm((p) => ({ ...p, requireInstantPayment: e.target.checked })), className: "h-4 w-4 rounded border-ui-border-base" }),
297
- /* @__PURE__ */ jsx("span", { className: "text-sm text-ui-fg-base", children: "Require Instant Payment" })
298
- ] }) }),
299
- /* @__PURE__ */ jsx(FieldRow, { label: "Send Item Details", hint: "Include all line item details in the payment request to PayPal so that they can be seen from the PayPal transaction details page.", children: /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2", children: [
300
- /* @__PURE__ */ jsx("input", { type: "checkbox", checked: form.sendItemDetails, onChange: (e) => setForm((p) => ({ ...p, sendItemDetails: e.target.checked })), className: "h-4 w-4 rounded border-ui-border-base" }),
301
- /* @__PURE__ */ jsx("span", { className: "text-sm text-ui-fg-base", children: "Send line item details to PayPal" })
296
+ /* @__PURE__ */ jsx(FieldRow, { label: "Enable/Disable", children: /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-2", children: [
297
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: form.enabled, onChange: (e) => setForm((p) => ({ ...p, enabled: e.target.checked })), className: "h-4 w-4 rounded border-ui-border-base" }),
298
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-ui-fg-base", children: "Enable Advanced Credit/Debit Card" })
302
299
  ] }) }),
303
- /* @__PURE__ */ jsx(FieldRow, { label: "Invoice prefix", htmlFor: "as-invoice-prefix", children: /* @__PURE__ */ jsx("input", { id: "as-invoice-prefix", value: form.invoicePrefix, onChange: (e) => setForm((p) => ({ ...p, invoicePrefix: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", placeholder: "PP-" }) }),
304
- /* @__PURE__ */ jsx(FieldRow, { label: "Credit Card Statement Name", htmlFor: "as-cc-statement", children: /* @__PURE__ */ jsx("input", { id: "as-cc-statement", value: form.creditCardStatementName, onChange: (e) => setForm((p) => ({ ...p, creditCardStatementName: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", placeholder: "PayPal" }) })
300
+ /* @__PURE__ */ jsx(FieldRow, { label: "Title", htmlFor: "acp-title", children: /* @__PURE__ */ jsx("input", { id: "acp-title", value: form.title, onChange: (e) => setForm((p) => ({ ...p, title: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", placeholder: "Credit or Debit Card" }) }),
301
+ /* @__PURE__ */ jsx(FieldRow, { label: "Contingency for 3D Secure", hint: "Choose when 3D Secure should be triggered during card payments.", htmlFor: "acp-threeds", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
302
+ /* @__PURE__ */ jsx("select", { id: "acp-threeds", value: form.threeDS, onChange: (e) => setForm((p) => ({ ...p, threeDS: e.target.value })), className: "w-full rounded-md border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm text-ui-fg-base outline-none focus:ring-2 focus:ring-ui-border-interactive", children: THREE_DS_OPTIONS.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value)) }),
303
+ ((_a = THREE_DS_OPTIONS.find((o) => o.value === form.threeDS)) == null ? void 0 : _a.hint) ? /* @__PURE__ */ jsx("div", { className: "text-xs text-ui-fg-subtle", children: (_b = THREE_DS_OPTIONS.find((o) => o.value === form.threeDS)) == null ? void 0 : _b.hint }) : null
304
+ ] }) })
305
305
  ] })
306
306
  }
307
307
  )
@@ -323,7 +323,7 @@ const PARTNER_JS_URLS = {
323
323
  const SERVICE_URL = "/admin/paypal/onboarding-link";
324
324
  const POPUP_NAME = "PPFrame";
325
325
  const CACHE_PREFIX = "pp_onboard_cache";
326
- const CACHE_EXPIRY = 10 * 60 * 1e3;
326
+ const CACHE_EXPIRY = 6 * 60 * 60 * 1e3;
327
327
  const ONBOARDING_COMPLETE_ENDPOINT = "/admin/paypal/onboard-complete";
328
328
  const STATUS_ENDPOINT = "/admin/paypal/status";
329
329
  const SAVE_CREDENTIALS_ENDPOINT = "/admin/paypal/save-credentials";
@@ -346,7 +346,9 @@ function readCachedUrl(env) {
346
346
  function writeCachedUrl(env, url) {
347
347
  try {
348
348
  localStorage.setItem(cacheKeyFor(env), JSON.stringify({ url, ts: Date.now() }));
349
+ return readCachedUrl(env) === url;
349
350
  } catch {
351
+ return false;
350
352
  }
351
353
  }
352
354
  function clearCachedUrl(env) {
@@ -360,58 +362,12 @@ function clearCachedUrl(env) {
360
362
  } catch {
361
363
  }
362
364
  }
363
- function isPayPalOrigin(origin) {
364
- try {
365
- const host = new URL(origin).hostname.toLowerCase();
366
- return host === "www.paypal.com" || host === "www.sandbox.paypal.com" || host.endsWith(".paypal.com") || host.endsWith(".paypalobjects.com");
367
- } catch {
368
- return false;
369
- }
370
- }
371
- function extractAuth(data) {
372
- if (!data) return {};
373
- if (typeof data === "object") {
374
- const obj = data;
375
- const authCode = obj.authCode ?? obj.auth_code ?? obj.authcode;
376
- const sharedId = obj.sharedId ?? obj.shared_id ?? obj.sharedid;
377
- if (authCode && sharedId) {
378
- return { authCode: String(authCode), sharedId: String(sharedId) };
379
- }
380
- for (const key of ["data", "payload", "message", "detail", "body"]) {
381
- if (obj[key] && typeof obj[key] === "object") {
382
- const inner = extractAuth(obj[key]);
383
- if (inner.authCode && inner.sharedId) return inner;
384
- }
385
- }
386
- return {};
387
- }
388
- if (typeof data === "string") {
389
- const s = data.trim();
390
- if (!s) return {};
391
- if (s.startsWith("{")) {
392
- try {
393
- return extractAuth(JSON.parse(s));
394
- } catch {
395
- }
396
- }
397
- if (/auth_?code/i.test(s) && /shared_?id/i.test(s)) {
398
- try {
399
- const sp = new URLSearchParams(s.replace(/^[?#]/, ""));
400
- const authCode = sp.get("authCode") || sp.get("auth_code") || void 0;
401
- const sharedId = sp.get("sharedId") || sp.get("shared_id") || void 0;
402
- if (authCode && sharedId) return { authCode, sharedId };
403
- } catch {
404
- }
405
- }
406
- }
407
- return {};
408
- }
409
365
  function PayPalConnectionPage() {
410
366
  const [env, setEnv] = useState("live");
411
367
  const [envReady, setEnvReady] = useState(false);
412
368
  const [connState, setConnState] = useState("loading");
369
+ const [partnerReady, setPartnerReady] = useState(false);
413
370
  const [error, setError] = useState(null);
414
- const [popupBlocked, setPopupBlocked] = useState(false);
415
371
  const [finalUrl, setFinalUrl] = useState("");
416
372
  const [showManual, setShowManual] = useState(false);
417
373
  const [clientId, setClientId] = useState("");
@@ -419,20 +375,11 @@ function PayPalConnectionPage() {
419
375
  const [statusInfo, setStatusInfo] = useState(null);
420
376
  const [onboardingInProgress, setOnboardingInProgress] = useState(false);
421
377
  const initLoaderRef = useRef(null);
422
- const paypalButtonRef = useRef(null);
423
378
  const errorLogRef = useRef(null);
424
379
  const runIdRef = useRef(0);
425
380
  const currentRunId = useRef(0);
426
- const finalUrlRef = useRef(finalUrl);
427
- finalUrlRef.current = finalUrl;
428
- const connStateRef = useRef(connState);
429
- connStateRef.current = connState;
430
- const inProgressRef = useRef(onboardingInProgress);
431
- inProgressRef.current = onboardingInProgress;
432
381
  const envRef = useRef(env);
433
382
  envRef.current = env;
434
- const popupRef = useRef(null);
435
- const popupPollRef = useRef(null);
436
383
  const completedRef = useRef(false);
437
384
  const ppBtnMeasureRef = useRef(null);
438
385
  const [ppBtnWidth, setPpBtnWidth] = useState(null);
@@ -443,131 +390,23 @@ function PayPalConnectionPage() {
443
390
  setConnState("error");
444
391
  setError(msg);
445
392
  }, []);
446
- const stopPopupPoll = useCallback(() => {
447
- if (popupPollRef.current) {
448
- clearInterval(popupPollRef.current);
449
- popupPollRef.current = null;
450
- }
451
- }, []);
452
- const openOnboardingPopup = useCallback(
453
- (url) => {
454
- const w = 450;
455
- const h = 600;
456
- const baseLeft = typeof window.screenX === "number" ? window.screenX : window.screenLeft || 0;
457
- const baseTop = typeof window.screenY === "number" ? window.screenY : window.screenTop || 0;
458
- const outerW = window.outerWidth || document.documentElement.clientWidth || 1024;
459
- const outerH = window.outerHeight || document.documentElement.clientHeight || 768;
460
- const left = Math.round(baseLeft + Math.max(0, (outerW - w) / 2));
461
- const top = Math.round(baseTop + Math.max(0, (outerH - h) / 2));
462
- const features = `popup=yes,width=${w},height=${h},left=${left},top=${top},scrollbars=yes,resizable=yes`;
463
- let popup = null;
464
- try {
465
- popup = window.open(url, POPUP_NAME, features);
466
- } catch {
467
- popup = null;
468
- }
469
- if (popup) {
470
- popupRef.current = popup;
471
- try {
472
- popup.focus();
473
- } catch {
393
+ const generateLinkAndReload = useCallback(
394
+ async (targetEnv, runId) => {
395
+ if (initLoaderRef.current) {
396
+ const loaderText = initLoaderRef.current.querySelector("#loader-text");
397
+ if (loaderText) {
398
+ loaderText.textContent = "Generating onboarding session...";
474
399
  }
475
- stopPopupPoll();
476
- popupPollRef.current = setInterval(() => {
477
- if (!popupRef.current || popupRef.current.closed) {
478
- stopPopupPoll();
479
- }
480
- }, 1e3);
481
400
  }
482
- return popup;
483
- },
484
- [stopPopupPoll]
485
- );
486
- const completeOnboarding = useCallback(
487
- async (authCode, sharedId) => {
488
- var _a, _b, _c, _d, _e, _f, _g, _h;
489
- if (!authCode || !sharedId) return;
490
- if (completedRef.current) return;
491
- completedRef.current = true;
492
401
  try {
493
- window.onbeforeunload = null;
494
- } catch {
495
- }
496
- const activeEnv = envRef.current === "sandbox" ? "sandbox" : "live";
497
- setOnboardingInProgress(true);
498
- setConnState("loading");
499
- setError(null);
500
- setPopupBlocked(false);
501
- try {
502
- const res = await fetch(ONBOARDING_COMPLETE_ENDPOINT, {
402
+ const res = await fetch(SERVICE_URL, {
503
403
  method: "POST",
504
404
  headers: { "content-type": "application/json" },
505
405
  credentials: "include",
506
- body: JSON.stringify({ authCode, sharedId, env: activeEnv })
406
+ body: JSON.stringify({ products: ["PPCP"], environment: targetEnv })
507
407
  });
508
- if (!res.ok) {
509
- const txt = await res.text().catch(() => "");
510
- throw new Error(txt || `Onboarding exchange failed (${res.status})`);
511
- }
512
- try {
513
- (_a = popupRef.current) == null ? void 0 : _a.close();
514
- } catch {
515
- }
516
- stopPopupPoll();
517
- try {
518
- 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;
519
- if (typeof close1 === "function") close1();
520
- } catch {
521
- }
522
- try {
523
- 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;
524
- if (typeof close2 === "function") close2();
525
- } catch {
526
- }
527
- clearCachedUrl(activeEnv);
528
- try {
529
- const statusRes = await fetch(
530
- `${STATUS_ENDPOINT}?environment=${activeEnv}`,
531
- { method: "GET", credentials: "include" }
532
- );
533
- const refreshedStatus = await statusRes.json().catch(() => ({}));
534
- setStatusInfo(refreshedStatus || null);
535
- } catch {
536
- }
537
- setConnState("connected");
538
- setShowManual(false);
539
- } catch (e) {
540
- completedRef.current = false;
541
- console.error(e);
542
- setConnState("error");
543
- setError((e == null ? void 0 : e.message) || "Exchange failed while saving credentials.");
544
- } finally {
545
- setOnboardingInProgress(false);
546
- }
547
- },
548
- [stopPopupPoll]
549
- );
550
- const fetchFreshLink = useCallback(
551
- (runId, targetEnv) => {
552
- if (initLoaderRef.current) {
553
- const loaderText = initLoaderRef.current.querySelector("#loader-text");
554
- if (loaderText)
555
- loaderText.textContent = "Generating onboarding session...";
556
- }
557
- fetch(SERVICE_URL, {
558
- method: "POST",
559
- headers: { "content-type": "application/json" },
560
- credentials: "include",
561
- body: JSON.stringify({
562
- products: ["PPCP"],
563
- // Generate the link for the explicitly selected environment so it can
564
- // never depend on a racing POST /environment having landed yet.
565
- environment: targetEnv
566
- })
567
- }).then((r) => {
568
- if (!r.ok) throw new Error(`Service returned ${r.status}`);
569
- return r.json();
570
- }).then((data) => {
408
+ if (!res.ok) throw new Error(`Service returned ${res.status}`);
409
+ const data = await res.json();
571
410
  if (runId !== currentRunId.current) return;
572
411
  const href = data == null ? void 0 : data.onboarding_url;
573
412
  if (!href) {
@@ -575,13 +414,17 @@ function PayPalConnectionPage() {
575
414
  return;
576
415
  }
577
416
  const url = href + (href.includes("?") ? "&" : "?") + "displayMode=minibrowser";
578
- writeCachedUrl(targetEnv, url);
417
+ const persisted = writeCachedUrl(targetEnv, url);
418
+ if (persisted) {
419
+ window.location.reload();
420
+ return;
421
+ }
579
422
  setFinalUrl(url);
580
423
  setConnState("ready");
581
- }).catch(() => {
424
+ } catch {
582
425
  if (runId !== currentRunId.current) return;
583
426
  showError("Unable to connect to service.");
584
- });
427
+ }
585
428
  },
586
429
  [showError]
587
430
  );
@@ -598,12 +441,60 @@ function PayPalConnectionPage() {
598
441
  alive = false;
599
442
  };
600
443
  }, []);
444
+ useEffect(() => {
445
+ if (!envReady) return;
446
+ currentRunId.current = ++runIdRef.current;
447
+ const runId = currentRunId.current;
448
+ let cancelled = false;
449
+ completedRef.current = false;
450
+ const run = async () => {
451
+ setConnState("loading");
452
+ setError(null);
453
+ setFinalUrl("");
454
+ try {
455
+ const r = await fetch(`${STATUS_ENDPOINT}?environment=${env}`, {
456
+ method: "GET",
457
+ credentials: "include"
458
+ });
459
+ const st = await r.json().catch(() => ({}));
460
+ if (cancelled || runId !== currentRunId.current) return;
461
+ setStatusInfo(st);
462
+ const isConnected = (st == null ? void 0 : st.status) === "connected" && (st == null ? void 0 : st.seller_client_id_present) === true;
463
+ if (isConnected) {
464
+ setConnState("connected");
465
+ setShowManual(false);
466
+ return;
467
+ }
468
+ } catch (e) {
469
+ console.error(e);
470
+ }
471
+ if (cancelled || runId !== currentRunId.current) return;
472
+ const cached = readCachedUrl(env);
473
+ if (cached) {
474
+ setFinalUrl(cached);
475
+ setConnState("ready");
476
+ return;
477
+ }
478
+ await generateLinkAndReload(env, runId);
479
+ };
480
+ run();
481
+ return () => {
482
+ cancelled = true;
483
+ };
484
+ }, [env, envReady, generateLinkAndReload]);
601
485
  useEffect(() => {
602
486
  var _a, _b, _c;
603
487
  if (connState !== "ready" || !finalUrl) return;
604
488
  const scriptUrl = PARTNER_JS_URLS[env];
605
489
  let cancelled = false;
606
490
  let pollTimer;
491
+ setPartnerReady(false);
492
+ const readyFallback = setTimeout(() => {
493
+ if (!cancelled) setPartnerReady(true);
494
+ }, 6e3);
495
+ const markReady = () => {
496
+ if (!cancelled) setPartnerReady(true);
497
+ };
607
498
  const initPartner = (attempt = 0) => {
608
499
  var _a2, _b2;
609
500
  if (cancelled) return;
@@ -615,6 +506,7 @@ function PayPalConnectionPage() {
615
506
  } catch (e) {
616
507
  console.error("[paypal] partner.js init failed:", e);
617
508
  }
509
+ markReady();
618
510
  return;
619
511
  }
620
512
  if (typeof (signup == null ? void 0 : signup.render) === "function") {
@@ -623,6 +515,7 @@ function PayPalConnectionPage() {
623
515
  } catch (e) {
624
516
  console.error("[paypal] partner.js render failed:", e);
625
517
  }
518
+ markReady();
626
519
  return;
627
520
  }
628
521
  if (attempt < 40) {
@@ -633,6 +526,7 @@ function PayPalConnectionPage() {
633
526
  document.dispatchEvent(new Event("DOMContentLoaded"));
634
527
  } catch {
635
528
  }
529
+ markReady();
636
530
  };
637
531
  const existingScript = document.getElementById("paypal-partner-js");
638
532
  if (existingScript) {
@@ -649,9 +543,11 @@ function PayPalConnectionPage() {
649
543
  ppScript.src = scriptUrl;
650
544
  ppScript.async = true;
651
545
  ppScript.onload = () => initPartner();
546
+ ppScript.onerror = () => markReady();
652
547
  document.body.appendChild(ppScript);
653
548
  return () => {
654
549
  cancelled = true;
550
+ clearTimeout(readyFallback);
655
551
  if (pollTimer) {
656
552
  clearTimeout(pollTimer);
657
553
  }
@@ -660,59 +556,61 @@ function PayPalConnectionPage() {
660
556
  }
661
557
  };
662
558
  }, [connState, finalUrl, env]);
663
- useEffect(() => {
664
- const onMessage = (ev) => {
665
- if (!isPayPalOrigin(ev.origin)) return;
666
- const { authCode, sharedId } = extractAuth(ev.data);
667
- if (authCode && sharedId) {
668
- completeOnboarding(authCode, sharedId);
559
+ const completeOnboarding = useCallback(async (authCode, sharedId) => {
560
+ var _a, _b, _c, _d, _e, _f, _g;
561
+ if (!authCode || !sharedId) return;
562
+ if (completedRef.current) return;
563
+ completedRef.current = true;
564
+ try {
565
+ window.onbeforeunload = null;
566
+ } catch {
567
+ }
568
+ const activeEnv = envRef.current === "sandbox" ? "sandbox" : "live";
569
+ setOnboardingInProgress(true);
570
+ setConnState("loading");
571
+ setError(null);
572
+ try {
573
+ const res = await fetch(ONBOARDING_COMPLETE_ENDPOINT, {
574
+ method: "POST",
575
+ headers: { "content-type": "application/json" },
576
+ credentials: "include",
577
+ body: JSON.stringify({ authCode, sharedId, env: activeEnv })
578
+ });
579
+ if (!res.ok) {
580
+ const txt = await res.text().catch(() => "");
581
+ throw new Error(txt || `Onboarding exchange failed (${res.status})`);
669
582
  }
670
- };
671
- window.addEventListener("message", onMessage);
672
- return () => window.removeEventListener("message", onMessage);
673
- }, [completeOnboarding]);
674
- useEffect(() => {
675
- if (!envReady) return;
676
- currentRunId.current = ++runIdRef.current;
677
- const runId = currentRunId.current;
678
- let cancelled = false;
679
- completedRef.current = false;
680
- const run = async () => {
681
- setConnState("loading");
682
- setError(null);
683
- setPopupBlocked(false);
684
- setFinalUrl("");
685
583
  try {
686
- const r = await fetch(`${STATUS_ENDPOINT}?environment=${env}`, {
584
+ 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;
585
+ if (typeof close1 === "function") close1();
586
+ } catch {
587
+ }
588
+ try {
589
+ 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;
590
+ if (typeof close2 === "function") close2();
591
+ } catch {
592
+ }
593
+ clearCachedUrl(activeEnv);
594
+ try {
595
+ const statusRes = await fetch(`${STATUS_ENDPOINT}?environment=${activeEnv}`, {
687
596
  method: "GET",
688
597
  credentials: "include"
689
598
  });
690
- const st = await r.json().catch(() => ({}));
691
- if (cancelled || runId !== currentRunId.current) return;
692
- setStatusInfo(st);
693
- const isConnected = (st == null ? void 0 : st.status) === "connected" && (st == null ? void 0 : st.seller_client_id_present) === true;
694
- if (isConnected) {
695
- setConnState("connected");
696
- setShowManual(false);
697
- return;
698
- }
699
- } catch (e) {
700
- console.error(e);
701
- }
702
- if (cancelled || runId !== currentRunId.current) return;
703
- const cached = readCachedUrl(env);
704
- if (cached) {
705
- setFinalUrl(cached);
706
- setConnState("ready");
707
- } else {
708
- fetchFreshLink(runId, env);
599
+ const refreshedStatus = await statusRes.json().catch(() => ({}));
600
+ setStatusInfo(refreshedStatus || null);
601
+ } catch {
709
602
  }
710
- };
711
- run();
712
- return () => {
713
- cancelled = true;
714
- };
715
- }, [env, envReady, fetchFreshLink]);
603
+ setConnState("connected");
604
+ setShowManual(false);
605
+ } catch (e) {
606
+ completedRef.current = false;
607
+ console.error(e);
608
+ setConnState("error");
609
+ setError((e == null ? void 0 : e.message) || "Exchange failed while saving credentials.");
610
+ } finally {
611
+ setOnboardingInProgress(false);
612
+ }
613
+ }, []);
716
614
  useLayoutEffect(() => {
717
615
  window.onboardingCallback = (authCode, sharedId) => {
718
616
  completeOnboarding(authCode, sharedId);
@@ -721,11 +619,6 @@ function PayPalConnectionPage() {
721
619
  window.onboardingCallback = void 0;
722
620
  };
723
621
  }, [completeOnboarding]);
724
- useEffect(() => {
725
- return () => {
726
- stopPopupPoll();
727
- };
728
- }, [stopPopupPoll]);
729
622
  useLayoutEffect(() => {
730
623
  const el = ppBtnMeasureRef.current;
731
624
  if (!el) return;
@@ -746,26 +639,6 @@ function PayPalConnectionPage() {
746
639
  else window.removeEventListener("resize", update);
747
640
  };
748
641
  }, [connState, env, finalUrl]);
749
- useEffect(() => {
750
- const onCaptureClick = (e) => {
751
- const btn = paypalButtonRef.current;
752
- if (!btn) return;
753
- const target = e.target;
754
- if (!target || !btn.contains(target)) return;
755
- e.preventDefault();
756
- if (connStateRef.current !== "ready" || !finalUrlRef.current || inProgressRef.current) {
757
- return;
758
- }
759
- completedRef.current = false;
760
- const popup = openOnboardingPopup(finalUrlRef.current);
761
- setPopupBlocked(!popup);
762
- };
763
- document.addEventListener("click", onCaptureClick, true);
764
- return () => document.removeEventListener("click", onCaptureClick, true);
765
- }, [openOnboardingPopup]);
766
- const handleConnectClick = (e) => {
767
- e.preventDefault();
768
- };
769
642
  const handleSaveManual = async () => {
770
643
  if (!canSaveManual || onboardingInProgress) return;
771
644
  setOnboardingInProgress(true);
@@ -826,8 +699,7 @@ function PayPalConnectionPage() {
826
699
  clearCachedUrl(env);
827
700
  completedRef.current = false;
828
701
  currentRunId.current = ++runIdRef.current;
829
- const runId = currentRunId.current;
830
- fetchFreshLink(runId, env);
702
+ await generateLinkAndReload(env, currentRunId.current);
831
703
  } catch (e) {
832
704
  console.error(e);
833
705
  setConnState("error");
@@ -838,10 +710,10 @@ function PayPalConnectionPage() {
838
710
  };
839
711
  const handleEnvChange = async (e) => {
840
712
  const next = e.target.value;
713
+ if (next === env || onboardingInProgress) return;
841
714
  completedRef.current = false;
842
- setPopupBlocked(false);
843
715
  clearCachedUrl();
844
- setEnv(next);
716
+ setConnState("loading");
845
717
  try {
846
718
  await fetch(ENVIRONMENT_ENDPOINT, {
847
719
  method: "POST",
@@ -851,6 +723,7 @@ function PayPalConnectionPage() {
851
723
  });
852
724
  } catch {
853
725
  }
726
+ setEnv(next);
854
727
  };
855
728
  return /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
856
729
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6", children: [
@@ -910,7 +783,6 @@ function PayPalConnectionPage() {
910
783
  "a",
911
784
  {
912
785
  ref: (node) => {
913
- paypalButtonRef.current = node;
914
786
  ppBtnMeasureRef.current = node;
915
787
  },
916
788
  id: "paypal-button",
@@ -918,17 +790,19 @@ function PayPalConnectionPage() {
918
790
  "data-paypal-button": "true",
919
791
  href: finalUrl || "#",
920
792
  "data-paypal-onboard-complete": "onboardingCallback",
921
- onClick: handleConnectClick,
793
+ "aria-disabled": onboardingInProgress || !partnerReady,
922
794
  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",
923
795
  style: {
924
- cursor: onboardingInProgress ? "not-allowed" : "pointer",
925
- opacity: onboardingInProgress ? 0.6 : 1,
926
- pointerEvents: onboardingInProgress ? "none" : "auto"
796
+ cursor: onboardingInProgress || !partnerReady ? "not-allowed" : "pointer",
797
+ opacity: onboardingInProgress || !partnerReady ? 0.6 : 1,
798
+ // Keep the anchor mounted (partner.js must find it to bind)
799
+ // but un-clickable until partner.js has taken over the
800
+ // click — so it can never open a tab via the native href.
801
+ pointerEvents: onboardingInProgress || !partnerReady ? "none" : "auto"
927
802
  },
928
- children: "Connect to PayPal"
803
+ children: partnerReady || onboardingInProgress ? "Connect to PayPal" : "Preparing PayPal…"
929
804
  }
930
805
  ),
931
- popupBlocked && /* @__PURE__ */ 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." }),
932
806
  /* @__PURE__ */ jsx(
933
807
  "div",
934
808
  {
@@ -1301,14 +1175,14 @@ const routeModule = {
1301
1175
  Component: PayPalSettingsIndexRoute,
1302
1176
  path: "/settings/paypal"
1303
1177
  },
1304
- {
1305
- Component: AdvancedCardPaymentsTab,
1306
- path: "/settings/paypal/advanced-card-payments"
1307
- },
1308
1178
  {
1309
1179
  Component: AdditionalSettingsTab,
1310
1180
  path: "/settings/paypal/additional-settings"
1311
1181
  },
1182
+ {
1183
+ Component: AdvancedCardPaymentsTab,
1184
+ path: "/settings/paypal/advanced-card-payments"
1185
+ },
1312
1186
  {
1313
1187
  Component: PayPalApplePayPage,
1314
1188
  path: "/settings/paypal/apple-pay"