@tagadapay/plugin-sdk 3.1.24 → 4.0.0
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/README.md +1129 -1129
- package/build-cdn.js +499 -499
- package/dist/external-tracker.js +247 -2875
- package/dist/external-tracker.min.js +2 -2
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/config/payment.d.ts +2 -2
- package/dist/react/config/payment.js +5 -5
- package/dist/react/hooks/useCheckout.js +7 -2
- package/dist/react/hooks/usePayment.d.ts +7 -0
- package/dist/react/hooks/usePayment.js +1 -0
- package/dist/react/providers/TagadaProvider.js +5 -5
- package/dist/tagada-react-sdk-minimal.min.js +2 -2
- package/dist/tagada-react-sdk-minimal.min.js.map +4 -4
- package/dist/tagada-react-sdk.js +1680 -1172
- package/dist/tagada-react-sdk.min.js +2 -2
- package/dist/tagada-react-sdk.min.js.map +4 -4
- package/dist/tagada-sdk.js +1701 -3410
- package/dist/tagada-sdk.min.js +2 -2
- package/dist/tagada-sdk.min.js.map +4 -4
- package/dist/v2/core/client.js +1 -0
- package/dist/v2/core/config/environment.d.ts +3 -3
- package/dist/v2/core/config/environment.js +7 -7
- package/dist/v2/core/funnelClient.d.ts +10 -0
- package/dist/v2/core/funnelClient.js +1 -1
- package/dist/v2/core/resources/apiClient.d.ts +18 -14
- package/dist/v2/core/resources/apiClient.js +151 -109
- package/dist/v2/core/resources/checkout.d.ts +1 -1
- package/dist/v2/core/resources/funnel.d.ts +1 -1
- package/dist/v2/core/resources/geo.d.ts +50 -0
- package/dist/v2/core/resources/geo.js +35 -0
- package/dist/v2/core/resources/index.d.ts +1 -1
- package/dist/v2/core/resources/index.js +1 -1
- package/dist/v2/core/resources/offers.js +4 -4
- package/dist/v2/core/resources/payments.d.ts +20 -1
- package/dist/v2/core/resources/payments.js +8 -0
- package/dist/v2/core/utils/currency.d.ts +3 -0
- package/dist/v2/core/utils/currency.js +40 -2
- package/dist/v2/core/utils/deviceInfo.d.ts +1 -0
- package/dist/v2/core/utils/deviceInfo.js +1 -0
- package/dist/v2/core/utils/previewMode.js +12 -0
- package/dist/v2/core/utils/previewModeIndicator.js +101 -101
- package/dist/v2/react/components/ApplePayButton.js +39 -16
- package/dist/v2/react/components/FunnelScriptInjector.js +167 -19
- package/dist/v2/react/components/StripeExpressButton.d.ts +8 -0
- package/dist/v2/react/components/StripeExpressButton.js +23 -3
- package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.js +1 -0
- package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.d.ts +15 -0
- package/dist/v2/react/hooks/payment-actions/useNgeniusThreedsAction.js +166 -0
- package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.js +12 -0
- package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.js +1 -0
- package/dist/v2/react/hooks/useApiQuery.d.ts +1 -1
- package/dist/v2/react/hooks/useApiQuery.js +1 -1
- package/dist/v2/react/hooks/useCheckoutQuery.js +6 -2
- package/dist/v2/react/hooks/useISOData.js +25 -7
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +1 -1
- package/dist/v2/react/hooks/usePreviewOffer.js +1 -1
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +7 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +105 -9
- package/dist/v2/react/providers/TagadaProvider.js +6 -6
- package/dist/v2/standalone/apple-pay-service.d.ts +12 -0
- package/dist/v2/standalone/apple-pay-service.js +12 -0
- package/dist/v2/standalone/external-tracker.d.ts +1 -1
- package/dist/v2/standalone/google-pay-service.d.ts +9 -0
- package/dist/v2/standalone/google-pay-service.js +9 -0
- package/dist/v2/standalone/index.d.ts +8 -1
- package/dist/v2/standalone/index.js +7 -0
- package/dist/v2/standalone/payment-service.d.ts +18 -5
- package/dist/v2/standalone/payment-service.js +63 -9
- package/package.json +115 -114
|
@@ -10,7 +10,7 @@ export interface Payment {
|
|
|
10
10
|
subStatus: string;
|
|
11
11
|
requireAction: 'none' | 'redirect' | 'redirect_to_payment' | 'error' | 'radar' | 'stripe_express_checkout';
|
|
12
12
|
requireActionData?: {
|
|
13
|
-
type: 'redirect' | 'redirect_to_payment' | 'threeds_auth' | 'processor_auth' | 'error' | 'stripe_radar' | 'finix_radar' | 'radar' | 'kesspay_auth' | 'trustflow_auth' | 'mastercard_auth' | 'stripe_express_checkout';
|
|
13
|
+
type: 'redirect' | 'redirect_to_payment' | 'threeds_auth' | 'processor_auth' | 'error' | 'stripe_radar' | 'finix_radar' | 'radar' | 'kesspay_auth' | 'trustflow_auth' | 'mastercard_auth' | 'stripe_express_checkout' | 'ngenius_3ds';
|
|
14
14
|
url?: string;
|
|
15
15
|
processed: boolean;
|
|
16
16
|
processorId?: string;
|
|
@@ -112,6 +112,14 @@ export interface PaymentOptions {
|
|
|
112
112
|
* Payment method type (e.g., 'klarna', 'afterpay', 'paypal')
|
|
113
113
|
*/
|
|
114
114
|
paymentMethod?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Shipping rate selected by the customer at checkout. Forwarded to
|
|
117
|
+
* `processPaymentDirect` so the order is created with the right shipping
|
|
118
|
+
* even if the session's stored rate hasn't fully round-tripped or got
|
|
119
|
+
* cleared (race conditions). The backend treats this as authoritative
|
|
120
|
+
* when present, otherwise falls back to the session's stored rate.
|
|
121
|
+
*/
|
|
122
|
+
shippingRateId?: string;
|
|
115
123
|
/** @deprecated Use onPaymentSuccess instead - this will be removed in v3 */
|
|
116
124
|
onSuccess?: (response: PaymentResponse) => void;
|
|
117
125
|
/** @deprecated Use onPaymentFailed instead - this will be removed in v3 */
|
|
@@ -247,6 +255,7 @@ export declare class PaymentsResource {
|
|
|
247
255
|
processorId?: string;
|
|
248
256
|
paymentMethod?: string;
|
|
249
257
|
isExpress?: boolean;
|
|
258
|
+
shippingRateId?: string;
|
|
250
259
|
}): Promise<PaymentResponse>;
|
|
251
260
|
/**
|
|
252
261
|
* Get card payment instruments for customer
|
|
@@ -279,6 +288,7 @@ export declare class PaymentsResource {
|
|
|
279
288
|
error?: string;
|
|
280
289
|
}>;
|
|
281
290
|
saveRadarSession(data: {
|
|
291
|
+
paymentId?: string;
|
|
282
292
|
orderId?: string;
|
|
283
293
|
checkoutSessionId?: string;
|
|
284
294
|
finixRadarSessionId?: string;
|
|
@@ -303,4 +313,13 @@ export declare class PaymentsResource {
|
|
|
303
313
|
status: 'succeeded' | 'failed' | 'pending';
|
|
304
314
|
paymentIntentId: string;
|
|
305
315
|
}): Promise<Payment>;
|
|
316
|
+
/**
|
|
317
|
+
* Complete N-Genius payment after WebSDK 3DS flow finishes.
|
|
318
|
+
* Verifies state from N-Genius server-side and updates the payment record.
|
|
319
|
+
*/
|
|
320
|
+
ngeniusThreedsComplete(data: {
|
|
321
|
+
paymentId: string;
|
|
322
|
+
orderReference: string;
|
|
323
|
+
paymentReference: string;
|
|
324
|
+
}): Promise<Payment>;
|
|
306
325
|
}
|
|
@@ -148,6 +148,7 @@ export class PaymentsResource {
|
|
|
148
148
|
...(options.processorId && { processorId: options.processorId }),
|
|
149
149
|
...(options.paymentMethod && { paymentMethod: options.paymentMethod }),
|
|
150
150
|
...(options.isExpress && { isExpress: options.isExpress }),
|
|
151
|
+
...(options.shippingRateId && { shippingRateId: options.shippingRateId }),
|
|
151
152
|
};
|
|
152
153
|
console.log('[PaymentsResource] Request body being sent:', JSON.stringify(requestBody, null, 2));
|
|
153
154
|
const response = await this.apiClient.post('/api/public/v1/checkout/pay-v2', requestBody);
|
|
@@ -206,4 +207,11 @@ export class PaymentsResource {
|
|
|
206
207
|
async updateThreedsStatus(data) {
|
|
207
208
|
return this.apiClient.post('/api/v1/threeds/status', data);
|
|
208
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Complete N-Genius payment after WebSDK 3DS flow finishes.
|
|
212
|
+
* Verifies state from N-Genius server-side and updates the payment record.
|
|
213
|
+
*/
|
|
214
|
+
async ngeniusThreedsComplete(data) {
|
|
215
|
+
return this.apiClient.post('/api/v1/payments/ngenius/threeds-complete', data);
|
|
216
|
+
}
|
|
209
217
|
}
|
|
@@ -7,6 +7,8 @@ export interface Currency {
|
|
|
7
7
|
symbol: string;
|
|
8
8
|
name: string;
|
|
9
9
|
decimalPlaces: number;
|
|
10
|
+
/** True when the currency was explicitly set via URL param or persisted storage (not a SDK/store default) */
|
|
11
|
+
isExplicit: boolean;
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Format money amount from minor units (cents) to a formatted string
|
|
@@ -27,6 +29,7 @@ export declare class CurrencyUtils {
|
|
|
27
29
|
* Get currency from context or fallback to default
|
|
28
30
|
*/
|
|
29
31
|
static getCurrency(context: any, defaultCurrency?: string): Currency;
|
|
32
|
+
private static getCookieValue;
|
|
30
33
|
/**
|
|
31
34
|
* Get currency symbol
|
|
32
35
|
*/
|
|
@@ -47,9 +47,25 @@ export class CurrencyUtils {
|
|
|
47
47
|
* Get currency from context or fallback to default
|
|
48
48
|
*/
|
|
49
49
|
static getCurrency(context, defaultCurrency = 'USD') {
|
|
50
|
-
// Handle case where context.currency might be a Currency object or string
|
|
51
50
|
let currencyCode;
|
|
52
|
-
|
|
51
|
+
let isExplicit = false;
|
|
52
|
+
// 1. URL ?currency= param takes highest priority (set by CRM preview, storefront links, or currency selector)
|
|
53
|
+
const urlCurrency = typeof window !== 'undefined'
|
|
54
|
+
? new URLSearchParams(window.location.search).get('currency')
|
|
55
|
+
: null;
|
|
56
|
+
// 2. Persisted tgd_currency from storage/cookie (survives navigation between funnel steps)
|
|
57
|
+
const storedCurrency = typeof window !== 'undefined'
|
|
58
|
+
? (localStorage.getItem('tgd_currency') || CurrencyUtils.getCookieValue('tgd_currency'))
|
|
59
|
+
: null;
|
|
60
|
+
if (urlCurrency) {
|
|
61
|
+
currencyCode = urlCurrency.toUpperCase();
|
|
62
|
+
isExplicit = true;
|
|
63
|
+
}
|
|
64
|
+
else if (storedCurrency) {
|
|
65
|
+
currencyCode = storedCurrency.toUpperCase();
|
|
66
|
+
isExplicit = true;
|
|
67
|
+
}
|
|
68
|
+
else if (typeof context?.currency === 'string') {
|
|
53
69
|
currencyCode = context.currency;
|
|
54
70
|
}
|
|
55
71
|
else if (context?.currency?.code) {
|
|
@@ -61,13 +77,35 @@ export class CurrencyUtils {
|
|
|
61
77
|
else {
|
|
62
78
|
currencyCode = defaultCurrency;
|
|
63
79
|
}
|
|
80
|
+
// Validate against store's presentment currencies when available.
|
|
81
|
+
// This catches both explicit overrides (URL/storage) and fallback values that
|
|
82
|
+
// reference a currency the store doesn't support.
|
|
83
|
+
const presentment = context?.store?.presentmentCurrencies;
|
|
84
|
+
if (presentment?.length && !presentment.includes(currencyCode)) {
|
|
85
|
+
console.warn(`[CurrencyUtils] Currency "${currencyCode}" is not in store presentmentCurrencies [${presentment.join(', ')}]. Falling back to ${presentment[0]}.`);
|
|
86
|
+
currencyCode = presentment[0];
|
|
87
|
+
// Update persisted storage so subsequent renders don't keep requesting the unsupported currency
|
|
88
|
+
if (isExplicit && typeof window !== 'undefined') {
|
|
89
|
+
try {
|
|
90
|
+
localStorage.setItem('tgd_currency', currencyCode);
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
64
95
|
return {
|
|
65
96
|
code: currencyCode,
|
|
66
97
|
symbol: this.getCurrencySymbol(currencyCode),
|
|
67
98
|
name: this.getCurrencyName(currencyCode),
|
|
68
99
|
decimalPlaces: this.getDecimalPlaces(currencyCode),
|
|
100
|
+
isExplicit,
|
|
69
101
|
};
|
|
70
102
|
}
|
|
103
|
+
static getCookieValue(name) {
|
|
104
|
+
if (typeof document === 'undefined')
|
|
105
|
+
return null;
|
|
106
|
+
const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`));
|
|
107
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
108
|
+
}
|
|
71
109
|
/**
|
|
72
110
|
* Get currency symbol
|
|
73
111
|
*/
|
|
@@ -360,6 +360,18 @@ function persistSDKParamsFromURL() {
|
|
|
360
360
|
if (urlBaseUrl) {
|
|
361
361
|
setClientBaseUrl(urlBaseUrl);
|
|
362
362
|
}
|
|
363
|
+
// Persist currency if in URL (survives navigation between funnel steps)
|
|
364
|
+
const urlCurrency = urlParams.get('currency');
|
|
365
|
+
if (urlCurrency) {
|
|
366
|
+
setInStorage(STORAGE_KEYS.CURRENCY, urlCurrency.toUpperCase());
|
|
367
|
+
setInCookie(STORAGE_KEYS.CURRENCY, urlCurrency.toUpperCase(), 86400);
|
|
368
|
+
}
|
|
369
|
+
// Persist locale if in URL
|
|
370
|
+
const urlLocale = urlParams.get('locale');
|
|
371
|
+
if (urlLocale) {
|
|
372
|
+
setInStorage(STORAGE_KEYS.LOCALE, urlLocale);
|
|
373
|
+
setInCookie(STORAGE_KEYS.LOCALE, urlLocale, 86400);
|
|
374
|
+
}
|
|
363
375
|
}
|
|
364
376
|
/**
|
|
365
377
|
* Set funnel tracking mode in storage for persistence
|
|
@@ -212,143 +212,143 @@ export function injectPreviewModeIndicator() {
|
|
|
212
212
|
// Create container
|
|
213
213
|
const container = document.createElement('div');
|
|
214
214
|
container.id = 'tgd-preview-indicator';
|
|
215
|
-
container.style.cssText = `
|
|
216
|
-
position: fixed;
|
|
217
|
-
bottom: 16px;
|
|
218
|
-
right: 16px;
|
|
219
|
-
z-index: 999999;
|
|
220
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
215
|
+
container.style.cssText = `
|
|
216
|
+
position: fixed;
|
|
217
|
+
bottom: 16px;
|
|
218
|
+
right: 16px;
|
|
219
|
+
z-index: 999999;
|
|
220
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
221
221
|
`;
|
|
222
222
|
// Create badge
|
|
223
223
|
const badge = document.createElement('div');
|
|
224
|
-
badge.style.cssText = `
|
|
225
|
-
background: ${draftMode ? '#ff9500' : '#007aff'};
|
|
226
|
-
color: white;
|
|
227
|
-
padding: 8px 12px;
|
|
228
|
-
border-radius: 8px;
|
|
229
|
-
font-size: 13px;
|
|
230
|
-
font-weight: 600;
|
|
231
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
232
|
-
cursor: pointer;
|
|
233
|
-
transition: all 0.2s ease;
|
|
234
|
-
display: flex;
|
|
235
|
-
align-items: center;
|
|
236
|
-
gap: 6px;
|
|
224
|
+
badge.style.cssText = `
|
|
225
|
+
background: ${draftMode ? '#ff9500' : '#007aff'};
|
|
226
|
+
color: white;
|
|
227
|
+
padding: 8px 12px;
|
|
228
|
+
border-radius: 8px;
|
|
229
|
+
font-size: 13px;
|
|
230
|
+
font-weight: 600;
|
|
231
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
232
|
+
cursor: pointer;
|
|
233
|
+
transition: all 0.2s ease;
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: center;
|
|
236
|
+
gap: 6px;
|
|
237
237
|
`;
|
|
238
|
-
badge.innerHTML = `
|
|
239
|
-
<span style="font-size: 16px;">🔍</span>
|
|
240
|
-
<span>${draftMode ? 'Preview Mode' : 'Dev Mode'}</span>
|
|
238
|
+
badge.innerHTML = `
|
|
239
|
+
<span style="font-size: 16px;">🔍</span>
|
|
240
|
+
<span>${draftMode ? 'Preview Mode' : 'Dev Mode'}</span>
|
|
241
241
|
`;
|
|
242
242
|
// Create details popup (with padding-top to bridge gap with badge)
|
|
243
243
|
const details = document.createElement('div');
|
|
244
|
-
details.style.cssText = `
|
|
245
|
-
position: absolute;
|
|
246
|
-
bottom: calc(100% + 8px);
|
|
247
|
-
right: 0;
|
|
248
|
-
background: white;
|
|
249
|
-
border: 1px solid #e5e5e5;
|
|
250
|
-
border-radius: 8px;
|
|
251
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
252
|
-
padding: 12px;
|
|
253
|
-
min-width: 250px;
|
|
254
|
-
font-size: 12px;
|
|
255
|
-
line-height: 1.5;
|
|
256
|
-
display: none;
|
|
244
|
+
details.style.cssText = `
|
|
245
|
+
position: absolute;
|
|
246
|
+
bottom: calc(100% + 8px);
|
|
247
|
+
right: 0;
|
|
248
|
+
background: white;
|
|
249
|
+
border: 1px solid #e5e5e5;
|
|
250
|
+
border-radius: 8px;
|
|
251
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
252
|
+
padding: 12px;
|
|
253
|
+
min-width: 250px;
|
|
254
|
+
font-size: 12px;
|
|
255
|
+
line-height: 1.5;
|
|
256
|
+
display: none;
|
|
257
257
|
`;
|
|
258
258
|
details.style.paddingTop = '20px'; // Extra padding to bridge the gap
|
|
259
259
|
// Add invisible bridge between badge and popup to prevent flickering
|
|
260
260
|
const bridge = document.createElement('div');
|
|
261
|
-
bridge.style.cssText = `
|
|
262
|
-
position: absolute;
|
|
263
|
-
bottom: 100%;
|
|
264
|
-
left: 0;
|
|
265
|
-
right: 0;
|
|
266
|
-
height: 8px;
|
|
267
|
-
display: none;
|
|
261
|
+
bridge.style.cssText = `
|
|
262
|
+
position: absolute;
|
|
263
|
+
bottom: 100%;
|
|
264
|
+
left: 0;
|
|
265
|
+
right: 0;
|
|
266
|
+
height: 8px;
|
|
267
|
+
display: none;
|
|
268
268
|
`;
|
|
269
269
|
// Build details content
|
|
270
270
|
let detailsHTML = '<div style="margin-bottom: 8px; font-weight: 600; color: #1d1d1f;">Current Environment</div>';
|
|
271
271
|
detailsHTML += '<div style="display: flex; flex-direction: column; gap: 6px;">';
|
|
272
272
|
if (draftMode) {
|
|
273
|
-
detailsHTML += `
|
|
274
|
-
<div style="display: flex; justify-content: space-between; color: #86868b;">
|
|
275
|
-
<span>Draft Mode:</span>
|
|
276
|
-
<span style="color: #ff9500; font-weight: 600;">ON</span>
|
|
277
|
-
</div>
|
|
273
|
+
detailsHTML += `
|
|
274
|
+
<div style="display: flex; justify-content: space-between; color: #86868b;">
|
|
275
|
+
<span>Draft Mode:</span>
|
|
276
|
+
<span style="color: #ff9500; font-weight: 600;">ON</span>
|
|
277
|
+
</div>
|
|
278
278
|
`;
|
|
279
279
|
}
|
|
280
280
|
if (trackingDisabled) {
|
|
281
|
-
detailsHTML += `
|
|
282
|
-
<div style="display: flex; justify-content: space-between; color: #86868b;">
|
|
283
|
-
<span>Tracking:</span>
|
|
284
|
-
<span style="color: #ff3b30; font-weight: 600;">DISABLED</span>
|
|
285
|
-
</div>
|
|
281
|
+
detailsHTML += `
|
|
282
|
+
<div style="display: flex; justify-content: space-between; color: #86868b;">
|
|
283
|
+
<span>Tracking:</span>
|
|
284
|
+
<span style="color: #ff3b30; font-weight: 600;">DISABLED</span>
|
|
285
|
+
</div>
|
|
286
286
|
`;
|
|
287
287
|
}
|
|
288
288
|
if (params.funnelEnv) {
|
|
289
|
-
detailsHTML += `
|
|
290
|
-
<div style="display: flex; justify-content: space-between; color: #86868b;">
|
|
291
|
-
<span>Funnel Env:</span>
|
|
292
|
-
<span style="color: #1d1d1f; font-weight: 600; font-family: monospace; font-size: 11px;">
|
|
293
|
-
${params.funnelEnv}
|
|
294
|
-
</span>
|
|
295
|
-
</div>
|
|
289
|
+
detailsHTML += `
|
|
290
|
+
<div style="display: flex; justify-content: space-between; color: #86868b;">
|
|
291
|
+
<span>Funnel Env:</span>
|
|
292
|
+
<span style="color: #1d1d1f; font-weight: 600; font-family: monospace; font-size: 11px;">
|
|
293
|
+
${params.funnelEnv}
|
|
294
|
+
</span>
|
|
295
|
+
</div>
|
|
296
296
|
`;
|
|
297
297
|
}
|
|
298
298
|
if (params.tagadaClientEnv) {
|
|
299
|
-
detailsHTML += `
|
|
300
|
-
<div style="display: flex; justify-content: space-between; color: #86868b;">
|
|
301
|
-
<span>API Env:</span>
|
|
302
|
-
<span style="color: #1d1d1f; font-weight: 600; font-family: monospace; font-size: 11px;">
|
|
303
|
-
${params.tagadaClientEnv}
|
|
304
|
-
</span>
|
|
305
|
-
</div>
|
|
299
|
+
detailsHTML += `
|
|
300
|
+
<div style="display: flex; justify-content: space-between; color: #86868b;">
|
|
301
|
+
<span>API Env:</span>
|
|
302
|
+
<span style="color: #1d1d1f; font-weight: 600; font-family: monospace; font-size: 11px;">
|
|
303
|
+
${params.tagadaClientEnv}
|
|
304
|
+
</span>
|
|
305
|
+
</div>
|
|
306
306
|
`;
|
|
307
307
|
}
|
|
308
308
|
if (params.tagadaClientBaseUrl) {
|
|
309
|
-
detailsHTML += `
|
|
310
|
-
<div style="color: #86868b;">
|
|
311
|
-
<div style="margin-bottom: 4px;">API URL:</div>
|
|
312
|
-
<div style="color: #1d1d1f; font-weight: 600; font-family: monospace; font-size: 10px; word-break: break-all; background: #f5f5f7; padding: 4px 6px; border-radius: 4px;">
|
|
313
|
-
${params.tagadaClientBaseUrl}
|
|
314
|
-
</div>
|
|
315
|
-
</div>
|
|
309
|
+
detailsHTML += `
|
|
310
|
+
<div style="color: #86868b;">
|
|
311
|
+
<div style="margin-bottom: 4px;">API URL:</div>
|
|
312
|
+
<div style="color: #1d1d1f; font-weight: 600; font-family: monospace; font-size: 10px; word-break: break-all; background: #f5f5f7; padding: 4px 6px; border-radius: 4px;">
|
|
313
|
+
${params.tagadaClientBaseUrl}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
316
|
`;
|
|
317
317
|
}
|
|
318
318
|
if (params.funnelId) {
|
|
319
|
-
detailsHTML += `
|
|
320
|
-
<div style="color: #86868b; margin-top: 4px; padding-top: 8px; border-top: 1px solid #e5e5e5;">
|
|
321
|
-
<div style="margin-bottom: 4px;">Funnel ID:</div>
|
|
322
|
-
<div style="color: #1d1d1f; font-family: monospace; font-size: 10px; word-break: break-all; background: #f5f5f7; padding: 4px 6px; border-radius: 4px;">
|
|
323
|
-
${params.funnelId}
|
|
324
|
-
</div>
|
|
325
|
-
</div>
|
|
319
|
+
detailsHTML += `
|
|
320
|
+
<div style="color: #86868b; margin-top: 4px; padding-top: 8px; border-top: 1px solid #e5e5e5;">
|
|
321
|
+
<div style="margin-bottom: 4px;">Funnel ID:</div>
|
|
322
|
+
<div style="color: #1d1d1f; font-family: monospace; font-size: 10px; word-break: break-all; background: #f5f5f7; padding: 4px 6px; border-radius: 4px;">
|
|
323
|
+
${params.funnelId}
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
326
|
`;
|
|
327
327
|
}
|
|
328
328
|
detailsHTML += '</div>';
|
|
329
|
-
detailsHTML += `
|
|
330
|
-
<div style="margin-top: 12px; padding-top: 8px; border-top: 1px solid #e5e5e5; font-size: 11px; color: #86868b; text-align: center;">
|
|
331
|
-
Add <code style="background: #f5f5f7; padding: 2px 4px; border-radius: 3px;">?forceReset=true</code> to reset
|
|
332
|
-
</div>
|
|
329
|
+
detailsHTML += `
|
|
330
|
+
<div style="margin-top: 12px; padding-top: 8px; border-top: 1px solid #e5e5e5; font-size: 11px; color: #86868b; text-align: center;">
|
|
331
|
+
Add <code style="background: #f5f5f7; padding: 2px 4px; border-radius: 3px;">?forceReset=true</code> to reset
|
|
332
|
+
</div>
|
|
333
333
|
`;
|
|
334
334
|
// Add action button
|
|
335
|
-
detailsHTML += `
|
|
336
|
-
<div style="margin-top: 12px; padding-top: 8px; border-top: 1px solid #e5e5e5;">
|
|
337
|
-
<button id="tgd-leave-preview" style="
|
|
338
|
-
background: #ff3b30;
|
|
339
|
-
color: white;
|
|
340
|
-
border: none;
|
|
341
|
-
border-radius: 6px;
|
|
342
|
-
padding: 10px 12px;
|
|
343
|
-
font-size: 13px;
|
|
344
|
-
font-weight: 600;
|
|
345
|
-
cursor: pointer;
|
|
346
|
-
transition: opacity 0.2s;
|
|
347
|
-
width: 100%;
|
|
348
|
-
">
|
|
349
|
-
🚪 Leave Preview Mode
|
|
350
|
-
</button>
|
|
351
|
-
</div>
|
|
335
|
+
detailsHTML += `
|
|
336
|
+
<div style="margin-top: 12px; padding-top: 8px; border-top: 1px solid #e5e5e5;">
|
|
337
|
+
<button id="tgd-leave-preview" style="
|
|
338
|
+
background: #ff3b30;
|
|
339
|
+
color: white;
|
|
340
|
+
border: none;
|
|
341
|
+
border-radius: 6px;
|
|
342
|
+
padding: 10px 12px;
|
|
343
|
+
font-size: 13px;
|
|
344
|
+
font-weight: 600;
|
|
345
|
+
cursor: pointer;
|
|
346
|
+
transition: opacity 0.2s;
|
|
347
|
+
width: 100%;
|
|
348
|
+
">
|
|
349
|
+
🚪 Leave Preview Mode
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
352
352
|
`;
|
|
353
353
|
details.innerHTML = detailsHTML;
|
|
354
354
|
// Hover behavior - keep popup visible when hovering over badge, bridge, or popup
|
|
@@ -24,6 +24,30 @@ const applePayContactToAddress = (contact) => {
|
|
|
24
24
|
email: contact?.emailAddress || '',
|
|
25
25
|
};
|
|
26
26
|
};
|
|
27
|
+
const APPLE_PAY_SDK_URL = 'https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js';
|
|
28
|
+
let applePaySdkPromise = null;
|
|
29
|
+
function loadApplePaySdk() {
|
|
30
|
+
if (applePaySdkPromise)
|
|
31
|
+
return applePaySdkPromise;
|
|
32
|
+
// Check if the script is already in the DOM (e.g. from a previous load)
|
|
33
|
+
if (document.querySelector(`script[src="${APPLE_PAY_SDK_URL}"]`)) {
|
|
34
|
+
applePaySdkPromise = Promise.resolve();
|
|
35
|
+
return applePaySdkPromise;
|
|
36
|
+
}
|
|
37
|
+
applePaySdkPromise = new Promise((resolve, reject) => {
|
|
38
|
+
const script = document.createElement('script');
|
|
39
|
+
script.src = APPLE_PAY_SDK_URL;
|
|
40
|
+
script.crossOrigin = 'anonymous';
|
|
41
|
+
script.async = true;
|
|
42
|
+
script.onload = () => resolve();
|
|
43
|
+
script.onerror = () => {
|
|
44
|
+
applePaySdkPromise = null;
|
|
45
|
+
reject(new Error('[ApplePay] Failed to load Apple Pay SDK'));
|
|
46
|
+
};
|
|
47
|
+
document.head.appendChild(script);
|
|
48
|
+
});
|
|
49
|
+
return applePaySdkPromise;
|
|
50
|
+
}
|
|
27
51
|
export const ApplePayButton = ({ checkout, onSuccess, onError, onCancel }) => {
|
|
28
52
|
const { applePayPaymentMethod, reComputeOrderSummary, shippingMethods, lineItems, handleAddExpressId, updateCheckoutSessionValues, updateCustomerEmail, setError: setContextError, } = useExpressPaymentMethods();
|
|
29
53
|
const [processingPayment, setProcessingPayment] = useState(false);
|
|
@@ -38,25 +62,24 @@ export const ApplePayButton = ({ checkout, onSuccess, onError, onCancel }) => {
|
|
|
38
62
|
if (!applePayPaymentMethod) {
|
|
39
63
|
return null;
|
|
40
64
|
}
|
|
41
|
-
//
|
|
65
|
+
// Load SDK on demand, then check Apple Pay availability
|
|
42
66
|
useEffect(() => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
let cancelled = false;
|
|
68
|
+
const checkAvailability = () => {
|
|
69
|
+
try {
|
|
70
|
+
if (!cancelled && window?.ApplePaySession && ApplePaySession.canMakePayments()) {
|
|
71
|
+
setIsApplePayAvailable(true);
|
|
72
|
+
handleAddExpressId('apple_pay');
|
|
73
|
+
}
|
|
50
74
|
}
|
|
51
|
-
|
|
52
|
-
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.warn('[ApplePay] Apple Pay not available:', error);
|
|
53
77
|
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
78
|
+
};
|
|
79
|
+
loadApplePaySdk()
|
|
80
|
+
.then(checkAvailability)
|
|
81
|
+
.catch(() => { });
|
|
82
|
+
return () => { cancelled = true; };
|
|
60
83
|
}, [handleAddExpressId]);
|
|
61
84
|
// Helper to convert minor units to currency string
|
|
62
85
|
const minorUnitsToCurrencyString = useCallback((amountMinor, currency) => {
|