mirror of
https://gitlab.com/davidels1986/ore-cart-checkout.git
synced 2026-10-30 19:19:15 +00:00
144 lines
3.5 KiB
TypeScript
144 lines
3.5 KiB
TypeScript
import { CartItem } from '@/types';
|
|
|
|
const WEBHOOK_URL = 'https://n8nmac.apphub.co.za/webhook/wfsales_quotes';
|
|
const TIMEOUT_MS = 10_000;
|
|
|
|
interface CustomerInfo {
|
|
name: string;
|
|
email: string;
|
|
phone: string;
|
|
company: string;
|
|
}
|
|
|
|
interface WebhookPayload {
|
|
source: string;
|
|
request_type: string;
|
|
quote_request_id: string;
|
|
customer: {
|
|
name: string;
|
|
email: string;
|
|
phone: string;
|
|
company: string;
|
|
};
|
|
cart: {
|
|
cart_id: string;
|
|
currency: string;
|
|
subtotal: number;
|
|
items: {
|
|
sku: string;
|
|
description: string;
|
|
quantity: number;
|
|
unit: string;
|
|
}[];
|
|
};
|
|
metadata: {
|
|
website: string;
|
|
submitted_at: string;
|
|
};
|
|
}
|
|
|
|
function generateUUID(): string {
|
|
return crypto.randomUUID();
|
|
}
|
|
|
|
function isValidEmail(email: string): boolean {
|
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
}
|
|
|
|
export function validateQuoteRequest(
|
|
customer: CustomerInfo,
|
|
items: CartItem[]
|
|
): { valid: boolean; error?: string } {
|
|
if (!customer.name.trim()) {
|
|
return { valid: false, error: 'Customer name is required.' };
|
|
}
|
|
if (!customer.email.trim() || !isValidEmail(customer.email.trim())) {
|
|
return { valid: false, error: 'A valid email address is required.' };
|
|
}
|
|
if (items.length === 0) {
|
|
return { valid: false, error: 'Cart is empty.' };
|
|
}
|
|
return { valid: true };
|
|
}
|
|
|
|
export function buildPayload(
|
|
customer: CustomerInfo,
|
|
items: CartItem[]
|
|
): WebhookPayload {
|
|
return {
|
|
source: 'online_shop',
|
|
request_type: 'quote_request',
|
|
quote_request_id: generateUUID(),
|
|
customer: {
|
|
name: customer.name.trim(),
|
|
email: customer.email.trim(),
|
|
phone: customer.phone?.trim() || '',
|
|
company: customer.company?.trim() || '',
|
|
},
|
|
cart: {
|
|
cart_id: generateUUID(),
|
|
currency: 'ZAR',
|
|
subtotal: 0,
|
|
items: items.map((item) => ({
|
|
sku: item.product.sku || '',
|
|
description: item.product.name,
|
|
quantity: item.quantity || 1,
|
|
unit: 'each',
|
|
})),
|
|
},
|
|
metadata: {
|
|
website: 'Q - PORTAL',
|
|
submitted_at: new Date().toISOString(),
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function sendQuoteWebhook(
|
|
customer: CustomerInfo,
|
|
items: CartItem[]
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
const validation = validateQuoteRequest(customer, items);
|
|
if (!validation.valid) {
|
|
return { success: false, error: validation.error };
|
|
}
|
|
|
|
const payload = buildPayload(customer, items);
|
|
|
|
console.log('📤 Webhook request payload:', JSON.stringify(payload, null, 2));
|
|
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
|
|
try {
|
|
const response = await fetch(WEBHOOK_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
signal: controller.signal,
|
|
});
|
|
|
|
const responseBody = await response.text();
|
|
console.log('📥 Webhook response status:', response.status);
|
|
console.log('📥 Webhook response body:', responseBody);
|
|
|
|
if (!response.ok) {
|
|
return {
|
|
success: false,
|
|
error: `Webhook returned status ${response.status}. Please try again.`,
|
|
};
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (err: unknown) {
|
|
const message =
|
|
err instanceof DOMException && err.name === 'AbortError'
|
|
? 'Request timed out after 10 seconds. Please try again.'
|
|
: err instanceof Error
|
|
? err.message
|
|
: 'Unknown error';
|
|
console.error('❌ Webhook error:', message);
|
|
return { success: false, error: message };
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|