@tagadapay/plugin-sdk 3.1.24 → 3.1.25

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.
Files changed (39) hide show
  1. package/dist/external-tracker.js +243 -2871
  2. package/dist/external-tracker.min.js +2 -2
  3. package/dist/external-tracker.min.js.map +4 -4
  4. package/dist/react/hooks/useCheckout.js +7 -2
  5. package/dist/tagada-react-sdk-minimal.min.js +2 -2
  6. package/dist/tagada-react-sdk-minimal.min.js.map +3 -3
  7. package/dist/tagada-react-sdk.js +340 -173
  8. package/dist/tagada-react-sdk.min.js +2 -2
  9. package/dist/tagada-react-sdk.min.js.map +3 -3
  10. package/dist/tagada-sdk.js +776 -3327
  11. package/dist/tagada-sdk.min.js +2 -2
  12. package/dist/tagada-sdk.min.js.map +4 -4
  13. package/dist/v2/core/client.js +1 -0
  14. package/dist/v2/core/funnelClient.d.ts +8 -0
  15. package/dist/v2/core/funnelClient.js +1 -1
  16. package/dist/v2/core/resources/apiClient.d.ts +18 -14
  17. package/dist/v2/core/resources/apiClient.js +151 -109
  18. package/dist/v2/core/resources/checkout.d.ts +1 -1
  19. package/dist/v2/core/resources/index.d.ts +1 -1
  20. package/dist/v2/core/resources/index.js +1 -1
  21. package/dist/v2/core/resources/offers.js +4 -4
  22. package/dist/v2/core/resources/payments.d.ts +1 -0
  23. package/dist/v2/core/utils/currency.d.ts +3 -0
  24. package/dist/v2/core/utils/currency.js +40 -2
  25. package/dist/v2/core/utils/deviceInfo.d.ts +1 -0
  26. package/dist/v2/core/utils/deviceInfo.js +1 -0
  27. package/dist/v2/core/utils/previewMode.js +12 -0
  28. package/dist/v2/react/components/ApplePayButton.js +39 -16
  29. package/dist/v2/react/components/StripeExpressButton.js +1 -2
  30. package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.js +1 -0
  31. package/dist/v2/react/hooks/useApiQuery.d.ts +1 -1
  32. package/dist/v2/react/hooks/useApiQuery.js +1 -1
  33. package/dist/v2/react/hooks/useCheckoutQuery.js +6 -2
  34. package/dist/v2/react/hooks/usePreviewOffer.js +1 -1
  35. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +7 -0
  36. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +97 -9
  37. package/dist/v2/react/providers/TagadaProvider.js +1 -1
  38. package/dist/v2/standalone/payment-service.js +1 -0
  39. package/package.json +1 -1
@@ -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
- // Check Apple Pay availability (matches CMS pattern - useApplePayAvailable hook)
65
+ // Load SDK on demand, then check Apple Pay availability
42
66
  useEffect(() => {
43
- const addExpress = () => handleAddExpressId('apple_pay');
44
- try {
45
- // Apple Pay requires a secure context (HTTPS). On HTTP (like localhost),
46
- // calling canMakePayments() throws InvalidAccessError
47
- if (window?.ApplePaySession && ApplePaySession.canMakePayments()) {
48
- setIsApplePayAvailable(true);
49
- addExpress();
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
- else {
52
- setIsApplePayAvailable(false);
75
+ catch (error) {
76
+ console.warn('[ApplePay] Apple Pay not available:', error);
53
77
  }
54
- }
55
- catch (error) {
56
- // Likely "Trying to start an Apple Pay session from an insecure document"
57
- console.warn('[ApplePay] Apple Pay not available:', error);
58
- setIsApplePayAvailable(false);
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) => {
@@ -9,7 +9,7 @@ import { getGlobalApiClient } from '../hooks/useApiQuery';
9
9
  // Express method keys — drives processorGroup detection and ECE paymentMethods options.
10
10
  // 'klarna_express' is the CRM config key for Klarna via ECE, distinct from 'klarna' (redirect flow).
11
11
  // Backward compatible: existing configs with only apple_pay/google_pay continue to match.
12
- const EXPRESS_METHOD_KEYS = ['apple_pay', 'google_pay', 'paypal', 'link', 'klarna_express'];
12
+ const EXPRESS_METHOD_KEYS = ['apple_pay', 'google_pay', 'link', 'klarna_express'];
13
13
  // Inner component — must be a child of <Elements> to use useStripe() and useElements()
14
14
  function StripeExpressButtonInner({ checkout, processorId, enabledExpressMethods, onSuccess, onError, onCancel, }) {
15
15
  const stripe = useStripe();
@@ -141,7 +141,6 @@ function StripeExpressButtonInner({ checkout, processorId, enabledExpressMethods
141
141
  applePay: enabledExpressMethods.includes('apple_pay') ? 'always' : 'never',
142
142
  googlePay: enabledExpressMethods.includes('google_pay') ? 'always' : 'never',
143
143
  link: enabledExpressMethods.includes('link') ? 'auto' : 'never',
144
- paypal: enabledExpressMethods.includes('paypal') ? 'auto' : 'never',
145
144
  klarna: enabledExpressMethods.includes('klarna_express') ? 'auto' : 'never',
146
145
  },
147
146
  emailRequired: true,
@@ -62,6 +62,7 @@ export function useAirwallexRadarAction({ paymentsResource, startPolling, setErr
62
62
  console.log('Airwallex device fingerprint session created:', sessionId);
63
63
  // Save radar session to database
64
64
  await paymentsResource.saveRadarSession({
65
+ paymentId: payment.id,
65
66
  checkoutSessionId,
66
67
  orderId,
67
68
  airwallexRadarSessionId: sessionId,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * API Query Hook using TanStack Query + Axios
2
+ * API Query Hook using TanStack Query + fetch
3
3
  * Facade pattern for React SDK
4
4
  */
5
5
  import { UseMutationOptions, UseQueryOptions } from '@tanstack/react-query';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * API Query Hook using TanStack Query + Axios
2
+ * API Query Hook using TanStack Query + fetch
3
3
  * Facade pattern for React SDK
4
4
  */
5
5
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
@@ -80,10 +80,14 @@ export function useCheckoutQuery(options = {}) {
80
80
  }, [providedToken, internalToken]);
81
81
  // Use provided token or internal token
82
82
  const checkoutToken = providedToken || internalToken;
83
+ // Only send currency to the backend when explicitly set via URL param or persisted storage.
84
+ // When not explicit, the backend uses the session's own selectedPresentmentCurrency --
85
+ // the frontend then reads the currency from the response, never guesses.
86
+ const explicitCurrency = currency.isExplicit ? currency.code : undefined;
83
87
  // Main checkout query
84
88
  const { data: checkout, isLoading, error, isSuccess, refetch, } = useQuery({
85
- queryKey: ['checkout', checkoutToken, currency.code],
86
- queryFn: () => checkoutResource.getCheckout(checkoutToken, currency.code),
89
+ queryKey: ['checkout', checkoutToken, explicitCurrency],
90
+ queryFn: () => checkoutResource.getCheckout(checkoutToken, explicitCurrency),
87
91
  enabled: enabled && !!checkoutToken && isSessionInitialized,
88
92
  staleTime: 30000, // 30 seconds
89
93
  refetchOnWindowFocus: false,
@@ -12,7 +12,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
12
12
  import { OffersResource } from '../../core/resources/offers';
13
13
  import { getGlobalApiClient } from './useApiQuery';
14
14
  export function usePreviewOffer(options) {
15
- const { offerId, currency: requestedCurrency = 'USD', initialSelections = {} } = options;
15
+ const { offerId, currency: requestedCurrency = '', initialSelections = {} } = options;
16
16
  const queryParamsCurrency = typeof window !== 'undefined'
17
17
  ? new URLSearchParams(window.location.search).get('currency') || undefined
18
18
  : undefined;
@@ -1,9 +1,14 @@
1
1
  /**
2
2
  * Express Payment Methods Context Provider using v2 Architecture
3
3
  * Manages express payment methods (Apple Pay, Google Pay, PayPal, Klarna)
4
+ *
5
+ * Payment methods are resolved from:
6
+ * 1. paymentSetupConfig prop (from __TGD_STEP_CONFIG__ or useStepConfig)
7
+ * 2. API fallback (GET /api/v1/payment-methods) for legacy integrations
4
8
  */
5
9
  import React, { ReactNode } from 'react';
6
10
  import { TagadaError } from '../../core/errors';
11
+ import type { PaymentSetupConfig } from '../../core/funnelClient';
7
12
  import { CheckoutData } from '../../core/resources/checkout';
8
13
  import { Address, PaymentMethod } from '../../core/resources/expressPaymentMethods';
9
14
  type ExpressOrderLineItem = {
@@ -56,6 +61,8 @@ interface ExpressPaymentMethodsProviderProps {
56
61
  children: ReactNode;
57
62
  customerId?: string;
58
63
  checkout?: CheckoutData;
64
+ /** Step-level payment config from __TGD_STEP_CONFIG__ / useStepConfig(). */
65
+ paymentSetupConfig?: PaymentSetupConfig;
59
66
  }
60
67
  export declare const ExpressPaymentMethodsContext: React.Context<ExpressPaymentMethodsContextType | undefined>;
61
68
  export declare const ExpressPaymentMethodsProvider: React.FC<ExpressPaymentMethodsProviderProps>;
@@ -2,19 +2,66 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
2
  /**
3
3
  * Express Payment Methods Context Provider using v2 Architecture
4
4
  * Manages express payment methods (Apple Pay, Google Pay, PayPal, Klarna)
5
+ *
6
+ * Payment methods are resolved from:
7
+ * 1. paymentSetupConfig prop (from __TGD_STEP_CONFIG__ or useStepConfig)
8
+ * 2. API fallback (GET /api/v1/payment-methods) for legacy integrations
5
9
  */
6
10
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
7
- import { createContext, useCallback, useMemo, useState } from 'react';
11
+ import { createContext, useCallback, useEffect, useMemo, useState } from 'react';
8
12
  import { TagadaError, TagadaErrorCode } from '../../core/errors';
9
13
  import { ExpressPaymentMethodsResource, } from '../../core/resources/expressPaymentMethods';
10
14
  import { getGlobalApiClient } from '../hooks/useApiQuery';
11
15
  import { useShippingRatesQuery } from '../hooks/useShippingRatesQuery';
12
16
  export const ExpressPaymentMethodsContext = createContext(undefined);
13
- export const ExpressPaymentMethodsProvider = ({ children, customerId, checkout, }) => {
17
+ /**
18
+ * Synthesize PaymentMethod[] from paymentSetupConfig entries.
19
+ * Converts express_checkout:processorId entries into the shape the provider expects.
20
+ */
21
+ function paymentMethodsFromSetupConfig(config) {
22
+ const methods = [];
23
+ for (const [key, entry] of Object.entries(config)) {
24
+ if (!entry.enabled)
25
+ continue;
26
+ // express_checkout:processorId entries → synthesize a stripe_apm-style method
27
+ if (key.startsWith('express_checkout:') && entry.methods) {
28
+ methods.push({
29
+ id: key,
30
+ type: 'stripe_apm',
31
+ title: entry.label || 'Express Checkout',
32
+ iconUrl: entry.logoUrl || '',
33
+ default: false,
34
+ settings: {
35
+ publishableKey: entry.publishableKey,
36
+ processors: [{
37
+ processorId: entry.processorId,
38
+ methods: entry.methods,
39
+ }],
40
+ },
41
+ });
42
+ continue;
43
+ }
44
+ // Individual express methods (apple_pay, google_pay, etc.)
45
+ if (entry.express || entry.type === 'apple_pay' || entry.type === 'google_pay') {
46
+ methods.push({
47
+ id: key,
48
+ type: entry.type || entry.method || key,
49
+ title: entry.label || key,
50
+ iconUrl: entry.logoUrl || '',
51
+ default: false,
52
+ settings: { processorId: entry.processorId, publishableKey: entry.publishableKey },
53
+ });
54
+ }
55
+ }
56
+ return methods;
57
+ }
58
+ export const ExpressPaymentMethodsProvider = ({ children, customerId, checkout, paymentSetupConfig, }) => {
14
59
  const queryClient = useQueryClient();
15
60
  const [availableExpressPaymentMethodIds, setAvailableExpressPaymentMethodIds] = useState([]);
16
61
  const [error, setError] = useState(null);
17
62
  const checkoutSessionId = checkout?.checkoutSession?.id;
63
+ // If paymentSetupConfig is provided, derive payment methods from it (no API call needed)
64
+ const configDerivedMethods = useMemo(() => paymentSetupConfig ? paymentMethodsFromSetupConfig(paymentSetupConfig) : undefined, [paymentSetupConfig]);
18
65
  // Create express payment methods resource client
19
66
  const expressPaymentResource = useMemo(() => {
20
67
  try {
@@ -25,14 +72,15 @@ export const ExpressPaymentMethodsProvider = ({ children, customerId, checkout,
25
72
  (error instanceof Error ? error.message : 'Unknown error'));
26
73
  }
27
74
  }, []);
28
- // Fetch payment methods using TanStack Query
29
- const { data: paymentMethods, isLoading: isLoadingPaymentMethods } = useQuery({
75
+ // Only fetch from API when paymentSetupConfig is NOT provided
76
+ const { data: apiPaymentMethods, isLoading: isLoadingPaymentMethods } = useQuery({
30
77
  queryKey: ['payment-methods', checkoutSessionId],
31
78
  queryFn: () => expressPaymentResource.getPaymentMethods(checkoutSessionId),
32
- enabled: !!checkoutSessionId,
33
- staleTime: 60000, // 1 minute
79
+ enabled: !!checkoutSessionId && !configDerivedMethods,
80
+ staleTime: 60000,
34
81
  refetchOnWindowFocus: false,
35
82
  });
83
+ const paymentMethods = configDerivedMethods ?? apiPaymentMethods;
36
84
  // Use v2 shipping rates hook
37
85
  const { shippingRates, refetch: refetchRates } = useShippingRatesQuery({ checkout });
38
86
  // Get order summary from checkout data
@@ -161,8 +209,48 @@ export const ExpressPaymentMethodsProvider = ({ children, customerId, checkout,
161
209
  return;
162
210
  await updateEmailMutation.mutateAsync(input.data);
163
211
  }, [customerId, updateEmailMutation]);
164
- // Check if Apple Pay is available (window.ApplePaySession only exists on Apple devices)
165
- const isApplePayAvailable = typeof window !== 'undefined' && typeof window.ApplePaySession !== 'undefined';
212
+ // Check if Apple Pay is available load SDK on demand then re-check reactively
213
+ const [isApplePayAvailable, setIsApplePayAvailable] = useState(() => typeof window !== 'undefined' && typeof window.ApplePaySession !== 'undefined');
214
+ const [isApplePaySdkLoading, setIsApplePaySdkLoading] = useState(false);
215
+ const hasApplePayConfig = paymentMethods?.some((p) => p.type === 'apple_pay');
216
+ useEffect(() => {
217
+ if (isApplePayAvailable || !hasApplePayConfig)
218
+ return;
219
+ const APPLE_PAY_SDK_URL = 'https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js';
220
+ const onSdkReady = () => {
221
+ if (typeof window.ApplePaySession !== 'undefined') {
222
+ setIsApplePayAvailable(true);
223
+ }
224
+ setIsApplePaySdkLoading(false);
225
+ };
226
+ // Check if script is already in DOM (may still be loading)
227
+ const existing = document.querySelector(`script[src="${APPLE_PAY_SDK_URL}"]`);
228
+ if (existing) {
229
+ setIsApplePaySdkLoading(true);
230
+ // Script may already be loaded or still loading
231
+ if (typeof window.ApplePaySession !== 'undefined') {
232
+ onSdkReady();
233
+ }
234
+ else {
235
+ existing.addEventListener('load', onSdkReady);
236
+ // Fallback timeout in case we missed the load event
237
+ const timer = setTimeout(onSdkReady, 2000);
238
+ return () => {
239
+ existing.removeEventListener('load', onSdkReady);
240
+ clearTimeout(timer);
241
+ };
242
+ }
243
+ return;
244
+ }
245
+ setIsApplePaySdkLoading(true);
246
+ const script = document.createElement('script');
247
+ script.src = APPLE_PAY_SDK_URL;
248
+ script.crossOrigin = 'anonymous';
249
+ script.async = true;
250
+ script.onload = onSdkReady;
251
+ script.onerror = () => setIsApplePaySdkLoading(false);
252
+ document.head.appendChild(script);
253
+ }, [isApplePayAvailable, hasApplePayConfig]);
166
254
  // Identify specific payment method types
167
255
  const applePayPaymentMethod = useMemo(() => {
168
256
  return isApplePayAvailable ? paymentMethods?.find((p) => p.type === 'apple_pay') : undefined;
@@ -177,7 +265,7 @@ export const ExpressPaymentMethodsProvider = ({ children, customerId, checkout,
177
265
  return paymentMethods?.find((p) => (p.type === 'stripe_apm' || p.type === 'tagadapay_apm') &&
178
266
  p.settings?.processors?.some((g) => EXPRESS_METHOD_KEYS.some((key) => g?.methods?.[key]?.enabled === true)));
179
267
  }, [paymentMethods]);
180
- const loading = !paymentMethods || isLoadingPaymentMethods;
268
+ const loading = !paymentMethods || (!configDerivedMethods && isLoadingPaymentMethods) || isApplePaySdkLoading;
181
269
  const tagadaError = useMemo(() => error ? new TagadaError(error, { code: TagadaErrorCode.PAYMENT_FAILED }) : null, [error]);
182
270
  const contextValue = {
183
271
  paymentMethods,
@@ -315,7 +315,7 @@ export function TagadaProvider({ children, environment, customApiConfig, debugMo
315
315
  console.log('🚀 [TagadaProvider] Auto-redirecting to:', result.url);
316
316
  // Set pending redirect flag BEFORE navigation to prevent renders
317
317
  setPendingRedirect(true);
318
- window.location.href = result.url;
318
+ window.location.replace(result.url);
319
319
  }
320
320
  };
321
321
  const next = async (event, options) => {
@@ -533,6 +533,7 @@ export class PaymentService {
533
533
  existingScript.setAttribute('data-order-session-id', sessionId);
534
534
  }
535
535
  await this.paymentsResource.saveRadarSession({
536
+ paymentId: payment.id,
536
537
  checkoutSessionId,
537
538
  orderId,
538
539
  airwallexRadarSessionId: sessionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagadapay/plugin-sdk",
3
- "version": "3.1.24",
3
+ "version": "3.1.25",
4
4
  "description": "Modern React SDK for building Tagada Pay plugins",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",