@tagadapay/plugin-sdk 2.2.0 → 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.
@@ -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 === '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: (() => {
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
  }
@@ -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: Array<{
163
+ initCheckoutSessionWithVariants: (offerId: string, orderId: string, lineItems: {
99
164
  variantId: string;
100
165
  quantity: number;
101
- }>) => Promise<{
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
- setOffers(response || []);
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: 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: offerId,
51
- lineItems: 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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagadapay/plugin-sdk",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Modern React SDK for building Tagada Pay plugins",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",