@tagadapay/plugin-sdk 2.1.3 → 2.2.1
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/dist/data/iso3166.d.ts +10 -8
- package/dist/data/iso3166.js +79 -16
- package/dist/react/components/DebugDrawer.js +16 -1
- package/dist/react/hooks/useISOData.d.ts +7 -6
- package/dist/react/hooks/useISOData.js +22 -61
- package/dist/react/hooks/usePostPurchases.d.ts +110 -2
- package/dist/react/hooks/usePostPurchases.js +226 -9
- package/package.json +1 -1
package/dist/data/iso3166.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export declare const AVAILABLE_LANGUAGES: readonly ["en", "ru", "de", "fr", "es", "zh", "hi", "pt", "ja", "ar", "it", "he"];
|
|
2
|
+
export type SupportedLanguage = typeof AVAILABLE_LANGUAGES[number];
|
|
1
3
|
export interface Country {
|
|
2
4
|
code: string;
|
|
3
5
|
name: string;
|
|
@@ -11,14 +13,14 @@ export interface State {
|
|
|
11
13
|
countryCode: string;
|
|
12
14
|
uniqueKey?: string;
|
|
13
15
|
}
|
|
14
|
-
export declare const getCountries: () => Country[];
|
|
15
|
-
export declare const getAllStates: () => State[];
|
|
16
|
-
export declare const getStatesForCountry: (countryCode: string) => State[];
|
|
17
|
-
export declare const findCountryByName: (countryName: string) => Country | null;
|
|
18
|
-
export declare const findRegionByCode: (regionCode: string) => State | null;
|
|
19
|
-
export declare const isValidCountryCode: (countryCode: string) => boolean;
|
|
20
|
-
export declare const isValidStateCode: (countryCode: string, stateCode: string) => boolean;
|
|
21
|
-
export declare const getCountryWithRegions: (countryCode: string) => {
|
|
16
|
+
export declare const getCountries: (language?: SupportedLanguage) => Country[];
|
|
17
|
+
export declare const getAllStates: (language?: SupportedLanguage) => State[];
|
|
18
|
+
export declare const getStatesForCountry: (countryCode: string, language?: SupportedLanguage) => State[];
|
|
19
|
+
export declare const findCountryByName: (countryName: string, language?: SupportedLanguage) => Country | null;
|
|
20
|
+
export declare const findRegionByCode: (regionCode: string, language?: SupportedLanguage) => State | null;
|
|
21
|
+
export declare const isValidCountryCode: (countryCode: string, language?: SupportedLanguage) => boolean;
|
|
22
|
+
export declare const isValidStateCode: (countryCode: string, stateCode: string, language?: SupportedLanguage) => boolean;
|
|
23
|
+
export declare const getCountryWithRegions: (countryCode: string, language?: SupportedLanguage) => {
|
|
22
24
|
country: {
|
|
23
25
|
code: any;
|
|
24
26
|
name: any;
|
package/dist/data/iso3166.js
CHANGED
|
@@ -1,11 +1,70 @@
|
|
|
1
|
-
// Import
|
|
1
|
+
// Import static language databases for better browser compatibility
|
|
2
|
+
// All supported languages from iso3166-2-db package
|
|
2
3
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
3
4
|
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
4
|
-
import
|
|
5
|
-
//
|
|
6
|
-
|
|
5
|
+
import enDatabase from 'iso3166-2-db/i18n/dispute/UN/en';
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
7
|
+
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
8
|
+
import ruDatabase from 'iso3166-2-db/i18n/dispute/UN/ru';
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
10
|
+
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
11
|
+
import deDatabase from 'iso3166-2-db/i18n/dispute/UN/de';
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
13
|
+
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
14
|
+
import frDatabase from 'iso3166-2-db/i18n/dispute/UN/fr';
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
16
|
+
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
17
|
+
import esDatabase from 'iso3166-2-db/i18n/dispute/UN/es';
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
19
|
+
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
20
|
+
import zhDatabase from 'iso3166-2-db/i18n/dispute/UN/zh';
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
22
|
+
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
23
|
+
import hiDatabase from 'iso3166-2-db/i18n/dispute/UN/hi';
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
25
|
+
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
26
|
+
import ptDatabase from 'iso3166-2-db/i18n/dispute/UN/pt';
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
28
|
+
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
29
|
+
import jaDatabase from 'iso3166-2-db/i18n/dispute/UN/ja';
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
31
|
+
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
32
|
+
import arDatabase from 'iso3166-2-db/i18n/dispute/UN/ar';
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
34
|
+
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
35
|
+
import itDatabase from 'iso3166-2-db/i18n/dispute/UN/it';
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
37
|
+
// @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
|
|
38
|
+
import heDatabase from 'iso3166-2-db/i18n/dispute/UN/he';
|
|
39
|
+
// All available languages from iso3166-2-db package
|
|
40
|
+
export const AVAILABLE_LANGUAGES = ['en', 'ru', 'de', 'fr', 'es', 'zh', 'hi', 'pt', 'ja', 'ar', 'it', 'he'];
|
|
41
|
+
// Static language database mapping for all supported languages
|
|
42
|
+
const languageDatabases = {
|
|
43
|
+
en: enDatabase, // English
|
|
44
|
+
ru: ruDatabase, // Russian
|
|
45
|
+
de: deDatabase, // German
|
|
46
|
+
fr: frDatabase, // French
|
|
47
|
+
es: esDatabase, // Spanish
|
|
48
|
+
zh: zhDatabase, // Chinese
|
|
49
|
+
hi: hiDatabase, // Hindi
|
|
50
|
+
pt: ptDatabase, // Portuguese
|
|
51
|
+
ja: jaDatabase, // Japanese
|
|
52
|
+
ar: arDatabase, // Arabic
|
|
53
|
+
it: itDatabase, // Italian
|
|
54
|
+
he: heDatabase, // Hebrew
|
|
55
|
+
};
|
|
56
|
+
// Function to get language-specific database
|
|
57
|
+
function getWorldDatabase(language = 'en') {
|
|
58
|
+
const database = languageDatabases[language];
|
|
59
|
+
if (!database) {
|
|
60
|
+
console.warn(`Language ${language} not available, falling back to English`);
|
|
61
|
+
return languageDatabases.en;
|
|
62
|
+
}
|
|
63
|
+
return database;
|
|
64
|
+
}
|
|
7
65
|
// Transform the ISO3166 data into our expected format
|
|
8
|
-
export const getCountries = () => {
|
|
66
|
+
export const getCountries = (language = 'en') => {
|
|
67
|
+
const worldDatabase = getWorldDatabase(language);
|
|
9
68
|
const countries = [];
|
|
10
69
|
Object.keys(worldDatabase).forEach((countryCode) => {
|
|
11
70
|
const countryData = worldDatabase[countryCode];
|
|
@@ -21,7 +80,8 @@ export const getCountries = () => {
|
|
|
21
80
|
return countries.sort((a, b) => a.name.localeCompare(b.name));
|
|
22
81
|
};
|
|
23
82
|
// Get all states/regions for all countries
|
|
24
|
-
export const getAllStates = () => {
|
|
83
|
+
export const getAllStates = (language = 'en') => {
|
|
84
|
+
const worldDatabase = getWorldDatabase(language);
|
|
25
85
|
const states = [];
|
|
26
86
|
Object.keys(worldDatabase).forEach((countryCode) => {
|
|
27
87
|
const countryData = worldDatabase[countryCode];
|
|
@@ -40,7 +100,8 @@ export const getAllStates = () => {
|
|
|
40
100
|
return states.sort((a, b) => a.name.localeCompare(b.name));
|
|
41
101
|
};
|
|
42
102
|
// Get states for a specific country
|
|
43
|
-
export const getStatesForCountry = (countryCode) => {
|
|
103
|
+
export const getStatesForCountry = (countryCode, language = 'en') => {
|
|
104
|
+
const worldDatabase = getWorldDatabase(language);
|
|
44
105
|
const countryData = worldDatabase[countryCode];
|
|
45
106
|
if (!countryData?.regions || !Array.isArray(countryData.regions)) {
|
|
46
107
|
return [];
|
|
@@ -55,8 +116,8 @@ export const getStatesForCountry = (countryCode) => {
|
|
|
55
116
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
56
117
|
};
|
|
57
118
|
// Find country by name (fuzzy search)
|
|
58
|
-
export const findCountryByName = (countryName) => {
|
|
59
|
-
const countries = getCountries();
|
|
119
|
+
export const findCountryByName = (countryName, language = 'en') => {
|
|
120
|
+
const countries = getCountries(language);
|
|
60
121
|
const normalizedSearchName = countryName.toLowerCase().trim();
|
|
61
122
|
// Exact match first
|
|
62
123
|
const exactMatch = countries.find((country) => country.name.toLowerCase() === normalizedSearchName);
|
|
@@ -68,24 +129,26 @@ export const findCountryByName = (countryName) => {
|
|
|
68
129
|
return partialMatch ?? null;
|
|
69
130
|
};
|
|
70
131
|
// Find region by ISO code (e.g., "US-CA" for California)
|
|
71
|
-
export const findRegionByCode = (regionCode) => {
|
|
132
|
+
export const findRegionByCode = (regionCode, language = 'en') => {
|
|
72
133
|
const [countryCode, stateCode] = regionCode.split('-');
|
|
73
134
|
if (!countryCode || !stateCode)
|
|
74
135
|
return null;
|
|
75
|
-
const states = getStatesForCountry(countryCode);
|
|
136
|
+
const states = getStatesForCountry(countryCode, language);
|
|
76
137
|
return states.find((state) => state.code === stateCode) ?? null;
|
|
77
138
|
};
|
|
78
139
|
// Validate if a country code exists
|
|
79
|
-
export const isValidCountryCode = (countryCode) => {
|
|
140
|
+
export const isValidCountryCode = (countryCode, language = 'en') => {
|
|
141
|
+
const worldDatabase = getWorldDatabase(language);
|
|
80
142
|
return Object.prototype.hasOwnProperty.call(worldDatabase, countryCode);
|
|
81
143
|
};
|
|
82
144
|
// Validate if a state code exists for a given country
|
|
83
|
-
export const isValidStateCode = (countryCode, stateCode) => {
|
|
84
|
-
const states = getStatesForCountry(countryCode);
|
|
145
|
+
export const isValidStateCode = (countryCode, stateCode, language = 'en') => {
|
|
146
|
+
const states = getStatesForCountry(countryCode, language);
|
|
85
147
|
return states.some((state) => state.code === stateCode);
|
|
86
148
|
};
|
|
87
149
|
// Get country info including regions
|
|
88
|
-
export const getCountryWithRegions = (countryCode) => {
|
|
150
|
+
export const getCountryWithRegions = (countryCode, language = 'en') => {
|
|
151
|
+
const worldDatabase = getWorldDatabase(language);
|
|
89
152
|
const countryData = worldDatabase[countryCode];
|
|
90
153
|
if (!countryData)
|
|
91
154
|
return null;
|
|
@@ -97,6 +160,6 @@ export const getCountryWithRegions = (countryCode) => {
|
|
|
97
160
|
numeric: countryData.numeric,
|
|
98
161
|
uniqueKey: `country-${countryData.iso}`,
|
|
99
162
|
},
|
|
100
|
-
regions: getStatesForCountry(countryCode),
|
|
163
|
+
regions: getStatesForCountry(countryCode, language),
|
|
101
164
|
};
|
|
102
165
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
5
|
+
import { usePluginConfig } from '../hooks/usePluginConfig';
|
|
5
6
|
const safeStringify = (value) => {
|
|
6
7
|
if (value === null)
|
|
7
8
|
return 'null';
|
|
@@ -74,6 +75,7 @@ const TreeView = ({ data, name, level = 0, maxLevel = 3 }) => {
|
|
|
74
75
|
};
|
|
75
76
|
export const DebugDrawer = ({ isOpen, onClose }) => {
|
|
76
77
|
const context = useTagadaContext();
|
|
78
|
+
const pluginConfig = usePluginConfig();
|
|
77
79
|
const [activeTab, setActiveTab] = useState('overview');
|
|
78
80
|
useEffect(() => {
|
|
79
81
|
const handleEscape = (e) => {
|
|
@@ -88,6 +90,7 @@ export const DebugDrawer = ({ isOpen, onClose }) => {
|
|
|
88
90
|
return null;
|
|
89
91
|
const baseTabs = [
|
|
90
92
|
{ id: 'overview', label: 'Overview' },
|
|
93
|
+
{ id: 'config', label: 'Config' },
|
|
91
94
|
{ id: 'store', label: 'Store' },
|
|
92
95
|
{ id: 'session', label: 'Session' },
|
|
93
96
|
{ id: 'auth', label: 'Auth' },
|
|
@@ -154,7 +157,19 @@ export const DebugDrawer = ({ isOpen, onClose }) => {
|
|
|
154
157
|
padding: '16px',
|
|
155
158
|
overflow: 'auto',
|
|
156
159
|
backgroundColor: '#1f2937',
|
|
157
|
-
}, children: [activeTab === 'overview' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 16px 0', color: '#60a5fa' }, children: "SDK Overview" }), _jsxs("div", { style: { display: 'grid', gap: '12px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Initialized:" }), _jsx("span", { style: { color: context.isInitialized ? '#10b981' : '#ef4444' }, children: context.isInitialized ? '✅ Yes' : '❌ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Loading:" }), _jsx("span", { style: { color: context.isLoading ? '#f59e0b' : '#10b981' }, children: context.isLoading ? '⏳ Yes' : '✅ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Environment:" }), _jsx("span", { style: { color: '#60a5fa' }, children: context.environment.apiConfig.baseUrl.includes('dev') ? 'Development' : 'Production' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "API Base URL:" }), _jsx("span", { style: { color: '#10b981' }, children: context.environment.apiConfig.baseUrl })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Store ID:" }), _jsx("span", { style: { color: '#60a5fa' }, children: context.store?.id || 'Not set' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Customer ID:" }), _jsx("span", { style: { color: '#60a5fa' }, children: context.customer?.id || 'Not set' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Authenticated:" }), _jsx("span", { style: { color: context.auth.isAuthenticated ? '#10b981' : '#ef4444' }, children: context.auth.isAuthenticated ? '✅ Yes' : '❌ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Currency:" }), _jsxs("span", { style: { color: '#f59e0b' }, children: [context.currency.code, " (", context.currency.symbol, ")"] })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Locale:" }), _jsx("span", { style: { color: '#8b5cf6' }, children: context.locale.locale })] })] })] })), activeTab === '
|
|
160
|
+
}, children: [activeTab === 'overview' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 16px 0', color: '#60a5fa' }, children: "SDK Overview" }), _jsxs("div", { style: { display: 'grid', gap: '12px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Initialized:" }), _jsx("span", { style: { color: context.isInitialized ? '#10b981' : '#ef4444' }, children: context.isInitialized ? '✅ Yes' : '❌ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Loading:" }), _jsx("span", { style: { color: context.isLoading ? '#f59e0b' : '#10b981' }, children: context.isLoading ? '⏳ Yes' : '✅ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Environment:" }), _jsx("span", { style: { color: '#60a5fa' }, children: context.environment.apiConfig.baseUrl.includes('dev') ? 'Development' : 'Production' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "API Base URL:" }), _jsx("span", { style: { color: '#10b981' }, children: context.environment.apiConfig.baseUrl })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Store ID:" }), _jsx("span", { style: { color: '#60a5fa' }, children: context.store?.id || 'Not set' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Customer ID:" }), _jsx("span", { style: { color: '#60a5fa' }, children: context.customer?.id || 'Not set' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Authenticated:" }), _jsx("span", { style: { color: context.auth.isAuthenticated ? '#10b981' : '#ef4444' }, children: context.auth.isAuthenticated ? '✅ Yes' : '❌ No' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Currency:" }), _jsxs("span", { style: { color: '#f59e0b' }, children: [context.currency.code, " (", context.currency.symbol, ")"] })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Locale:" }), _jsx("span", { style: { color: '#8b5cf6' }, children: context.locale.locale })] })] })] })), activeTab === 'config' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 16px 0', color: '#60a5fa' }, children: "Plugin Configuration" }), _jsxs("div", { style: { marginBottom: '24px' }, children: [_jsx("h4", { style: { margin: '0 0 12px 0', color: '#9ca3af', fontSize: '14px' }, children: "Configuration Summary" }), _jsxs("div", { style: { display: 'grid', gap: '8px', fontSize: '13px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Store ID:" }), _jsx("span", { style: { color: '#60a5fa' }, children: pluginConfig.storeId || 'Not set' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Account ID:" }), _jsx("span", { style: { color: '#60a5fa' }, children: pluginConfig.accountId || 'Not set' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Base Path:" }), _jsx("span", { style: { color: '#10b981' }, children: pluginConfig.basePath || '/' })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Config Name:" }), _jsx("span", { style: { color: '#f59e0b' }, children: pluginConfig.config?.configName || 'default' })] }), pluginConfig.config?.branding?.primaryColor && (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Primary Color:" }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx("div", { style: {
|
|
161
|
+
width: '16px',
|
|
162
|
+
height: '16px',
|
|
163
|
+
backgroundColor: pluginConfig.config.branding.primaryColor,
|
|
164
|
+
border: '1px solid #374151',
|
|
165
|
+
borderRadius: '3px',
|
|
166
|
+
} }), _jsx("span", { style: { color: '#10b981' }, children: pluginConfig.config.branding.primaryColor })] })] }))] })] }), _jsxs("div", { style: { marginBottom: '16px' }, children: [_jsx("h4", { style: { margin: '0 0 12px 0', color: '#9ca3af', fontSize: '14px' }, children: "Full Configuration" }), _jsx(TreeView, { data: pluginConfig, name: "pluginConfig" })] }), process.env.NODE_ENV === 'development' && (_jsxs("div", { style: {
|
|
167
|
+
padding: '12px',
|
|
168
|
+
backgroundColor: '#1f2937',
|
|
169
|
+
border: '1px solid #374151',
|
|
170
|
+
borderRadius: '6px',
|
|
171
|
+
marginTop: '16px',
|
|
172
|
+
}, children: [_jsx("h4", { style: { margin: '0 0 8px 0', color: '#f59e0b', fontSize: '14px' }, children: "Development Mode" }), _jsxs("p", { style: { margin: '0', fontSize: '12px', color: '#9ca3af', lineHeight: '1.4' }, children: ["Configuration is loaded from ", _jsx("code", { style: { color: '#60a5fa' }, children: "/.local.json" }), " and", ' ', _jsxs("code", { style: { color: '#60a5fa' }, children: ["/config/", pluginConfig.config?.configName || 'default', ".tgd.json"] })] }), _jsxs("p", { style: { margin: '8px 0 0 0', fontSize: '12px', color: '#9ca3af', lineHeight: '1.4' }, children: ["Local config variant:", ' ', _jsx("code", { style: { color: '#f59e0b' }, children: pluginConfig.config?.configName || 'default' })] })] }))] })), activeTab === 'store' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 16px 0', color: '#60a5fa' }, children: "Store Configuration" }), context.store ? (_jsx(TreeView, { data: context.store, name: "store" })) : (_jsx("p", { style: { color: '#6b7280' }, children: "No store data available" }))] })), activeTab === 'session' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 16px 0', color: '#60a5fa' }, children: "Session Data" }), context.session ? (_jsx(TreeView, { data: context.session, name: "session" })) : (_jsx("p", { style: { color: '#6b7280' }, children: "No session data available" }))] })), activeTab === 'auth' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 16px 0', color: '#60a5fa' }, children: "Authentication State" }), _jsx(TreeView, { data: context.auth, name: "auth" }), context.customer && (_jsxs(_Fragment, { children: [_jsx("h4", { style: { margin: '24px 0 12px 0', color: '#60a5fa' }, children: "Customer Data" }), _jsx(TreeView, { data: context.customer, name: "customer" })] }))] })), activeTab === 'items' && (_jsxs("div", { children: [_jsx("h3", { style: { margin: '0 0 16px 0', color: '#60a5fa' }, children: "Items & Discounts" }), context.debugCheckout.data?.checkout?.summary ? (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: '24px' }, children: [_jsx("h4", { style: { margin: '0 0 12px 0', color: '#60a5fa' }, children: "Summary" }), _jsxs("div", { style: { display: 'grid', gap: '8px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Currency:" }), _jsx("span", { style: { color: '#f59e0b' }, children: context.debugCheckout.data.checkout.summary.currency })] }), _jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [_jsx("span", { children: "Subtotal:" }), _jsx("span", { style: { color: '#9ca3af' }, children: (() => {
|
|
158
173
|
try {
|
|
159
174
|
return context.money.formatMoney(Number(context.debugCheckout.data.checkout.summary.subtotalAmount) || 0, String(context.debugCheckout.data.checkout.summary.currency) || 'USD', context.locale.locale);
|
|
160
175
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SupportedLanguage } from '../../data/iso3166';
|
|
1
2
|
export interface ISOCountry {
|
|
2
3
|
iso: string;
|
|
3
4
|
iso3: string;
|
|
@@ -16,26 +17,26 @@ export interface UseISODataResult {
|
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* React hook for accessing ISO3166 countries and regions data
|
|
19
|
-
* @param language - Language code (en,
|
|
20
|
-
* @param disputeSetting - Territorial dispute perspective (UN
|
|
20
|
+
* @param language - Language code (supports: en, ru, de, fr, es, zh, hi, pt, ja, ar, it, he)
|
|
21
|
+
* @param disputeSetting - Territorial dispute perspective (currently only UN is supported)
|
|
21
22
|
* @returns Object with countries data and helper functions
|
|
22
23
|
*/
|
|
23
|
-
export declare function useISOData(language?:
|
|
24
|
+
export declare function useISOData(language?: SupportedLanguage, disputeSetting?: string): UseISODataResult;
|
|
24
25
|
/**
|
|
25
26
|
* Get available languages for ISO data
|
|
26
27
|
*/
|
|
27
|
-
export declare function getAvailableLanguages():
|
|
28
|
+
export declare function getAvailableLanguages(): SupportedLanguage[];
|
|
28
29
|
/**
|
|
29
30
|
* Get list of countries as options for select components
|
|
30
31
|
*/
|
|
31
|
-
export declare function useCountryOptions(language?:
|
|
32
|
+
export declare function useCountryOptions(language?: SupportedLanguage): {
|
|
32
33
|
value: string;
|
|
33
34
|
label: string;
|
|
34
35
|
}[];
|
|
35
36
|
/**
|
|
36
37
|
* Get list of regions/states for a country as options for select components
|
|
37
38
|
*/
|
|
38
|
-
export declare function useRegionOptions(countryCode: string, language?:
|
|
39
|
+
export declare function useRegionOptions(countryCode: string, language?: SupportedLanguage): {
|
|
39
40
|
value: string;
|
|
40
41
|
label: string;
|
|
41
42
|
}[];
|
|
@@ -1,75 +1,35 @@
|
|
|
1
|
-
import { useMemo
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
// Import the pre-built ISO data functions
|
|
3
|
+
import { getCountries, getStatesForCountry } from '../../data/iso3166';
|
|
2
4
|
/**
|
|
3
5
|
* React hook for accessing ISO3166 countries and regions data
|
|
4
|
-
* @param language - Language code (en,
|
|
5
|
-
* @param disputeSetting - Territorial dispute perspective (UN
|
|
6
|
+
* @param language - Language code (supports: en, ru, de, fr, es, zh, hi, pt, ja, ar, it, he)
|
|
7
|
+
* @param disputeSetting - Territorial dispute perspective (currently only UN is supported)
|
|
6
8
|
* @returns Object with countries data and helper functions
|
|
7
9
|
*/
|
|
8
10
|
export function useISOData(language = 'en', disputeSetting = 'UN') {
|
|
9
|
-
const [isoModule, setIsoModule] = useState(null);
|
|
10
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
let isMounted = true;
|
|
13
|
-
const loadModule = async () => {
|
|
14
|
-
try {
|
|
15
|
-
// Try dynamic import first (ES modules)
|
|
16
|
-
const isoModule = await import('iso3166-2-db');
|
|
17
|
-
if (isMounted) {
|
|
18
|
-
setIsoModule(isoModule);
|
|
19
|
-
setIsLoading(false);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
catch (importError) {
|
|
23
|
-
console.error(`Failed to load ISO data module:`, importError);
|
|
24
|
-
if (isMounted) {
|
|
25
|
-
setIsoModule(null);
|
|
26
|
-
setIsLoading(false);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
void loadModule();
|
|
31
|
-
return () => {
|
|
32
|
-
isMounted = false;
|
|
33
|
-
};
|
|
34
|
-
}, []);
|
|
35
11
|
const data = useMemo(() => {
|
|
36
|
-
if (isLoading || !isoModule) {
|
|
37
|
-
return {
|
|
38
|
-
countries: {},
|
|
39
|
-
getRegions: () => [],
|
|
40
|
-
findRegion: () => null,
|
|
41
|
-
mapGoogleToISO: () => null,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
12
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// Transform to our expected format
|
|
13
|
+
// Get countries from pre-built data with language support (now synchronous)
|
|
14
|
+
const countriesArray = getCountries(language);
|
|
15
|
+
// Transform to our expected format (Record<string, ISOCountry>)
|
|
49
16
|
const countries = {};
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
name: countryData.name,
|
|
17
|
+
countriesArray.forEach((country) => {
|
|
18
|
+
countries[country.code] = {
|
|
19
|
+
iso: country.code,
|
|
20
|
+
iso3: country.iso3 || '',
|
|
21
|
+
numeric: country.numeric || 0,
|
|
22
|
+
name: country.name,
|
|
57
23
|
};
|
|
58
24
|
});
|
|
59
25
|
// Helper to load regions for a specific country
|
|
60
26
|
const getRegions = (countryCode) => {
|
|
61
27
|
try {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const regionData = countryData.regions[regionCode];
|
|
68
|
-
return {
|
|
69
|
-
iso: regionData.iso,
|
|
70
|
-
name: regionData.name,
|
|
71
|
-
};
|
|
72
|
-
});
|
|
28
|
+
const states = getStatesForCountry(countryCode, language);
|
|
29
|
+
return states.map((state) => ({
|
|
30
|
+
iso: state.code,
|
|
31
|
+
name: state.name,
|
|
32
|
+
}));
|
|
73
33
|
}
|
|
74
34
|
catch {
|
|
75
35
|
return []; // Return empty array if no regions
|
|
@@ -117,14 +77,15 @@ export function useISOData(language = 'en', disputeSetting = 'UN') {
|
|
|
117
77
|
mapGoogleToISO: () => null,
|
|
118
78
|
};
|
|
119
79
|
}
|
|
120
|
-
}, [
|
|
80
|
+
}, [language, disputeSetting]);
|
|
121
81
|
return data;
|
|
122
82
|
}
|
|
123
83
|
/**
|
|
124
84
|
* Get available languages for ISO data
|
|
125
85
|
*/
|
|
126
86
|
export function getAvailableLanguages() {
|
|
127
|
-
|
|
87
|
+
// Return all statically imported languages for browser compatibility
|
|
88
|
+
return ['en', 'ru', 'de', 'fr', 'es', 'zh', 'hi', 'pt', 'ja', 'ar', 'it', 'he'];
|
|
128
89
|
}
|
|
129
90
|
/**
|
|
130
91
|
* Get list of countries as options for select components
|
|
@@ -46,6 +46,59 @@ export interface PostPurchaseOffer {
|
|
|
46
46
|
summaries: PostPurchaseOfferSummary[];
|
|
47
47
|
offerLineItems: PostPurchaseOfferLineItem[];
|
|
48
48
|
}
|
|
49
|
+
export interface CurrencyOptions {
|
|
50
|
+
rate: number;
|
|
51
|
+
date: string;
|
|
52
|
+
amount: number;
|
|
53
|
+
lock: boolean;
|
|
54
|
+
}
|
|
55
|
+
export interface VariantOption {
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
sku: string | null;
|
|
59
|
+
default: boolean | null;
|
|
60
|
+
externalVariantId: string | null;
|
|
61
|
+
prices: {
|
|
62
|
+
id: string;
|
|
63
|
+
currencyOptions: CurrencyOptions;
|
|
64
|
+
}[];
|
|
65
|
+
}
|
|
66
|
+
export interface OrderSummaryItem {
|
|
67
|
+
id: string;
|
|
68
|
+
productId: string;
|
|
69
|
+
productName: string;
|
|
70
|
+
productDescription: string | null;
|
|
71
|
+
variantId: string;
|
|
72
|
+
variantName: string;
|
|
73
|
+
variantSku: string | null;
|
|
74
|
+
variantDefault: boolean | null;
|
|
75
|
+
variantExternalId: string | null;
|
|
76
|
+
priceId: string;
|
|
77
|
+
currencyOptions: CurrencyOptions;
|
|
78
|
+
quantity: number;
|
|
79
|
+
unitAmount: number;
|
|
80
|
+
amount: number;
|
|
81
|
+
adjustedAmount: number;
|
|
82
|
+
imageUrl?: string;
|
|
83
|
+
product: {
|
|
84
|
+
name: string;
|
|
85
|
+
description: string;
|
|
86
|
+
};
|
|
87
|
+
variant: {
|
|
88
|
+
name: string;
|
|
89
|
+
description: string;
|
|
90
|
+
imageUrl: string;
|
|
91
|
+
grams: number | null;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export interface OrderSummary {
|
|
95
|
+
items: OrderSummaryItem[];
|
|
96
|
+
totalAmount: number;
|
|
97
|
+
totalAdjustedAmount: number;
|
|
98
|
+
totalPromotionAmount: number;
|
|
99
|
+
currency: string;
|
|
100
|
+
options: Record<string, VariantOption[]>;
|
|
101
|
+
}
|
|
49
102
|
export interface UsePostPurchasesOptions {
|
|
50
103
|
/**
|
|
51
104
|
* OrderID to fetch post-purchase offers for
|
|
@@ -56,6 +109,18 @@ export interface UsePostPurchasesOptions {
|
|
|
56
109
|
* @default true
|
|
57
110
|
*/
|
|
58
111
|
enabled?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Whether to automatically initialize checkout sessions for offers
|
|
114
|
+
* @default false
|
|
115
|
+
*/
|
|
116
|
+
autoInitializeCheckout?: boolean;
|
|
117
|
+
}
|
|
118
|
+
export interface CheckoutSessionState {
|
|
119
|
+
checkoutSessionId: string | null;
|
|
120
|
+
orderSummary: OrderSummary | null;
|
|
121
|
+
selectedVariants: Record<string, string>;
|
|
122
|
+
loadingVariants: Record<string, boolean>;
|
|
123
|
+
isUpdatingSummary: boolean;
|
|
59
124
|
}
|
|
60
125
|
export interface UsePostPurchasesResult {
|
|
61
126
|
/**
|
|
@@ -95,15 +160,58 @@ export interface UsePostPurchasesResult {
|
|
|
95
160
|
/**
|
|
96
161
|
* Initialize a checkout session for a post-purchase offer with specific variants
|
|
97
162
|
*/
|
|
98
|
-
initCheckoutSessionWithVariants: (offerId: string, orderId: string, lineItems:
|
|
163
|
+
initCheckoutSessionWithVariants: (offerId: string, orderId: string, lineItems: {
|
|
99
164
|
variantId: string;
|
|
100
165
|
quantity: number;
|
|
101
|
-
}
|
|
166
|
+
}[]) => Promise<{
|
|
102
167
|
checkoutSessionId: string;
|
|
103
168
|
}>;
|
|
104
169
|
/**
|
|
105
170
|
* Pay with a checkout session for a post-purchase offer
|
|
106
171
|
*/
|
|
107
172
|
payWithCheckoutSession: (checkoutSessionId: string, orderId?: string) => Promise<void>;
|
|
173
|
+
/**
|
|
174
|
+
* Get checkout session state for an offer
|
|
175
|
+
*/
|
|
176
|
+
getCheckoutSessionState: (offerId: string) => CheckoutSessionState | null;
|
|
177
|
+
/**
|
|
178
|
+
* Initialize checkout session with variant options for an offer
|
|
179
|
+
*/
|
|
180
|
+
initializeOfferCheckout: (offerId: string) => Promise<void>;
|
|
181
|
+
/**
|
|
182
|
+
* Get available variants for a product in an offer's checkout session
|
|
183
|
+
*/
|
|
184
|
+
getAvailableVariants: (offerId: string, productId: string) => {
|
|
185
|
+
variantId: string;
|
|
186
|
+
variantName: string;
|
|
187
|
+
variantSku: string | null;
|
|
188
|
+
variantDefault: boolean | null;
|
|
189
|
+
variantExternalId: string | null;
|
|
190
|
+
priceId: string;
|
|
191
|
+
currencyOptions: CurrencyOptions;
|
|
192
|
+
}[];
|
|
193
|
+
/**
|
|
194
|
+
* Select a variant for a product in an offer's checkout session
|
|
195
|
+
*/
|
|
196
|
+
selectVariant: (offerId: string, productId: string, variantId: string) => Promise<void>;
|
|
197
|
+
/**
|
|
198
|
+
* Get the order summary for an offer's checkout session
|
|
199
|
+
*/
|
|
200
|
+
getOrderSummary: (offerId: string) => OrderSummary | null;
|
|
201
|
+
/**
|
|
202
|
+
* Check if variants are being loaded for a specific product in an offer
|
|
203
|
+
*/
|
|
204
|
+
isLoadingVariants: (offerId: string, productId: string) => boolean;
|
|
205
|
+
/**
|
|
206
|
+
* Check if order summary is being updated for an offer
|
|
207
|
+
*/
|
|
208
|
+
isUpdatingOrderSummary: (offerId: string) => boolean;
|
|
209
|
+
/**
|
|
210
|
+
* Confirm purchase for an offer with current variant selections
|
|
211
|
+
*/
|
|
212
|
+
confirmPurchase: (offerId: string, options?: {
|
|
213
|
+
draft?: boolean;
|
|
214
|
+
returnUrl?: string;
|
|
215
|
+
}) => Promise<void>;
|
|
108
216
|
}
|
|
109
217
|
export declare function usePostPurchases(options: UsePostPurchasesOptions): UsePostPurchasesResult;
|
|
@@ -2,10 +2,12 @@ import { useCallback, useEffect, useState } from 'react';
|
|
|
2
2
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
3
3
|
export function usePostPurchases(options) {
|
|
4
4
|
const { apiService, session } = useTagadaContext();
|
|
5
|
-
const { orderId, enabled = true } = options;
|
|
5
|
+
const { orderId, enabled = true, autoInitializeCheckout = false } = options;
|
|
6
6
|
const [offers, setOffers] = useState([]);
|
|
7
7
|
const [isLoading, setIsLoading] = useState(false);
|
|
8
8
|
const [error, setError] = useState(null);
|
|
9
|
+
// Enhanced state for checkout sessions per offer
|
|
10
|
+
const [checkoutSessions, setCheckoutSessions] = useState({});
|
|
9
11
|
const fetchOffers = useCallback(async () => {
|
|
10
12
|
if (!orderId) {
|
|
11
13
|
setOffers([]);
|
|
@@ -17,7 +19,14 @@ export function usePostPurchases(options) {
|
|
|
17
19
|
const response = await apiService.fetch(`/api/v1/post-purchase/${orderId}/offers`, {
|
|
18
20
|
method: 'GET',
|
|
19
21
|
});
|
|
20
|
-
|
|
22
|
+
const fetchedOffers = response || [];
|
|
23
|
+
setOffers(fetchedOffers);
|
|
24
|
+
// Auto-initialize checkout sessions if enabled
|
|
25
|
+
if (autoInitializeCheckout && fetchedOffers.length > 0) {
|
|
26
|
+
for (const offer of fetchedOffers) {
|
|
27
|
+
await initializeOfferCheckout(offer.id);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
21
30
|
}
|
|
22
31
|
catch (err) {
|
|
23
32
|
const error = err instanceof Error ? err : new Error('Failed to fetch post-purchase offers');
|
|
@@ -27,7 +36,7 @@ export function usePostPurchases(options) {
|
|
|
27
36
|
finally {
|
|
28
37
|
setIsLoading(false);
|
|
29
38
|
}
|
|
30
|
-
}, [orderId]);
|
|
39
|
+
}, [orderId, autoInitializeCheckout]);
|
|
31
40
|
const initCheckoutSession = useCallback(async (offerId, orderId) => {
|
|
32
41
|
if (!session?.customerId) {
|
|
33
42
|
throw new Error('Customer ID is required');
|
|
@@ -35,26 +44,26 @@ export function usePostPurchases(options) {
|
|
|
35
44
|
const response = await apiService.fetch(`/api/v1/checkout/offer/init`, {
|
|
36
45
|
method: 'POST',
|
|
37
46
|
body: JSON.stringify({
|
|
38
|
-
offerId
|
|
47
|
+
offerId,
|
|
39
48
|
returnUrl: window.location.href,
|
|
40
49
|
customerId: session?.customerId,
|
|
41
50
|
orderId,
|
|
42
51
|
}),
|
|
43
52
|
});
|
|
44
53
|
return response;
|
|
45
|
-
}, []);
|
|
54
|
+
}, [apiService, session?.customerId]);
|
|
46
55
|
const initCheckoutSessionWithVariants = useCallback(async (offerId, orderId, lineItems) => {
|
|
47
56
|
const response = await apiService.fetch(`/api/v1/offers/${offerId}/transform-to-checkout`, {
|
|
48
57
|
method: 'POST',
|
|
49
58
|
body: JSON.stringify({
|
|
50
|
-
offerId
|
|
51
|
-
lineItems
|
|
59
|
+
offerId,
|
|
60
|
+
lineItems,
|
|
52
61
|
returnUrl: window.location.href,
|
|
53
62
|
mainOrderId: orderId,
|
|
54
63
|
}),
|
|
55
64
|
});
|
|
56
65
|
return response;
|
|
57
|
-
}, []);
|
|
66
|
+
}, [apiService]);
|
|
58
67
|
const payWithCheckoutSession = useCallback(async (checkoutSessionId, orderId) => {
|
|
59
68
|
const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkoutSessionId}/pay`, {
|
|
60
69
|
method: 'POST',
|
|
@@ -67,7 +76,206 @@ export function usePostPurchases(options) {
|
|
|
67
76
|
}),
|
|
68
77
|
});
|
|
69
78
|
return response;
|
|
70
|
-
}, []);
|
|
79
|
+
}, [apiService]);
|
|
80
|
+
// Enhanced checkout session management
|
|
81
|
+
const initializeOfferCheckout = useCallback(async (offerId) => {
|
|
82
|
+
if (!session?.customerId) {
|
|
83
|
+
throw new Error('Customer ID is required');
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
// Initialize checkout session
|
|
87
|
+
const initResult = await initCheckoutSession(offerId, orderId);
|
|
88
|
+
if (!initResult.checkoutSessionId) {
|
|
89
|
+
throw new Error('Failed to initialize checkout session');
|
|
90
|
+
}
|
|
91
|
+
const sessionId = initResult.checkoutSessionId;
|
|
92
|
+
// Initialize session state
|
|
93
|
+
setCheckoutSessions(prev => ({
|
|
94
|
+
...prev,
|
|
95
|
+
[offerId]: {
|
|
96
|
+
checkoutSessionId: sessionId,
|
|
97
|
+
orderSummary: null,
|
|
98
|
+
selectedVariants: {},
|
|
99
|
+
loadingVariants: {},
|
|
100
|
+
isUpdatingSummary: false,
|
|
101
|
+
}
|
|
102
|
+
}));
|
|
103
|
+
// Fetch order summary with variant options
|
|
104
|
+
await fetchOrderSummary(offerId, sessionId);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.error(`[SDK] Failed to initialize checkout for offer ${offerId}:`, error);
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}, [initCheckoutSession, orderId, session?.customerId]);
|
|
111
|
+
const fetchOrderSummary = useCallback(async (offerId, sessionId) => {
|
|
112
|
+
try {
|
|
113
|
+
// Set updating state
|
|
114
|
+
setCheckoutSessions(prev => ({
|
|
115
|
+
...prev,
|
|
116
|
+
[offerId]: {
|
|
117
|
+
...prev[offerId],
|
|
118
|
+
isUpdatingSummary: true,
|
|
119
|
+
}
|
|
120
|
+
}));
|
|
121
|
+
const summaryResult = await apiService.fetch(`/api/v1/checkout-sessions/${sessionId}/order-summary`, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
body: JSON.stringify({ includeVariantOptions: true }),
|
|
124
|
+
});
|
|
125
|
+
if (summaryResult) {
|
|
126
|
+
// Sort items by productId to ensure consistent order
|
|
127
|
+
const sortedItems = [...summaryResult.items].sort((a, b) => a.productId.localeCompare(b.productId));
|
|
128
|
+
const orderSummary = {
|
|
129
|
+
...summaryResult,
|
|
130
|
+
items: sortedItems,
|
|
131
|
+
};
|
|
132
|
+
// Initialize selected variants based on the summary
|
|
133
|
+
const initialVariants = {};
|
|
134
|
+
sortedItems.forEach((item) => {
|
|
135
|
+
if (item.productId && item.variantId) {
|
|
136
|
+
initialVariants[item.productId] = item.variantId;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
setCheckoutSessions(prev => ({
|
|
140
|
+
...prev,
|
|
141
|
+
[offerId]: {
|
|
142
|
+
...prev[offerId],
|
|
143
|
+
orderSummary,
|
|
144
|
+
selectedVariants: initialVariants,
|
|
145
|
+
isUpdatingSummary: false,
|
|
146
|
+
}
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error(`[SDK] Failed to fetch order summary for offer ${offerId}:`, error);
|
|
152
|
+
setCheckoutSessions(prev => ({
|
|
153
|
+
...prev,
|
|
154
|
+
[offerId]: {
|
|
155
|
+
...prev[offerId],
|
|
156
|
+
isUpdatingSummary: false,
|
|
157
|
+
}
|
|
158
|
+
}));
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}, [apiService]);
|
|
162
|
+
const selectVariant = useCallback(async (offerId, productId, variantId) => {
|
|
163
|
+
const sessionState = checkoutSessions[offerId];
|
|
164
|
+
if (!sessionState?.checkoutSessionId || !sessionState.orderSummary) {
|
|
165
|
+
throw new Error('Checkout session not initialized for this offer');
|
|
166
|
+
}
|
|
167
|
+
// Set loading state for this specific variant
|
|
168
|
+
setCheckoutSessions(prev => ({
|
|
169
|
+
...prev,
|
|
170
|
+
[offerId]: {
|
|
171
|
+
...prev[offerId],
|
|
172
|
+
loadingVariants: {
|
|
173
|
+
...prev[offerId].loadingVariants,
|
|
174
|
+
[productId]: true,
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}));
|
|
178
|
+
try {
|
|
179
|
+
const availableVariants = getAvailableVariants(offerId, productId);
|
|
180
|
+
const selectedVariant = availableVariants.find(v => v.variantId === variantId);
|
|
181
|
+
if (!selectedVariant) {
|
|
182
|
+
throw new Error('Selected variant not found');
|
|
183
|
+
}
|
|
184
|
+
// Find the current item to get its quantity
|
|
185
|
+
const currentItem = sessionState.orderSummary.items.find(item => item.productId === productId);
|
|
186
|
+
if (!currentItem) {
|
|
187
|
+
throw new Error('Current item not found');
|
|
188
|
+
}
|
|
189
|
+
// Update selected variants state
|
|
190
|
+
setCheckoutSessions(prev => ({
|
|
191
|
+
...prev,
|
|
192
|
+
[offerId]: {
|
|
193
|
+
...prev[offerId],
|
|
194
|
+
selectedVariants: {
|
|
195
|
+
...prev[offerId].selectedVariants,
|
|
196
|
+
[productId]: variantId,
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}));
|
|
200
|
+
// Update line items on the server
|
|
201
|
+
await apiService.fetch(`/api/v1/checkout-sessions/${sessionState.checkoutSessionId}/line-items`, {
|
|
202
|
+
method: 'POST',
|
|
203
|
+
body: JSON.stringify({
|
|
204
|
+
lineItems: [
|
|
205
|
+
{
|
|
206
|
+
variantId: selectedVariant.variantId,
|
|
207
|
+
quantity: currentItem.quantity,
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
}),
|
|
211
|
+
});
|
|
212
|
+
// Refetch order summary after successful line item update
|
|
213
|
+
await fetchOrderSummary(offerId, sessionState.checkoutSessionId);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.error(`[SDK] Failed to update variant for offer ${offerId}:`, error);
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
// Clear loading state for this specific variant
|
|
221
|
+
setCheckoutSessions(prev => ({
|
|
222
|
+
...prev,
|
|
223
|
+
[offerId]: {
|
|
224
|
+
...prev[offerId],
|
|
225
|
+
loadingVariants: {
|
|
226
|
+
...prev[offerId].loadingVariants,
|
|
227
|
+
[productId]: false,
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
}, [checkoutSessions, apiService, fetchOrderSummary]);
|
|
233
|
+
const confirmPurchase = useCallback(async (offerId, options) => {
|
|
234
|
+
const sessionState = checkoutSessions[offerId];
|
|
235
|
+
if (!sessionState?.checkoutSessionId) {
|
|
236
|
+
throw new Error('Checkout session not initialized for this offer');
|
|
237
|
+
}
|
|
238
|
+
const response = await apiService.fetch(`/api/v1/checkout-sessions/${sessionState.checkoutSessionId}/pay`, {
|
|
239
|
+
method: 'POST',
|
|
240
|
+
body: JSON.stringify({
|
|
241
|
+
checkoutSessionId: sessionState.checkoutSessionId,
|
|
242
|
+
draft: options?.draft || false,
|
|
243
|
+
returnUrl: options?.returnUrl || window.location.href,
|
|
244
|
+
metadata: {
|
|
245
|
+
comingFromPostPurchase: true,
|
|
246
|
+
postOrder: orderId,
|
|
247
|
+
},
|
|
248
|
+
}),
|
|
249
|
+
});
|
|
250
|
+
return response;
|
|
251
|
+
}, [checkoutSessions, apiService, orderId]);
|
|
252
|
+
// Helper functions
|
|
253
|
+
const getCheckoutSessionState = useCallback((offerId) => {
|
|
254
|
+
return checkoutSessions[offerId] || null;
|
|
255
|
+
}, [checkoutSessions]);
|
|
256
|
+
const getAvailableVariants = useCallback((offerId, productId) => {
|
|
257
|
+
const sessionState = checkoutSessions[offerId];
|
|
258
|
+
if (!sessionState?.orderSummary?.options?.[productId])
|
|
259
|
+
return [];
|
|
260
|
+
return sessionState.orderSummary.options[productId].map((variant) => ({
|
|
261
|
+
variantId: variant.id,
|
|
262
|
+
variantName: variant.name,
|
|
263
|
+
variantSku: variant.sku,
|
|
264
|
+
variantDefault: variant.default,
|
|
265
|
+
variantExternalId: variant.externalVariantId,
|
|
266
|
+
priceId: variant.prices[0]?.id,
|
|
267
|
+
currencyOptions: variant.prices[0]?.currencyOptions,
|
|
268
|
+
}));
|
|
269
|
+
}, [checkoutSessions]);
|
|
270
|
+
const getOrderSummary = useCallback((offerId) => {
|
|
271
|
+
return checkoutSessions[offerId]?.orderSummary || null;
|
|
272
|
+
}, [checkoutSessions]);
|
|
273
|
+
const isLoadingVariants = useCallback((offerId, productId) => {
|
|
274
|
+
return checkoutSessions[offerId]?.loadingVariants?.[productId] || false;
|
|
275
|
+
}, [checkoutSessions]);
|
|
276
|
+
const isUpdatingOrderSummary = useCallback((offerId) => {
|
|
277
|
+
return checkoutSessions[offerId]?.isUpdatingSummary || false;
|
|
278
|
+
}, [checkoutSessions]);
|
|
71
279
|
useEffect(() => {
|
|
72
280
|
if (enabled && orderId) {
|
|
73
281
|
fetchOffers();
|
|
@@ -103,5 +311,14 @@ export function usePostPurchases(options) {
|
|
|
103
311
|
initCheckoutSession,
|
|
104
312
|
initCheckoutSessionWithVariants,
|
|
105
313
|
payWithCheckoutSession,
|
|
314
|
+
// Enhanced functionality
|
|
315
|
+
getCheckoutSessionState,
|
|
316
|
+
initializeOfferCheckout,
|
|
317
|
+
getAvailableVariants,
|
|
318
|
+
selectVariant,
|
|
319
|
+
getOrderSummary,
|
|
320
|
+
isLoadingVariants,
|
|
321
|
+
isUpdatingOrderSummary,
|
|
322
|
+
confirmPurchase,
|
|
106
323
|
};
|
|
107
324
|
}
|