@tagadapay/plugin-sdk 2.2.0 → 2.2.2
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 === '
|
|
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:
|
|
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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tagadapay/plugin-sdk",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"description": "Modern React SDK for building Tagada Pay plugins",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,24 +16,6 @@
|
|
|
16
16
|
"require": "./dist/react/index.js"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsc",
|
|
21
|
-
"clean": "rm -rf dist",
|
|
22
|
-
"lint": "echo \"No linting configured\"",
|
|
23
|
-
"test": "echo \"No tests yet\" && exit 0",
|
|
24
|
-
"dev": "tsc --watch",
|
|
25
|
-
"prepublishOnly": "npm run clean && npm run build",
|
|
26
|
-
"publish:patch": "npm version patch && npm publish",
|
|
27
|
-
"publish:minor": "npm version minor && npm publish",
|
|
28
|
-
"publish:major": "npm version major && npm publish",
|
|
29
|
-
"publish:beta": "npm version prerelease --preid=beta && npm publish --tag beta",
|
|
30
|
-
"publish:alpha": "npm version prerelease --preid=alpha && npm publish --tag alpha",
|
|
31
|
-
"version:patch": "npm version patch",
|
|
32
|
-
"version:minor": "npm version minor",
|
|
33
|
-
"version:major": "npm version major",
|
|
34
|
-
"version:beta": "npm version prerelease --preid=beta",
|
|
35
|
-
"version:alpha": "npm version prerelease --preid=alpha"
|
|
36
|
-
},
|
|
37
19
|
"keywords": [
|
|
38
20
|
"tagadapay",
|
|
39
21
|
"cms",
|
|
@@ -74,5 +56,22 @@
|
|
|
74
56
|
"bugs": {
|
|
75
57
|
"url": "https://github.com/tagadapay/plugin-sdk/issues"
|
|
76
58
|
},
|
|
77
|
-
"homepage": "https://github.com/tagadapay/plugin-sdk#readme"
|
|
78
|
-
|
|
59
|
+
"homepage": "https://github.com/tagadapay/plugin-sdk#readme",
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsc",
|
|
62
|
+
"clean": "rm -rf dist",
|
|
63
|
+
"lint": "echo \"No linting configured\"",
|
|
64
|
+
"test": "echo \"No tests yet\" && exit 0",
|
|
65
|
+
"dev": "tsc --watch",
|
|
66
|
+
"publish:patch": "npm version patch && npm publish",
|
|
67
|
+
"publish:minor": "npm version minor && npm publish",
|
|
68
|
+
"publish:major": "npm version major && npm publish",
|
|
69
|
+
"publish:beta": "npm version prerelease --preid=beta && npm publish --tag beta",
|
|
70
|
+
"publish:alpha": "npm version prerelease --preid=alpha && npm publish --tag alpha",
|
|
71
|
+
"version:patch": "npm version patch",
|
|
72
|
+
"version:minor": "npm version minor",
|
|
73
|
+
"version:major": "npm version major",
|
|
74
|
+
"version:beta": "npm version prerelease --preid=beta",
|
|
75
|
+
"version:alpha": "npm version prerelease --preid=alpha"
|
|
76
|
+
}
|
|
77
|
+
}
|