@easypayment/medusa-paypal-ui 1.1.1 → 1.2.2
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/CHANGELOG.md +97 -61
- package/README.md +781 -759
- package/dist/index.cjs +143 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -2
- package/dist/index.d.ts +64 -2
- package/dist/index.mjs +133 -136
- package/dist/index.mjs.map +1 -1
- package/dist/order.cjs +14 -3
- package/dist/order.cjs.map +1 -1
- package/dist/order.d.cts +13 -4
- package/dist/order.d.ts +13 -4
- package/dist/order.mjs +13 -4
- package/dist/order.mjs.map +1 -1
- package/package.json +76 -75
- package/src/adapters/MedusaNextPayPalAdapter.tsx +34 -5
- package/src/client/http.ts +9 -1
- package/src/client/paypal.ts +27 -1
- package/src/components/PayPalAdvancedCard.tsx +24 -0
- package/src/components/PayPalPaymentSection.tsx +95 -202
- package/src/components/PayPalSmartButtons.tsx +24 -0
- package/src/constants.ts +22 -0
- package/src/hooks/usePayPalPaymentMethods.ts +15 -1
- package/src/index.ts +5 -0
- package/src/order.ts +7 -10
- package/src/utils/captured-state.ts +44 -0
package/dist/index.d.cts
CHANGED
|
@@ -45,8 +45,27 @@ type PayPalSettingsResponse = {
|
|
|
45
45
|
type HttpOptions = {
|
|
46
46
|
baseUrl: string;
|
|
47
47
|
publishableApiKey?: string;
|
|
48
|
+
/**
|
|
49
|
+
* `credentials` mode for every request. Defaults to "include" (the
|
|
50
|
+
* historical behavior, required when the backend sits behind cookie-based
|
|
51
|
+
* gateways like Cloudflare Access). Set to "omit" or "same-origin" when the
|
|
52
|
+
* backend answers with `Access-Control-Allow-Origin: *` — browsers reject
|
|
53
|
+
* credentialed requests against a wildcard CORS origin outright.
|
|
54
|
+
*/
|
|
55
|
+
credentials?: RequestCredentials;
|
|
48
56
|
};
|
|
49
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Fresh idempotency key per create-order ATTEMPT (i.e. per buyer click).
|
|
60
|
+
*
|
|
61
|
+
* The backend derives the PayPal-Request-Id from this header. It must be
|
|
62
|
+
* unique per attempt: PayPal replays the cached response for a reused request
|
|
63
|
+
* id, so a key stable across cart changes would hand back the ORIGINAL order —
|
|
64
|
+
* at the original total — after the buyer edits the cart, charging a stale
|
|
65
|
+
* amount. Reuse/dedup of an unchanged order is handled server-side by the
|
|
66
|
+
* stored-order staleness check, not by this key.
|
|
67
|
+
*/
|
|
68
|
+
declare function generateIdempotencyKey(): string;
|
|
50
69
|
declare function markPaymentComplete(baseUrl: string, cartId: string, publishableApiKey?: string): Promise<Record<string, unknown>>;
|
|
51
70
|
declare function createPayPalStoreApi(opts: HttpOptions): {
|
|
52
71
|
getConfig(cartId?: string, signal?: AbortSignal): Promise<PayPalConfig>;
|
|
@@ -113,9 +132,18 @@ type MedusaNextPayPalAdapterProps = {
|
|
|
113
132
|
};
|
|
114
133
|
declare function MedusaNextPayPalAdapter(props: MedusaNextPayPalAdapterProps): React.JSX.Element | null;
|
|
115
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Provider ids registered by `@easypayment/medusa-payment-paypal`.
|
|
137
|
+
*
|
|
138
|
+
* Single source of truth — `PayPalPaymentSection`, `MedusaNextPayPalAdapter`,
|
|
139
|
+
* and the server-safe `order` entrypoint all re-export from here so the ids
|
|
140
|
+
* can never drift between entrypoints.
|
|
141
|
+
*/
|
|
116
142
|
declare const PAYPAL_WALLET_PROVIDER_ID: "pp_paypal_paypal";
|
|
117
143
|
declare const PAYPAL_CARD_PROVIDER_ID: "pp_paypal_card_paypal_card";
|
|
118
|
-
|
|
144
|
+
/** True when the provider id belongs to this PayPal plugin. */
|
|
145
|
+
declare function isPayPalProviderId(providerId?: string | null): boolean;
|
|
146
|
+
|
|
119
147
|
type PayPalPaymentSectionProps = {
|
|
120
148
|
cartId: string;
|
|
121
149
|
selectedProviderId: string | null | undefined;
|
|
@@ -126,6 +154,14 @@ type PayPalPaymentSectionProps = {
|
|
|
126
154
|
onError?: (message: string) => void;
|
|
127
155
|
onPaid?: (result: unknown) => void;
|
|
128
156
|
};
|
|
157
|
+
/**
|
|
158
|
+
* Drop-in PayPal payment section for a Medusa checkout.
|
|
159
|
+
*
|
|
160
|
+
* A thin wrapper over {@link MedusaNextPayPalAdapter} (they previously carried
|
|
161
|
+
* two ~90%-identical render paths that had to be edited in lockstep): this
|
|
162
|
+
* component pins the default provider ids and adds the `sessionLoading` gate
|
|
163
|
+
* shown while the storefront is still creating the payment session.
|
|
164
|
+
*/
|
|
129
165
|
declare function PayPalPaymentSection({ cartId, selectedProviderId, baseUrl, publishableApiKey, sessionLoading, onSuccess, onError, onPaid, }: PayPalPaymentSectionProps): React.JSX.Element | null;
|
|
130
166
|
|
|
131
167
|
type Args = {
|
|
@@ -140,10 +176,36 @@ type Result = {
|
|
|
140
176
|
cardEnabled: boolean;
|
|
141
177
|
cardTitle: string;
|
|
142
178
|
loading: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Non-null when the config fetch failed and the returned flags are the
|
|
181
|
+
* optimistic defaults rather than the merchant's real settings. Storefronts
|
|
182
|
+
* can use this to hide or annotate the PayPal options instead of advertising
|
|
183
|
+
* methods that will fail once selected.
|
|
184
|
+
*/
|
|
185
|
+
error: string | null;
|
|
143
186
|
};
|
|
144
187
|
declare function usePayPalPaymentMethods({ baseUrl, publishableApiKey, cartId, enabled, }: Args): Result;
|
|
145
188
|
|
|
146
189
|
declare function showProcessingOverlay(): void;
|
|
147
190
|
declare function hideProcessingOverlay(): void;
|
|
148
191
|
|
|
149
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Per-cart "money was captured" flag, persisted in sessionStorage.
|
|
194
|
+
*
|
|
195
|
+
* The in-memory `capturedRef` protects a buyer within one page lifetime, but a
|
|
196
|
+
* reload/crash between the capture and the order finalization loses it — the
|
|
197
|
+
* buyer would then see fresh payment buttons for a cart whose money is already
|
|
198
|
+
* taken. Persisting the flag lets the components restore the finalize-only
|
|
199
|
+
* Retry path after a reload instead of offering a second payment.
|
|
200
|
+
*
|
|
201
|
+
* sessionStorage is deliberately chosen over localStorage: it is scoped to the
|
|
202
|
+
* tab/session, so an abandoned flag can't leak into a future visit after the
|
|
203
|
+
* cart id is reused, and it needs no expiry logic. Every access is guarded —
|
|
204
|
+
* private browsing modes and storage-blocking settings throw on access, and a
|
|
205
|
+
* storage failure must never break the payment flow.
|
|
206
|
+
*/
|
|
207
|
+
declare function markCartCaptured(cartId: string): void;
|
|
208
|
+
declare function wasCartCaptured(cartId: string): boolean;
|
|
209
|
+
declare function clearCartCaptured(cartId: string): void;
|
|
210
|
+
|
|
211
|
+
export { MedusaNextPayPalAdapter, type MedusaNextPayPalAdapterProps, PAYPAL_CARD_PROVIDER_ID, PAYPAL_WALLET_PROVIDER_ID, PayPalAdvancedCard, type PayPalConfig, PayPalCurrencyNotice, PayPalPaymentSection, type PayPalPaymentSectionProps, PayPalProvider, type PayPalSettingsResponse, PayPalSmartButtons, clearCartCaptured, createPayPalStoreApi, generateIdempotencyKey, hideProcessingOverlay, isPayPalProviderId, markCartCaptured, markPaymentComplete, showProcessingOverlay, usePayPalConfig, usePayPalPaymentMethods, wasCartCaptured };
|
package/dist/index.d.ts
CHANGED
|
@@ -45,8 +45,27 @@ type PayPalSettingsResponse = {
|
|
|
45
45
|
type HttpOptions = {
|
|
46
46
|
baseUrl: string;
|
|
47
47
|
publishableApiKey?: string;
|
|
48
|
+
/**
|
|
49
|
+
* `credentials` mode for every request. Defaults to "include" (the
|
|
50
|
+
* historical behavior, required when the backend sits behind cookie-based
|
|
51
|
+
* gateways like Cloudflare Access). Set to "omit" or "same-origin" when the
|
|
52
|
+
* backend answers with `Access-Control-Allow-Origin: *` — browsers reject
|
|
53
|
+
* credentialed requests against a wildcard CORS origin outright.
|
|
54
|
+
*/
|
|
55
|
+
credentials?: RequestCredentials;
|
|
48
56
|
};
|
|
49
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Fresh idempotency key per create-order ATTEMPT (i.e. per buyer click).
|
|
60
|
+
*
|
|
61
|
+
* The backend derives the PayPal-Request-Id from this header. It must be
|
|
62
|
+
* unique per attempt: PayPal replays the cached response for a reused request
|
|
63
|
+
* id, so a key stable across cart changes would hand back the ORIGINAL order —
|
|
64
|
+
* at the original total — after the buyer edits the cart, charging a stale
|
|
65
|
+
* amount. Reuse/dedup of an unchanged order is handled server-side by the
|
|
66
|
+
* stored-order staleness check, not by this key.
|
|
67
|
+
*/
|
|
68
|
+
declare function generateIdempotencyKey(): string;
|
|
50
69
|
declare function markPaymentComplete(baseUrl: string, cartId: string, publishableApiKey?: string): Promise<Record<string, unknown>>;
|
|
51
70
|
declare function createPayPalStoreApi(opts: HttpOptions): {
|
|
52
71
|
getConfig(cartId?: string, signal?: AbortSignal): Promise<PayPalConfig>;
|
|
@@ -113,9 +132,18 @@ type MedusaNextPayPalAdapterProps = {
|
|
|
113
132
|
};
|
|
114
133
|
declare function MedusaNextPayPalAdapter(props: MedusaNextPayPalAdapterProps): React.JSX.Element | null;
|
|
115
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Provider ids registered by `@easypayment/medusa-payment-paypal`.
|
|
137
|
+
*
|
|
138
|
+
* Single source of truth — `PayPalPaymentSection`, `MedusaNextPayPalAdapter`,
|
|
139
|
+
* and the server-safe `order` entrypoint all re-export from here so the ids
|
|
140
|
+
* can never drift between entrypoints.
|
|
141
|
+
*/
|
|
116
142
|
declare const PAYPAL_WALLET_PROVIDER_ID: "pp_paypal_paypal";
|
|
117
143
|
declare const PAYPAL_CARD_PROVIDER_ID: "pp_paypal_card_paypal_card";
|
|
118
|
-
|
|
144
|
+
/** True when the provider id belongs to this PayPal plugin. */
|
|
145
|
+
declare function isPayPalProviderId(providerId?: string | null): boolean;
|
|
146
|
+
|
|
119
147
|
type PayPalPaymentSectionProps = {
|
|
120
148
|
cartId: string;
|
|
121
149
|
selectedProviderId: string | null | undefined;
|
|
@@ -126,6 +154,14 @@ type PayPalPaymentSectionProps = {
|
|
|
126
154
|
onError?: (message: string) => void;
|
|
127
155
|
onPaid?: (result: unknown) => void;
|
|
128
156
|
};
|
|
157
|
+
/**
|
|
158
|
+
* Drop-in PayPal payment section for a Medusa checkout.
|
|
159
|
+
*
|
|
160
|
+
* A thin wrapper over {@link MedusaNextPayPalAdapter} (they previously carried
|
|
161
|
+
* two ~90%-identical render paths that had to be edited in lockstep): this
|
|
162
|
+
* component pins the default provider ids and adds the `sessionLoading` gate
|
|
163
|
+
* shown while the storefront is still creating the payment session.
|
|
164
|
+
*/
|
|
129
165
|
declare function PayPalPaymentSection({ cartId, selectedProviderId, baseUrl, publishableApiKey, sessionLoading, onSuccess, onError, onPaid, }: PayPalPaymentSectionProps): React.JSX.Element | null;
|
|
130
166
|
|
|
131
167
|
type Args = {
|
|
@@ -140,10 +176,36 @@ type Result = {
|
|
|
140
176
|
cardEnabled: boolean;
|
|
141
177
|
cardTitle: string;
|
|
142
178
|
loading: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Non-null when the config fetch failed and the returned flags are the
|
|
181
|
+
* optimistic defaults rather than the merchant's real settings. Storefronts
|
|
182
|
+
* can use this to hide or annotate the PayPal options instead of advertising
|
|
183
|
+
* methods that will fail once selected.
|
|
184
|
+
*/
|
|
185
|
+
error: string | null;
|
|
143
186
|
};
|
|
144
187
|
declare function usePayPalPaymentMethods({ baseUrl, publishableApiKey, cartId, enabled, }: Args): Result;
|
|
145
188
|
|
|
146
189
|
declare function showProcessingOverlay(): void;
|
|
147
190
|
declare function hideProcessingOverlay(): void;
|
|
148
191
|
|
|
149
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Per-cart "money was captured" flag, persisted in sessionStorage.
|
|
194
|
+
*
|
|
195
|
+
* The in-memory `capturedRef` protects a buyer within one page lifetime, but a
|
|
196
|
+
* reload/crash between the capture and the order finalization loses it — the
|
|
197
|
+
* buyer would then see fresh payment buttons for a cart whose money is already
|
|
198
|
+
* taken. Persisting the flag lets the components restore the finalize-only
|
|
199
|
+
* Retry path after a reload instead of offering a second payment.
|
|
200
|
+
*
|
|
201
|
+
* sessionStorage is deliberately chosen over localStorage: it is scoped to the
|
|
202
|
+
* tab/session, so an abandoned flag can't leak into a future visit after the
|
|
203
|
+
* cart id is reused, and it needs no expiry logic. Every access is guarded —
|
|
204
|
+
* private browsing modes and storage-blocking settings throw on access, and a
|
|
205
|
+
* storage failure must never break the payment flow.
|
|
206
|
+
*/
|
|
207
|
+
declare function markCartCaptured(cartId: string): void;
|
|
208
|
+
declare function wasCartCaptured(cartId: string): boolean;
|
|
209
|
+
declare function clearCartCaptured(cartId: string): void;
|
|
210
|
+
|
|
211
|
+
export { MedusaNextPayPalAdapter, type MedusaNextPayPalAdapterProps, PAYPAL_CARD_PROVIDER_ID, PAYPAL_WALLET_PROVIDER_ID, PayPalAdvancedCard, type PayPalConfig, PayPalCurrencyNotice, PayPalPaymentSection, type PayPalPaymentSectionProps, PayPalProvider, type PayPalSettingsResponse, PayPalSmartButtons, clearCartCaptured, createPayPalStoreApi, generateIdempotencyKey, hideProcessingOverlay, isPayPalProviderId, markCartCaptured, markPaymentComplete, showProcessingOverlay, usePayPalConfig, usePayPalPaymentMethods, wasCartCaptured };
|
package/dist/index.mjs
CHANGED
|
@@ -66,7 +66,7 @@ function createHttpClient(opts) {
|
|
|
66
66
|
res = await fetch(url, {
|
|
67
67
|
...init,
|
|
68
68
|
headers,
|
|
69
|
-
credentials: "include",
|
|
69
|
+
credentials: opts.credentials ?? "include",
|
|
70
70
|
signal: controller.signal
|
|
71
71
|
});
|
|
72
72
|
text = await res.text().catch(() => "");
|
|
@@ -135,6 +135,14 @@ function createHttpClient(opts) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// src/client/paypal.ts
|
|
138
|
+
function generateIdempotencyKey() {
|
|
139
|
+
try {
|
|
140
|
+
const c = globalThis.crypto;
|
|
141
|
+
if (c?.randomUUID) return c.randomUUID();
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
return `pp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
|
|
145
|
+
}
|
|
138
146
|
async function markPaymentComplete(baseUrl, cartId, publishableApiKey) {
|
|
139
147
|
const http = createHttpClient({ baseUrl, publishableApiKey });
|
|
140
148
|
return http.request(
|
|
@@ -160,7 +168,13 @@ function createPayPalStoreApi(opts) {
|
|
|
160
168
|
createOrder(cartId, isCardPayment = false) {
|
|
161
169
|
return http.request(`/store/paypal/create-order`, {
|
|
162
170
|
method: "POST",
|
|
163
|
-
headers: {
|
|
171
|
+
headers: {
|
|
172
|
+
"Content-Type": "application/json",
|
|
173
|
+
// One key per attempt — see generateIdempotencyKey. Deliberately NOT
|
|
174
|
+
// sent for capture-order: there the server's deterministic
|
|
175
|
+
// per-order-id fallback is the correct idempotency scope.
|
|
176
|
+
"Idempotency-Key": generateIdempotencyKey()
|
|
177
|
+
},
|
|
164
178
|
body: JSON.stringify({ cart_id: cartId, is_card_payment: isCardPayment })
|
|
165
179
|
});
|
|
166
180
|
},
|
|
@@ -293,6 +307,31 @@ function PayPalCurrencyNotice({ config }) {
|
|
|
293
307
|
import { useCallback, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
|
|
294
308
|
import { PayPalButtons, usePayPalScriptReducer } from "@paypal/react-paypal-js";
|
|
295
309
|
|
|
310
|
+
// src/utils/captured-state.ts
|
|
311
|
+
var KEY_PREFIX = "__pp_captured::";
|
|
312
|
+
function markCartCaptured(cartId) {
|
|
313
|
+
if (!cartId) return;
|
|
314
|
+
try {
|
|
315
|
+
window.sessionStorage.setItem(`${KEY_PREFIX}${cartId}`, String(Date.now()));
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function wasCartCaptured(cartId) {
|
|
320
|
+
if (!cartId) return false;
|
|
321
|
+
try {
|
|
322
|
+
return window.sessionStorage.getItem(`${KEY_PREFIX}${cartId}`) !== null;
|
|
323
|
+
} catch {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function clearCartCaptured(cartId) {
|
|
328
|
+
if (!cartId) return;
|
|
329
|
+
try {
|
|
330
|
+
window.sessionStorage.removeItem(`${KEY_PREFIX}${cartId}`);
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
296
335
|
// src/utils/next-errors.ts
|
|
297
336
|
function isNextRouterError(e) {
|
|
298
337
|
if (typeof e !== "object" || e === null || !("digest" in e)) return false;
|
|
@@ -416,6 +455,13 @@ function PayPalSmartButtons(props) {
|
|
|
416
455
|
window.addEventListener("pageshow", onPageShow);
|
|
417
456
|
return () => window.removeEventListener("pageshow", onPageShow);
|
|
418
457
|
}, []);
|
|
458
|
+
useEffect2(() => {
|
|
459
|
+
if (capturedRef.current || !wasCartCaptured(cartId)) return;
|
|
460
|
+
capturedRef.current = {};
|
|
461
|
+
setCompletionPending(true);
|
|
462
|
+
const msg = "Your payment was already received for this cart. Please use the button below to finish placing your order \u2014 do not pay again.";
|
|
463
|
+
setError(msg);
|
|
464
|
+
}, [cartId]);
|
|
419
465
|
useEffect2(() => {
|
|
420
466
|
if (!isResolved || buttonsReady) {
|
|
421
467
|
return;
|
|
@@ -431,6 +477,7 @@ function PayPalSmartButtons(props) {
|
|
|
431
477
|
setError(null);
|
|
432
478
|
try {
|
|
433
479
|
const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey);
|
|
480
|
+
clearCartCaptured(cartId);
|
|
434
481
|
await onPaid?.({ ...captured, ...completeResult });
|
|
435
482
|
setCompletionPending(false);
|
|
436
483
|
} catch (e) {
|
|
@@ -701,6 +748,7 @@ function PayPalSmartButtons(props) {
|
|
|
701
748
|
if (!orderId) throw new Error("PayPal order ID is missing from approval response");
|
|
702
749
|
const result = await api.captureOrder(cartId, orderId);
|
|
703
750
|
capturedRef.current = result || {};
|
|
751
|
+
markCartCaptured(cartId);
|
|
704
752
|
} catch (e) {
|
|
705
753
|
if (isNextRouterError(e)) return;
|
|
706
754
|
hideProcessingOverlay();
|
|
@@ -916,6 +964,14 @@ function PayPalAdvancedCard(props) {
|
|
|
916
964
|
window.addEventListener("pageshow", onPageShow);
|
|
917
965
|
return () => window.removeEventListener("pageshow", onPageShow);
|
|
918
966
|
}, []);
|
|
967
|
+
React3.useEffect(() => {
|
|
968
|
+
if (capturedRef.current || !wasCartCaptured(cartId)) return;
|
|
969
|
+
capturedRef.current = {};
|
|
970
|
+
setCompletionPending(true);
|
|
971
|
+
setError(
|
|
972
|
+
"Your payment was already received for this cart. Please use the button below to finish placing your order \u2014 do not pay again."
|
|
973
|
+
);
|
|
974
|
+
}, [cartId]);
|
|
919
975
|
const finalizeCapturedPayment = React3.useCallback(async () => {
|
|
920
976
|
const captured = capturedRef.current;
|
|
921
977
|
if (!captured) return;
|
|
@@ -924,6 +980,7 @@ function PayPalAdvancedCard(props) {
|
|
|
924
980
|
setError(null);
|
|
925
981
|
try {
|
|
926
982
|
const completeResult = await markPaymentComplete(baseUrl, cartId, publishableApiKey);
|
|
983
|
+
clearCartCaptured(cartId);
|
|
927
984
|
await onPaid?.({ ...captured, ...completeResult });
|
|
928
985
|
setCompletionPending(false);
|
|
929
986
|
} catch (e) {
|
|
@@ -1145,6 +1202,7 @@ function PayPalAdvancedCard(props) {
|
|
|
1145
1202
|
if (!orderId) throw new Error("PayPal order ID is missing from approval response");
|
|
1146
1203
|
const result = await api.captureOrder(cartId, orderId);
|
|
1147
1204
|
capturedRef.current = result || {};
|
|
1205
|
+
markCartCaptured(cartId);
|
|
1148
1206
|
} catch (e) {
|
|
1149
1207
|
if (isNextRouterError(e)) return;
|
|
1150
1208
|
resetSubmitState();
|
|
@@ -1333,9 +1391,23 @@ function PayPalAdvancedCard(props) {
|
|
|
1333
1391
|
|
|
1334
1392
|
// src/adapters/MedusaNextPayPalAdapter.tsx
|
|
1335
1393
|
import { useCallback as useCallback2 } from "react";
|
|
1394
|
+
|
|
1395
|
+
// src/constants.ts
|
|
1396
|
+
var PAYPAL_WALLET_PROVIDER_ID = "pp_paypal_paypal";
|
|
1397
|
+
var PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card";
|
|
1398
|
+
var PAYPAL_PROVIDER_IDS = [
|
|
1399
|
+
PAYPAL_WALLET_PROVIDER_ID,
|
|
1400
|
+
PAYPAL_CARD_PROVIDER_ID
|
|
1401
|
+
];
|
|
1402
|
+
function isPayPalProviderId(providerId) {
|
|
1403
|
+
if (!providerId) return false;
|
|
1404
|
+
return PAYPAL_PROVIDER_IDS.includes(
|
|
1405
|
+
providerId
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// src/adapters/MedusaNextPayPalAdapter.tsx
|
|
1336
1410
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1337
|
-
var DEFAULT_PAYPAL_PROVIDER_ID = "pp_paypal_paypal";
|
|
1338
|
-
var DEFAULT_PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card";
|
|
1339
1411
|
var SPIN_STYLE3 = `@keyframes _pp_spin { to { transform: rotate(360deg) } }`;
|
|
1340
1412
|
function PayPalLoadingCard() {
|
|
1341
1413
|
return /* @__PURE__ */ jsxs4(
|
|
@@ -1393,6 +1465,26 @@ function PayPalErrorCard({ message }) {
|
|
|
1393
1465
|
}
|
|
1394
1466
|
);
|
|
1395
1467
|
}
|
|
1468
|
+
function PayPalUnavailableCard({ label }) {
|
|
1469
|
+
return /* @__PURE__ */ jsxs4(
|
|
1470
|
+
"div",
|
|
1471
|
+
{
|
|
1472
|
+
role: "status",
|
|
1473
|
+
style: {
|
|
1474
|
+
padding: "12px 16px",
|
|
1475
|
+
background: "#f9fafb",
|
|
1476
|
+
border: "1px solid #e5e7eb",
|
|
1477
|
+
borderRadius: 10,
|
|
1478
|
+
fontSize: 13,
|
|
1479
|
+
color: "#6b7280"
|
|
1480
|
+
},
|
|
1481
|
+
children: [
|
|
1482
|
+
label,
|
|
1483
|
+
" is currently unavailable. Please choose a different payment method."
|
|
1484
|
+
]
|
|
1485
|
+
}
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1396
1488
|
function MedusaNextPayPalAdapter(props) {
|
|
1397
1489
|
const {
|
|
1398
1490
|
cartId,
|
|
@@ -1404,8 +1496,8 @@ function MedusaNextPayPalAdapter(props) {
|
|
|
1404
1496
|
onError,
|
|
1405
1497
|
onPaid
|
|
1406
1498
|
} = props;
|
|
1407
|
-
const paypalProviderId = providerIds?.paypal ||
|
|
1408
|
-
const paypalCardProviderId = providerIds?.paypalCard ||
|
|
1499
|
+
const paypalProviderId = providerIds?.paypal || PAYPAL_WALLET_PROVIDER_ID;
|
|
1500
|
+
const paypalCardProviderId = providerIds?.paypalCard || PAYPAL_CARD_PROVIDER_ID;
|
|
1409
1501
|
const shouldRender = selectedProviderId === paypalProviderId || selectedProviderId === paypalCardProviderId;
|
|
1410
1502
|
const { config, loading, error } = usePayPalConfig({
|
|
1411
1503
|
baseUrl,
|
|
@@ -1425,8 +1517,12 @@ function MedusaNextPayPalAdapter(props) {
|
|
|
1425
1517
|
if (error) return /* @__PURE__ */ jsx5(PayPalErrorCard, { message: error });
|
|
1426
1518
|
if (!config) return null;
|
|
1427
1519
|
const isCardProvider = selectedProviderId === paypalCardProviderId;
|
|
1428
|
-
if (config.paypal_enabled === false && !isCardProvider)
|
|
1429
|
-
|
|
1520
|
+
if (config.paypal_enabled === false && !isCardProvider) {
|
|
1521
|
+
return /* @__PURE__ */ jsx5(PayPalUnavailableCard, { label: config.paypal_title || "PayPal" });
|
|
1522
|
+
}
|
|
1523
|
+
if (isCardProvider && config.card_enabled === false) {
|
|
1524
|
+
return /* @__PURE__ */ jsx5(PayPalUnavailableCard, { label: config.card_title || "Card payment" });
|
|
1525
|
+
}
|
|
1430
1526
|
const disableFunding = Array.isArray(config.disable_buttons) ? config.disable_buttons.join(",") : void 0;
|
|
1431
1527
|
return /* @__PURE__ */ jsxs4("div", { style: { display: "grid", gap: 12 }, children: [
|
|
1432
1528
|
/* @__PURE__ */ jsx5(PayPalCurrencyNotice, { config }),
|
|
@@ -1463,18 +1559,7 @@ function MedusaNextPayPalAdapter(props) {
|
|
|
1463
1559
|
}
|
|
1464
1560
|
|
|
1465
1561
|
// src/components/PayPalPaymentSection.tsx
|
|
1466
|
-
import { useCallback as useCallback3 } from "react";
|
|
1467
1562
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1468
|
-
var PAYPAL_WALLET_PROVIDER_ID = "pp_paypal_paypal";
|
|
1469
|
-
var PAYPAL_CARD_PROVIDER_ID = "pp_paypal_card_paypal_card";
|
|
1470
|
-
var PAYPAL_PROVIDER_IDS = [
|
|
1471
|
-
PAYPAL_WALLET_PROVIDER_ID,
|
|
1472
|
-
PAYPAL_CARD_PROVIDER_ID
|
|
1473
|
-
];
|
|
1474
|
-
function isPayPalProviderId(id) {
|
|
1475
|
-
if (!id) return false;
|
|
1476
|
-
return PAYPAL_PROVIDER_IDS.includes(id);
|
|
1477
|
-
}
|
|
1478
1563
|
var SPIN_STYLE4 = `@keyframes _pp_section_spin { to { transform: rotate(360deg) } }`;
|
|
1479
1564
|
function SessionInitCard() {
|
|
1480
1565
|
return /* @__PURE__ */ jsxs5(
|
|
@@ -1513,62 +1598,6 @@ function SessionInitCard() {
|
|
|
1513
1598
|
}
|
|
1514
1599
|
);
|
|
1515
1600
|
}
|
|
1516
|
-
function ConfigLoadingCard() {
|
|
1517
|
-
return /* @__PURE__ */ jsxs5(
|
|
1518
|
-
"div",
|
|
1519
|
-
{
|
|
1520
|
-
role: "status",
|
|
1521
|
-
"aria-label": "Connecting to PayPal",
|
|
1522
|
-
style: {
|
|
1523
|
-
display: "flex",
|
|
1524
|
-
alignItems: "center",
|
|
1525
|
-
gap: 12,
|
|
1526
|
-
padding: "14px 16px",
|
|
1527
|
-
background: "#f9fafb",
|
|
1528
|
-
border: "1px solid #e5e7eb",
|
|
1529
|
-
borderRadius: 10
|
|
1530
|
-
},
|
|
1531
|
-
children: [
|
|
1532
|
-
/* @__PURE__ */ jsx6("style", { children: SPIN_STYLE4 }),
|
|
1533
|
-
/* @__PURE__ */ jsx6(
|
|
1534
|
-
"div",
|
|
1535
|
-
{
|
|
1536
|
-
style: {
|
|
1537
|
-
width: 22,
|
|
1538
|
-
height: 22,
|
|
1539
|
-
borderRadius: "50%",
|
|
1540
|
-
border: "2.5px solid #e5e7eb",
|
|
1541
|
-
borderTopColor: "#0070ba",
|
|
1542
|
-
animation: "_pp_section_spin .7s linear infinite",
|
|
1543
|
-
flexShrink: 0
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
),
|
|
1547
|
-
/* @__PURE__ */ jsxs5("div", { children: [
|
|
1548
|
-
/* @__PURE__ */ jsx6("div", { style: { fontSize: 13, fontWeight: 500, color: "#111827" }, children: "Connecting to PayPal\u2026" }),
|
|
1549
|
-
/* @__PURE__ */ jsx6("div", { style: { fontSize: 12, color: "#6b7280", marginTop: 2 }, children: "Setting up secure payment" })
|
|
1550
|
-
] })
|
|
1551
|
-
]
|
|
1552
|
-
}
|
|
1553
|
-
);
|
|
1554
|
-
}
|
|
1555
|
-
function ErrorCard({ message }) {
|
|
1556
|
-
return /* @__PURE__ */ jsx6(
|
|
1557
|
-
"div",
|
|
1558
|
-
{
|
|
1559
|
-
role: "alert",
|
|
1560
|
-
style: {
|
|
1561
|
-
padding: "12px 16px",
|
|
1562
|
-
background: "#fef2f2",
|
|
1563
|
-
border: "1px solid #fecaca",
|
|
1564
|
-
borderRadius: 10,
|
|
1565
|
-
fontSize: 13,
|
|
1566
|
-
color: "#b91c1c"
|
|
1567
|
-
},
|
|
1568
|
-
children: message
|
|
1569
|
-
}
|
|
1570
|
-
);
|
|
1571
|
-
}
|
|
1572
1601
|
function PayPalPaymentSection({
|
|
1573
1602
|
cartId,
|
|
1574
1603
|
selectedProviderId,
|
|
@@ -1579,63 +1608,20 @@ function PayPalPaymentSection({
|
|
|
1579
1608
|
onError,
|
|
1580
1609
|
onPaid
|
|
1581
1610
|
}) {
|
|
1582
|
-
|
|
1583
|
-
const { config, loading, error } = usePayPalConfig({
|
|
1584
|
-
baseUrl,
|
|
1585
|
-
publishableApiKey,
|
|
1586
|
-
cartId,
|
|
1587
|
-
enabled: shouldRender
|
|
1588
|
-
});
|
|
1589
|
-
const handlePaid = useCallback3(
|
|
1590
|
-
async (captureResult) => {
|
|
1591
|
-
onPaid?.(captureResult);
|
|
1592
|
-
await onSuccess?.(cartId);
|
|
1593
|
-
},
|
|
1594
|
-
[cartId, onPaid, onSuccess]
|
|
1595
|
-
);
|
|
1596
|
-
if (!shouldRender) return null;
|
|
1611
|
+
if (!isPayPalProviderId(selectedProviderId)) return null;
|
|
1597
1612
|
if (sessionLoading) return /* @__PURE__ */ jsx6(SessionInitCard, {});
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
PayPalProvider,
|
|
1611
|
-
{
|
|
1612
|
-
config,
|
|
1613
|
-
intent: config.intent === "authorize" ? "authorize" : "capture",
|
|
1614
|
-
disableFunding,
|
|
1615
|
-
children: isCardProvider ? /* @__PURE__ */ jsx6(
|
|
1616
|
-
PayPalAdvancedCard,
|
|
1617
|
-
{
|
|
1618
|
-
baseUrl,
|
|
1619
|
-
publishableApiKey,
|
|
1620
|
-
cartId,
|
|
1621
|
-
config,
|
|
1622
|
-
onPaid: handlePaid,
|
|
1623
|
-
onError
|
|
1624
|
-
}
|
|
1625
|
-
) : /* @__PURE__ */ jsx6(
|
|
1626
|
-
PayPalSmartButtons,
|
|
1627
|
-
{
|
|
1628
|
-
baseUrl,
|
|
1629
|
-
publishableApiKey,
|
|
1630
|
-
cartId,
|
|
1631
|
-
config,
|
|
1632
|
-
onPaid: handlePaid,
|
|
1633
|
-
onError
|
|
1634
|
-
}
|
|
1635
|
-
)
|
|
1636
|
-
}
|
|
1637
|
-
)
|
|
1638
|
-
] }, selectedProviderId);
|
|
1613
|
+
return /* @__PURE__ */ jsx6(
|
|
1614
|
+
MedusaNextPayPalAdapter,
|
|
1615
|
+
{
|
|
1616
|
+
cartId,
|
|
1617
|
+
selectedProviderId,
|
|
1618
|
+
baseUrl,
|
|
1619
|
+
publishableApiKey,
|
|
1620
|
+
onSuccess,
|
|
1621
|
+
onError,
|
|
1622
|
+
onPaid
|
|
1623
|
+
}
|
|
1624
|
+
);
|
|
1639
1625
|
}
|
|
1640
1626
|
|
|
1641
1627
|
// src/hooks/usePayPalPaymentMethods.ts
|
|
@@ -1658,7 +1644,8 @@ var DEFAULT_RESULT = {
|
|
|
1658
1644
|
paypalTitle: "PayPal",
|
|
1659
1645
|
cardEnabled: true,
|
|
1660
1646
|
cardTitle: "Credit or Debit Card",
|
|
1661
|
-
loading: false
|
|
1647
|
+
loading: false,
|
|
1648
|
+
error: null
|
|
1662
1649
|
};
|
|
1663
1650
|
function usePayPalPaymentMethods({
|
|
1664
1651
|
baseUrl,
|
|
@@ -1699,7 +1686,8 @@ function usePayPalPaymentMethods({
|
|
|
1699
1686
|
paypalTitle: typeof cfg.paypal_title === "string" && cfg.paypal_title ? cfg.paypal_title : "PayPal",
|
|
1700
1687
|
cardEnabled: cfg.card_enabled !== false,
|
|
1701
1688
|
cardTitle: typeof cfg.card_title === "string" && cfg.card_title ? cfg.card_title : "Credit or Debit Card",
|
|
1702
|
-
loading: false
|
|
1689
|
+
loading: false,
|
|
1690
|
+
error: null
|
|
1703
1691
|
};
|
|
1704
1692
|
cacheSet2(k, { result: next, at: Date.now() });
|
|
1705
1693
|
setResult(next);
|
|
@@ -1712,12 +1700,17 @@ function usePayPalPaymentMethods({
|
|
|
1712
1700
|
...DEFAULT_RESULT,
|
|
1713
1701
|
paypalEnabled: false,
|
|
1714
1702
|
cardEnabled: false,
|
|
1715
|
-
loading: false
|
|
1703
|
+
loading: false,
|
|
1704
|
+
error: msg || "PayPal is disabled"
|
|
1716
1705
|
};
|
|
1717
1706
|
setResult(disabled);
|
|
1718
1707
|
return;
|
|
1719
1708
|
}
|
|
1720
|
-
setResult({
|
|
1709
|
+
setResult({
|
|
1710
|
+
...DEFAULT_RESULT,
|
|
1711
|
+
loading: false,
|
|
1712
|
+
error: msg || "Failed to load PayPal payment methods"
|
|
1713
|
+
});
|
|
1721
1714
|
}
|
|
1722
1715
|
})();
|
|
1723
1716
|
return () => {
|
|
@@ -1736,12 +1729,16 @@ export {
|
|
|
1736
1729
|
PayPalPaymentSection,
|
|
1737
1730
|
PayPalProvider,
|
|
1738
1731
|
PayPalSmartButtons,
|
|
1732
|
+
clearCartCaptured,
|
|
1739
1733
|
createPayPalStoreApi,
|
|
1734
|
+
generateIdempotencyKey,
|
|
1740
1735
|
hideProcessingOverlay,
|
|
1741
1736
|
isPayPalProviderId,
|
|
1737
|
+
markCartCaptured,
|
|
1742
1738
|
markPaymentComplete,
|
|
1743
1739
|
showProcessingOverlay,
|
|
1744
1740
|
usePayPalConfig,
|
|
1745
|
-
usePayPalPaymentMethods
|
|
1741
|
+
usePayPalPaymentMethods,
|
|
1742
|
+
wasCartCaptured
|
|
1746
1743
|
};
|
|
1747
1744
|
//# sourceMappingURL=index.mjs.map
|