@tagadapay/plugin-sdk 3.1.22 → 3.1.24

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 (76) hide show
  1. package/build-cdn.js +274 -6
  2. package/dist/external-tracker.js +236 -3906
  3. package/dist/external-tracker.min.js +2 -25
  4. package/dist/external-tracker.min.js.map +4 -4
  5. package/dist/react/config/payment.d.ts +14 -4
  6. package/dist/react/config/payment.js +47 -9
  7. package/dist/react/hooks/useCheckout.d.ts +3 -0
  8. package/dist/react/hooks/useCheckout.js +4 -1
  9. package/dist/react/hooks/usePluginConfig.js +9 -10
  10. package/dist/react/providers/TagadaProvider.js +1 -1
  11. package/dist/tagada-react-sdk-minimal.min.js +36 -0
  12. package/dist/tagada-react-sdk-minimal.min.js.map +7 -0
  13. package/dist/tagada-react-sdk.js +37821 -0
  14. package/dist/tagada-react-sdk.min.js +78 -0
  15. package/dist/tagada-react-sdk.min.js.map +7 -0
  16. package/dist/tagada-sdk.js +10309 -6331
  17. package/dist/tagada-sdk.min.js +4 -22
  18. package/dist/tagada-sdk.min.js.map +4 -4
  19. package/dist/v2/cdn-react-minimal.d.ts +23 -0
  20. package/dist/v2/cdn-react-minimal.js +26 -0
  21. package/dist/v2/core/client.js +1 -1
  22. package/dist/v2/core/config/environment.js +2 -1
  23. package/dist/v2/core/funnelClient.d.ts +98 -10
  24. package/dist/v2/core/funnelClient.js +121 -27
  25. package/dist/v2/core/index.d.ts +0 -1
  26. package/dist/v2/core/index.js +0 -2
  27. package/dist/v2/core/isoData.d.ts +4 -4
  28. package/dist/v2/core/isoData.js +7 -7
  29. package/dist/v2/core/pixelMapping.js +64 -26
  30. package/dist/v2/core/resources/checkout.d.ts +10 -0
  31. package/dist/v2/core/resources/checkout.js +6 -0
  32. package/dist/v2/core/resources/expressPaymentMethods.d.ts +1 -0
  33. package/dist/v2/core/resources/payments.d.ts +7 -2
  34. package/dist/v2/core/resources/payments.js +1 -0
  35. package/dist/v2/core/resources/postPurchases.d.ts +17 -0
  36. package/dist/v2/core/resources/postPurchases.js +20 -0
  37. package/dist/v2/core/utils/deviceInfo.d.ts +0 -10
  38. package/dist/v2/core/utils/deviceInfo.js +152 -76
  39. package/dist/v2/core/utils/order.d.ts +2 -0
  40. package/dist/v2/core/utils/pluginConfig.js +18 -22
  41. package/dist/v2/index.d.ts +4 -3
  42. package/dist/v2/index.js +4 -2
  43. package/dist/v2/react/components/FunnelScriptInjector.js +145 -77
  44. package/dist/v2/react/components/StripeExpressButton.d.ts +13 -0
  45. package/dist/v2/react/components/StripeExpressButton.js +171 -0
  46. package/dist/v2/react/components/WhopCheckout.js +7 -1
  47. package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.js +21 -3
  48. package/dist/v2/react/hooks/useApplePayCheckout.js +8 -8
  49. package/dist/v2/react/hooks/useCheckoutQuery.d.ts +10 -0
  50. package/dist/v2/react/hooks/useCheckoutQuery.js +21 -13
  51. package/dist/v2/react/hooks/useFunnel.d.ts +15 -4
  52. package/dist/v2/react/hooks/useFunnel.js +8 -4
  53. package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +2 -0
  54. package/dist/v2/react/hooks/useGoogleAutocomplete.js +29 -15
  55. package/dist/v2/react/hooks/useISOData.d.ts +2 -5
  56. package/dist/v2/react/hooks/useISOData.js +25 -26
  57. package/dist/v2/react/hooks/usePaymentPolling.d.ts +2 -2
  58. package/dist/v2/react/hooks/usePixelTracking.js +151 -70
  59. package/dist/v2/react/hooks/usePostPurchasesQuery.js +34 -2
  60. package/dist/v2/react/hooks/useRemappableParams.d.ts +2 -6
  61. package/dist/v2/react/hooks/useRemappableParams.js +23 -23
  62. package/dist/v2/react/hooks/useSetPaymentMethod.d.ts +16 -0
  63. package/dist/v2/react/hooks/useSetPaymentMethod.js +33 -0
  64. package/dist/v2/react/hooks/useStepConfig.d.ts +23 -6
  65. package/dist/v2/react/hooks/useStepConfig.js +14 -7
  66. package/dist/v2/react/hooks/useTranslation.js +23 -8
  67. package/dist/v2/react/index.d.ts +8 -1
  68. package/dist/v2/react/index.js +3 -0
  69. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +1 -0
  70. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +9 -1
  71. package/dist/v2/react/providers/TagadaProvider.js +4 -4
  72. package/dist/v2/standalone/index.d.ts +21 -3
  73. package/dist/v2/standalone/index.js +25 -3
  74. package/dist/v2/standalone/payment-service.d.ts +134 -0
  75. package/dist/v2/standalone/payment-service.js +928 -0
  76. package/package.json +4 -2
@@ -72,9 +72,43 @@ function convertValueToMajor(params) {
72
72
  }
73
73
  return params;
74
74
  }
75
+ /** Convert top-level monetary fields (shipping, tax, subtotal, total_discount) from minor → major units. */
76
+ function convertMonetaryFieldsToMajor(params) {
77
+ const currency = params.currency ? String(params.currency) : null;
78
+ if (!currency)
79
+ return params;
80
+ const result = { ...params };
81
+ for (const field of ['shipping', 'tax', 'subtotal', 'total_discount']) {
82
+ if (result[field] != null) {
83
+ result[field] = minorToMajor(Number(result[field]), currency);
84
+ }
85
+ }
86
+ return result;
87
+ }
88
+ /** Convert price fields inside contents[] items from minor → major units. */
89
+ function convertContentsPricesToMajor(params) {
90
+ if (!params.contents || !Array.isArray(params.contents))
91
+ return params;
92
+ const currency = params.currency ? String(params.currency) : null;
93
+ const result = { ...params };
94
+ result.contents = params.contents.map((item) => {
95
+ const itemCurrency = item.currency ? String(item.currency) : currency;
96
+ if (!itemCurrency)
97
+ return { ...item };
98
+ const converted = { ...item };
99
+ if (converted.price != null)
100
+ converted.price = minorToMajor(Number(converted.price), itemCurrency);
101
+ if (converted.original_price != null)
102
+ converted.original_price = minorToMajor(Number(converted.original_price), itemCurrency);
103
+ return converted;
104
+ });
105
+ return result;
106
+ }
75
107
  function baseTransform(params) {
76
108
  let p = ensureCurrencyUppercase(params);
77
109
  p = convertValueToMajor(p);
110
+ p = convertMonetaryFieldsToMajor(p);
111
+ p = convertContentsPricesToMajor(p);
78
112
  return p;
79
113
  }
80
114
  // ---------------------------------------------------------------------------
@@ -98,6 +132,14 @@ export function mapMetaEvent(eventName, parameters) {
98
132
  if (params.contents && Array.isArray(params.contents)) {
99
133
  params.content_ids = params.contents.map((i) => i.content_id).filter(Boolean);
100
134
  params.content_type = params.content_type ?? 'product';
135
+ // Reshape contents to Meta's expected format (prices already converted by baseTransform)
136
+ params.contents = params.contents.map((item) => ({
137
+ id: item.content_id,
138
+ quantity: item.quantity != null ? Number(item.quantity) : undefined,
139
+ item_price: item.price != null ? Number(item.price) : undefined,
140
+ content_name: item.content_name,
141
+ content_category: item.content_category,
142
+ }));
101
143
  }
102
144
  return { name, params };
103
145
  }
@@ -124,35 +166,29 @@ const TIKTOK_EVENT_MAP = {
124
166
  export function mapTikTokEvent(eventName, parameters) {
125
167
  const name = TIKTOK_EVENT_MAP[eventName] ?? eventName;
126
168
  const params = baseTransform(parameters);
127
- // Convert additional monetary fields from minor to major units
128
- const currency = params.currency ? String(params.currency) : null;
129
- if (currency) {
130
- for (const field of ['shipping', 'tax', 'subtotal', 'total_discount']) {
131
- if (params[field] != null) {
132
- params[field] = minorToMajor(Number(params[field]), currency);
133
- }
134
- }
135
- }
136
169
  if (params.contents && Array.isArray(params.contents)) {
137
- // Extract content_ids at top level for TikTok product matching
170
+ const ids = params.contents
171
+ .map((i) => i.content_id)
172
+ .filter(Boolean);
173
+ // TikTok requires content_id (singular) at the top level for VSA
174
+ if (!params.content_id && ids.length > 0) {
175
+ params.content_id = ids.length === 1 ? ids[0] : JSON.stringify(ids);
176
+ }
177
+ // Also keep content_ids for backwards compatibility
138
178
  if (!params.content_ids) {
139
- params.content_ids = params.contents
140
- .map((i) => i.content_id)
141
- .filter(Boolean);
179
+ params.content_ids = ids;
142
180
  }
143
- params.contents = params.contents.map((item) => {
144
- const itemCurrency = item.currency ? String(item.currency) : currency;
145
- return {
146
- content_id: item.content_id,
147
- content_name: item.content_name,
148
- content_type: item.content_type ?? 'product',
149
- content_category: item.content_category,
150
- price: item.price != null && itemCurrency ? minorToMajor(Number(item.price), itemCurrency) : item.price != null ? Number(item.price) : undefined,
151
- original_price: item.original_price != null && itemCurrency ? minorToMajor(Number(item.original_price), itemCurrency) : item.original_price != null ? Number(item.original_price) : undefined,
152
- quantity: item.quantity != null ? Number(item.quantity) : undefined,
153
- description: item.description,
154
- };
155
- });
181
+ // Reshape contents to TikTok's expected format (prices already converted by baseTransform)
182
+ params.contents = params.contents.map((item) => ({
183
+ content_id: item.content_id,
184
+ content_name: item.content_name,
185
+ content_type: item.content_type ?? 'product',
186
+ content_category: item.content_category,
187
+ price: item.price != null ? Number(item.price) : undefined,
188
+ original_price: item.original_price != null ? Number(item.original_price) : undefined,
189
+ quantity: item.quantity != null ? Number(item.quantity) : undefined,
190
+ description: item.description,
191
+ }));
156
192
  }
157
193
  // Set required fields for InitiateCheckout
158
194
  if (eventName === 'InitiateCheckout') {
@@ -224,6 +260,7 @@ export function mapPinterestEvent(eventName, parameters) {
224
260
  params.order_quantity = Number(params.order_quantity);
225
261
  }
226
262
  if (params.contents && Array.isArray(params.contents)) {
263
+ // Reshape to Pinterest line_items format (prices already converted by baseTransform)
227
264
  params.line_items = params.contents.map((item) => ({
228
265
  product_id: item.content_id,
229
266
  product_name: item.content_name,
@@ -259,6 +296,7 @@ export function mapGTMEvent(eventName, parameters) {
259
296
  params.num_items = Number(params.num_items);
260
297
  }
261
298
  if (params.contents && Array.isArray(params.contents)) {
299
+ // Reshape to GA4 items format (prices already converted by baseTransform)
262
300
  params.items = params.contents.map((item) => ({
263
301
  item_id: item.content_id,
264
302
  item_name: item.content_name,
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import type { OrderAddress } from '../types';
6
6
  import { ApiClient } from './apiClient';
7
+ export type PaymentMethodName = 'paypal' | 'card' | 'klarna' | 'zelle';
7
8
  export interface CheckoutLineItem {
8
9
  externalProductId?: string | null;
9
10
  externalVariantId?: string | null;
@@ -26,6 +27,7 @@ export interface CheckoutInitParams {
26
27
  currency?: string;
27
28
  locale?: string;
28
29
  };
30
+ enabledOrderBumpOfferIds?: string[];
29
31
  }
30
32
  export interface UpsellTrigger {
31
33
  id: string;
@@ -185,6 +187,7 @@ export interface CheckoutSummaryItem {
185
187
  intervalCount?: number;
186
188
  totalBillingCycles?: number;
187
189
  unitAmountAfterFirstCycle?: number;
190
+ properties?: Record<string, unknown>;
188
191
  orderLineItemProduct: {
189
192
  name: string;
190
193
  };
@@ -257,6 +260,7 @@ export interface CheckoutSessionPreviewItem {
257
260
  totalBillingCycles?: number;
258
261
  unitAmountAfterFirstCycle?: number;
259
262
  subscriptionSettings?: Record<string, unknown>;
263
+ properties?: Record<string, unknown>;
260
264
  orderLineItemProduct: {
261
265
  name: string | null;
262
266
  } | null;
@@ -519,4 +523,10 @@ export declare class CheckoutResource {
519
523
  success: boolean;
520
524
  preview: CheckoutSessionPreview;
521
525
  }>;
526
+ /**
527
+ * Set selected payment method for a checkout session
528
+ */
529
+ setPaymentMethod(checkoutSessionId: string, paymentMethodName: PaymentMethodName): Promise<{
530
+ success: boolean;
531
+ }>;
522
532
  }
@@ -213,4 +213,10 @@ export class CheckoutResource {
213
213
  async previewCheckoutSession(params) {
214
214
  return this.apiClient.post('/api/v1/checkout-sessions/preview', params);
215
215
  }
216
+ /**
217
+ * Set selected payment method for a checkout session
218
+ */
219
+ async setPaymentMethod(checkoutSessionId, paymentMethodName) {
220
+ return this.apiClient.post(`/api/v1/checkout-sessions/${checkoutSessionId}/payment-method`, { paymentMethodName });
221
+ }
216
222
  }
@@ -9,6 +9,7 @@ export interface PaymentMethod {
9
9
  title: string;
10
10
  iconUrl: string;
11
11
  default: boolean;
12
+ settings?: Record<string, unknown>;
12
13
  metadata?: Record<string, unknown>;
13
14
  }
14
15
  export interface Address {
@@ -8,9 +8,9 @@ export interface Payment {
8
8
  id: string;
9
9
  status: string;
10
10
  subStatus: string;
11
- requireAction: 'none' | 'redirect' | 'redirect_to_payment' | 'error' | 'radar';
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';
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';
14
14
  url?: string;
15
15
  processed: boolean;
16
16
  processorId?: string;
@@ -32,6 +32,10 @@ export interface Payment {
32
32
  orderId?: string;
33
33
  publishableKey?: string;
34
34
  };
35
+ stripeExpressCheckout?: {
36
+ clientSecret: string;
37
+ publishableKey?: string;
38
+ };
35
39
  provider?: string;
36
40
  isTest?: boolean;
37
41
  };
@@ -242,6 +246,7 @@ export declare class PaymentsResource {
242
246
  paymentFlowId?: string;
243
247
  processorId?: string;
244
248
  paymentMethod?: string;
249
+ isExpress?: boolean;
245
250
  }): Promise<PaymentResponse>;
246
251
  /**
247
252
  * Get card payment instruments for customer
@@ -147,6 +147,7 @@ export class PaymentsResource {
147
147
  ...(options.paymentFlowId && { paymentFlowId: options.paymentFlowId }),
148
148
  ...(options.processorId && { processorId: options.processorId }),
149
149
  ...(options.paymentMethod && { paymentMethod: options.paymentMethod }),
150
+ ...(options.isExpress && { isExpress: options.isExpress }),
150
151
  };
151
152
  console.log('[PaymentsResource] Request body being sent:', JSON.stringify(requestBody, null, 2));
152
153
  const response = await this.apiClient.post('/api/public/v1/checkout/pay-v2', requestBody);
@@ -110,6 +110,7 @@ export interface CheckoutSessionState {
110
110
  selectedVariants: Record<string, string>;
111
111
  loadingVariants: Record<string, boolean>;
112
112
  isUpdatingSummary: boolean;
113
+ isWhop?: boolean;
113
114
  }
114
115
  export declare class PostPurchasesResource {
115
116
  private apiClient;
@@ -172,6 +173,22 @@ export declare class PostPurchasesResource {
172
173
  * Get order summary for a checkout session
173
174
  */
174
175
  getOrderSummary(sessionId: string, includeVariantOptions?: boolean): Promise<any>;
176
+ /**
177
+ * Get payment methods for a checkout session
178
+ */
179
+ getPaymentMethods(checkoutSessionId: string): Promise<{
180
+ type: string;
181
+ }[]>;
182
+ /**
183
+ * Charge a Whop post-purchase using the stored payment method from the original order
184
+ */
185
+ chargeWhopPostPurchase(params: {
186
+ checkoutSessionId: string;
187
+ orderId: string;
188
+ storeId: string;
189
+ }): Promise<{
190
+ checkoutRequestId: string;
191
+ }>;
175
192
  /**
176
193
  * Update line items for a checkout session
177
194
  */
@@ -108,6 +108,26 @@ export class PostPurchasesResource {
108
108
  includeVariantOptions,
109
109
  });
110
110
  }
111
+ /**
112
+ * Get payment methods for a checkout session
113
+ */
114
+ async getPaymentMethods(checkoutSessionId) {
115
+ return this.apiClient.get(`/api/v1/payment-methods?checkoutSessionId=${encodeURIComponent(checkoutSessionId)}`);
116
+ }
117
+ /**
118
+ * Charge a Whop post-purchase using the stored payment method from the original order
119
+ */
120
+ async chargeWhopPostPurchase(params) {
121
+ return this.apiClient.post(`/api/v1/checkout-sessions/${params.checkoutSessionId}/whop-charge-payment`, {
122
+ checkoutSessionId: params.checkoutSessionId,
123
+ orderId: params.orderId,
124
+ storeId: params.storeId,
125
+ metadata: {
126
+ comingFromPostPurchase: true,
127
+ originalOrderId: params.orderId,
128
+ },
129
+ });
130
+ }
111
131
  /**
112
132
  * Update line items for a checkout session
113
133
  */
@@ -5,7 +5,6 @@ export interface DeviceInfo {
5
5
  major: string;
6
6
  name: string;
7
7
  version: string;
8
- type?: string;
9
8
  };
10
9
  os: {
11
10
  name: string;
@@ -36,17 +35,8 @@ export interface DeviceInfo {
36
35
  isAppleSilicon: boolean;
37
36
  };
38
37
  }
39
- /**
40
- * Get browser locale
41
- */
42
38
  export declare function getBrowserLocale(): string;
43
- /**
44
- * Collect all device information using UAParser
45
- */
46
39
  export declare function collectDeviceInfo(): DeviceInfo;
47
- /**
48
- * Get URL parameters for session initialization
49
- */
50
40
  export declare function getUrlParams(): {
51
41
  locale?: string;
52
42
  currency?: string;
@@ -1,44 +1,159 @@
1
- import { UAParser } from '@ua-parser-js/pro-enterprise';
2
- import { isBot, isChromeFamily, isStandalonePWA, isAppleSilicon, } from '@ua-parser-js/pro-enterprise/helpers';
3
- /**
4
- * Get screen resolution
5
- */
6
1
  function getScreenResolution() {
7
- return {
8
- width: window.screen.width,
9
- height: window.screen.height,
10
- };
2
+ return { width: window.screen.width, height: window.screen.height };
11
3
  }
12
- /**
13
- * Get timezone
14
- */
15
4
  function getTimeZone() {
16
5
  try {
17
6
  return Intl.DateTimeFormat().resolvedOptions().timeZone;
18
7
  }
19
- catch (error) {
20
- console.error('Failed to get timezone:', error);
8
+ catch {
21
9
  return 'UTC';
22
10
  }
23
11
  }
24
- /**
25
- * Get browser locale
26
- */
27
12
  export function getBrowserLocale() {
28
13
  try {
29
14
  return navigator.language || 'en-US';
30
15
  }
31
- catch (error) {
32
- console.error('Failed to get browser locale:', error);
16
+ catch {
33
17
  return 'en-US';
34
18
  }
35
19
  }
36
- /**
37
- * Collect all device information using UAParser
38
- */
20
+ // ---------------------------------------------------------------------------
21
+ // Lightweight UA parsing no third-party library needed
22
+ // ---------------------------------------------------------------------------
23
+ const UA = typeof navigator !== 'undefined' ? navigator.userAgent : '';
24
+ /** Extract first match's groups from a UA string */
25
+ function match(ua, ...patterns) {
26
+ for (const p of patterns) {
27
+ const m = ua.match(p);
28
+ if (m)
29
+ return m;
30
+ }
31
+ return null;
32
+ }
33
+ function parseBrowser(ua) {
34
+ // Order matters — more specific first
35
+ const tests = [
36
+ [/EdgA?\/([\d.]+)/i, 'Edge'],
37
+ [/OPR\/([\d.]+)/i, 'Opera'],
38
+ [/Brave\/([\d.]+)/i, 'Brave'],
39
+ [/Vivaldi\/([\d.]+)/i, 'Vivaldi'],
40
+ [/SamsungBrowser\/([\d.]+)/i, 'Samsung Internet'],
41
+ [/UCBrowser\/([\d.]+)/i, 'UC Browser'],
42
+ [/Firefox\/([\d.]+)/i, 'Firefox'],
43
+ [/CriOS\/([\d.]+)/i, 'Chrome'], // Chrome on iOS
44
+ [/FxiOS\/([\d.]+)/i, 'Firefox'], // Firefox on iOS
45
+ [/Chrome\/([\d.]+)/i, 'Chrome'],
46
+ [/Version\/([\d.]+).*Safari/i, 'Safari'],
47
+ [/Safari\/([\d.]+)/i, 'Safari'],
48
+ ];
49
+ for (const [re, name] of tests) {
50
+ const m = ua.match(re);
51
+ if (m) {
52
+ const version = m[1] || '';
53
+ return { name, version, major: version.split('.')[0] || '' };
54
+ }
55
+ }
56
+ return { name: '', version: '', major: '' };
57
+ }
58
+ function parseOS(ua) {
59
+ const m = match(ua, /Windows NT ([\d.]+)/i) ||
60
+ match(ua, /Mac OS X ([\d_.]+)/i) ||
61
+ match(ua, /Android ([\d.]+)/i) ||
62
+ match(ua, /iPhone OS ([\d_]+)/i) ||
63
+ match(ua, /iPad.*OS ([\d_]+)/i) ||
64
+ match(ua, /CrOS[\w ]*\/([\d.]+)/i) ||
65
+ match(ua, /Linux/i);
66
+ if (!m)
67
+ return { name: '', version: '' };
68
+ const raw = m[0];
69
+ const version = (m[1] || '').replace(/_/g, '.');
70
+ if (/Windows/i.test(raw)) {
71
+ const ntMap = {
72
+ '10.0': '10', '6.3': '8.1', '6.2': '8', '6.1': '7', '6.0': 'Vista', '5.1': 'XP',
73
+ };
74
+ return { name: 'Windows', version: ntMap[version] || version };
75
+ }
76
+ if (/Mac OS X/i.test(raw))
77
+ return { name: 'macOS', version };
78
+ if (/Android/i.test(raw))
79
+ return { name: 'Android', version };
80
+ if (/iPhone|iPad/i.test(raw))
81
+ return { name: 'iOS', version };
82
+ if (/CrOS/i.test(raw))
83
+ return { name: 'Chrome OS', version };
84
+ if (/Linux/i.test(raw))
85
+ return { name: 'Linux', version: '' };
86
+ return { name: '', version: '' };
87
+ }
88
+ function parseDevice(ua) {
89
+ if (/iPad/i.test(ua))
90
+ return { type: 'tablet', vendor: 'Apple', model: 'iPad' };
91
+ if (/iPhone/i.test(ua))
92
+ return { type: 'mobile', vendor: 'Apple', model: 'iPhone' };
93
+ if (/iPod/i.test(ua))
94
+ return { type: 'mobile', vendor: 'Apple', model: 'iPod' };
95
+ if (/Android/i.test(ua)) {
96
+ const m = ua.match(/Android[\s\d.]+;\s*([^)]+?)(?:\s+Build)/i);
97
+ const mobile = /Mobile/i.test(ua);
98
+ return { type: mobile ? 'mobile' : 'tablet', model: m?.[1]?.trim() };
99
+ }
100
+ return undefined;
101
+ }
102
+ function parseEngine(ua) {
103
+ const m = match(ua, /AppleWebKit\/([\d.]+)/i) ||
104
+ match(ua, /Gecko\/([\d.]+)/i) ||
105
+ match(ua, /Trident\/([\d.]+)/i) ||
106
+ match(ua, /Presto\/([\d.]+)/i);
107
+ if (!m)
108
+ return { name: '', version: '' };
109
+ const raw = m[0];
110
+ const version = m[1] || '';
111
+ if (/AppleWebKit/i.test(raw))
112
+ return { name: 'WebKit', version };
113
+ if (/Gecko/i.test(raw))
114
+ return { name: 'Gecko', version };
115
+ if (/Trident/i.test(raw))
116
+ return { name: 'Trident', version };
117
+ if (/Presto/i.test(raw))
118
+ return { name: 'Presto', version };
119
+ return { name: '', version: '' };
120
+ }
121
+ function parseCPU(ua) {
122
+ if (/x86_64|x64|amd64|Win64/i.test(ua))
123
+ return { architecture: 'amd64' };
124
+ if (/ia32|x86/i.test(ua))
125
+ return { architecture: 'ia32' };
126
+ if (/aarch64|arm64/i.test(ua))
127
+ return { architecture: 'arm64' };
128
+ if (/arm/i.test(ua))
129
+ return { architecture: 'arm' };
130
+ return { architecture: '' };
131
+ }
132
+ // ---------------------------------------------------------------------------
133
+ // Flags
134
+ // ---------------------------------------------------------------------------
135
+ function detectIsBot() {
136
+ if (navigator.webdriver)
137
+ return true;
138
+ return /bot|crawl|spider|slurp|Googlebot|bingbot|yandex|baidu|duckduck|facebookexternalhit|Twitterbot|linkedinbot|embedly|quora|pinterest|redditbot|Slackbot|Discordbot|WhatsApp|TelegramBot|Applebot/i.test(UA);
139
+ }
140
+ function detectIsChromeFamily(browserName) {
141
+ return /Chrome|Chromium|Edge|Brave|Opera|Vivaldi|Arc/i.test(browserName);
142
+ }
143
+ function detectIsStandalonePWA() {
144
+ return window.matchMedia('(display-mode: standalone)').matches
145
+ || navigator.standalone === true;
146
+ }
147
+ function detectIsAppleSilicon(osName) {
148
+ if (!/mac/i.test(osName))
149
+ return false;
150
+ return navigator.maxTouchPoints > 0;
151
+ }
152
+ // ---------------------------------------------------------------------------
153
+ // Main
154
+ // ---------------------------------------------------------------------------
39
155
  export function collectDeviceInfo() {
40
156
  if (typeof window === 'undefined') {
41
- // Server-side fallback
42
157
  return {
43
158
  userAgent: {
44
159
  name: '',
@@ -50,75 +165,36 @@ export function collectDeviceInfo() {
50
165
  },
51
166
  screenResolution: { width: 0, height: 0 },
52
167
  timeZone: 'UTC',
53
- flags: {
54
- isBot: false,
55
- isChromeFamily: false,
56
- isStandalonePWA: false,
57
- isAppleSilicon: false,
58
- },
168
+ flags: { isBot: false, isChromeFamily: false, isStandalonePWA: false, isAppleSilicon: false },
59
169
  };
60
170
  }
61
- const parser = new UAParser();
62
- const result = parser.getResult();
63
- // Enhanced detection using UAParser official helpers
171
+ const browser = parseBrowser(UA);
172
+ const os = parseOS(UA);
173
+ const device = parseDevice(UA);
174
+ const engine = parseEngine(UA);
175
+ const cpu = parseCPU(UA);
64
176
  let flags;
65
177
  try {
66
178
  flags = {
67
- isBot: isBot(result),
68
- isChromeFamily: isChromeFamily(result),
69
- isStandalonePWA: isStandalonePWA(),
70
- isAppleSilicon: isAppleSilicon(result),
179
+ isBot: detectIsBot(),
180
+ isChromeFamily: detectIsChromeFamily(browser.name),
181
+ isStandalonePWA: detectIsStandalonePWA(),
182
+ isAppleSilicon: detectIsAppleSilicon(os.name),
71
183
  };
72
184
  }
73
- catch (error) {
74
- console.error('Failed to compute device flags:', error);
75
- flags = {
76
- isBot: false,
77
- isChromeFamily: false,
78
- isStandalonePWA: false,
79
- isAppleSilicon: false,
80
- };
185
+ catch {
186
+ flags = { isBot: false, isChromeFamily: false, isStandalonePWA: false, isAppleSilicon: false };
81
187
  }
82
188
  return {
83
- userAgent: {
84
- name: result.ua,
85
- browser: {
86
- major: result.browser.major || '',
87
- name: result.browser.name || '',
88
- version: result.browser.version || '',
89
- type: result.browser.type,
90
- },
91
- os: {
92
- name: result.os.name || '',
93
- version: result.os.version || '',
94
- },
95
- device: result.device.model || result.device.type || result.device.vendor
96
- ? {
97
- model: result.device.model,
98
- type: result.device.type,
99
- vendor: result.device.vendor,
100
- }
101
- : undefined,
102
- engine: {
103
- name: result.engine.name || '',
104
- version: result.engine.version || '',
105
- },
106
- cpu: {
107
- architecture: result.cpu.architecture || '',
108
- },
109
- },
189
+ userAgent: { name: UA, browser, os, device, engine, cpu },
110
190
  screenResolution: getScreenResolution(),
111
191
  timeZone: getTimeZone(),
112
192
  flags,
113
193
  };
114
194
  }
115
- /**
116
- * Get URL parameters for session initialization
117
- */
118
195
  export function getUrlParams() {
119
- if (typeof window === 'undefined') {
196
+ if (typeof window === 'undefined')
120
197
  return {};
121
- }
122
198
  const params = new URLSearchParams(window.location.search);
123
199
  return {
124
200
  locale: params.get('locale') || undefined,
@@ -24,6 +24,7 @@ export interface OrderLineItem {
24
24
  totalBillingCycles: number;
25
25
  unitAmountAfterFirstCycle: number;
26
26
  subscriptionSettings?: Record<string, unknown> | null;
27
+ properties?: Record<string, unknown>;
27
28
  orderLineItemProduct: {
28
29
  id: string;
29
30
  name: string;
@@ -46,6 +47,7 @@ export interface OrderLineItem {
46
47
  }
47
48
  export interface Order {
48
49
  id: string;
50
+ orderNumber: number;
49
51
  storeId: string;
50
52
  accountId: string;
51
53
  createdAt: string;
@@ -159,6 +159,22 @@ const getMetaContent = (name) => {
159
159
  const metaTag = document.querySelector(`meta[name="${name}"]`);
160
160
  return metaTag?.getAttribute('content') || undefined;
161
161
  };
162
+ /**
163
+ * Read config from window global (preferred) or fall back to x-plugin-config meta tag
164
+ * (retrocompat with old deployments that still inject the meta tag).
165
+ */
166
+ const readWindowConfig = () => {
167
+ if (typeof window !== 'undefined' && window.__TAGADA_PLUGIN_CONFIG__) {
168
+ return window.__TAGADA_PLUGIN_CONFIG__;
169
+ }
170
+ try {
171
+ const encoded = getMetaContent('x-plugin-config');
172
+ if (encoded)
173
+ return JSON.parse(decodeURIComponent(encoded));
174
+ }
175
+ catch { /* ignore */ }
176
+ return {};
177
+ };
162
178
  /**
163
179
  * Synchronously get plugin config from meta tags
164
180
  * This is a lightweight sync alternative to loadPluginConfig
@@ -173,16 +189,7 @@ export function getPluginConfig() {
173
189
  const storeId = getMetaContent('x-plugin-store-id');
174
190
  const accountId = getMetaContent('x-plugin-account-id');
175
191
  const basePath = getMetaContent('x-plugin-base-path') || '/';
176
- let config = {};
177
- try {
178
- const encodedConfig = getMetaContent('x-plugin-config');
179
- if (encodedConfig) {
180
- config = JSON.parse(decodeURIComponent(encodedConfig));
181
- }
182
- }
183
- catch (error) {
184
- console.warn('[TagadaSDK] Failed to parse plugin config from meta tag:', error);
185
- }
192
+ const config = readWindowConfig();
186
193
  return { storeId, accountId, basePath, config };
187
194
  }
188
195
  /**
@@ -206,18 +213,7 @@ const loadProductionConfig = async () => {
206
213
  return null;
207
214
  }
208
215
  const basePath = getMetaContent('x-plugin-base-path') || '/';
209
- // Get deployment config from meta tags
210
- let config = {};
211
- try {
212
- const encodedConfig = getMetaContent('x-plugin-config');
213
- if (encodedConfig) {
214
- const decodedConfig = decodeURIComponent(encodedConfig);
215
- config = JSON.parse(decodedConfig);
216
- }
217
- }
218
- catch (error) {
219
- console.warn('⚠️ Failed to parse plugin config from meta tag:', error);
220
- }
216
+ const config = readWindowConfig();
221
217
  // Final validation
222
218
  if (!accountId) {
223
219
  console.warn('⚠️ Plugin config: Account ID not found in meta tags');