/** * Checkout Analytics - Multi-step checkout funnel tracking * * Tracks the entire checkout process from cart to purchase confirmation. */ import type { AnalyticsClient } from '@analytics/client'; import type { Cart, CartItem } from './cart-analytics'; // ───────────────────────────────────────────────────────────────────────────── // Types // ───────────────────────────────────────────────────────────────────────────── export type CheckoutStep = | 'cart_review' | 'shipping_info' | 'shipping_method' | 'payment_info' | 'review_order' | 'purchase_complete'; export interface ShippingInfo { country: string; region?: string; city?: string; postalCode?: string; } export interface ShippingMethod { id: string; name: string; price: number; estimatedDays?: number; } export interface PaymentMethod { type: 'card' | 'paypal' | 'apple_pay' | 'google_pay' | 'bank_transfer' | string; lastFour?: string; } export interface Order { id: string; items: CartItem[]; subtotal: number; shipping: number; tax: number; total: number; currency: string; paymentMethod: PaymentMethod; shippingMethod: ShippingMethod; } // ───────────────────────────────────────────────────────────────────────────── // Step Tracking // ───────────────────────────────────────────────────────────────────────────── const STEP_ORDER: CheckoutStep[] = [ 'cart_review', 'shipping_info', 'shipping_method', 'payment_info', 'review_order', 'purchase_complete', ]; /** * Track checkout step completion. */ export function trackCheckoutStep( client: AnalyticsClient, step: CheckoutStep, cart: Cart, metadata?: Record, ): void { const stepIndex = STEP_ORDER.indexOf(step); client.trackEngagement({ type: 'ecommerce', action: 'checkout_step', metadata: { step, stepNumber: stepIndex + 1, totalSteps: STEP_ORDER.length, funnelId: 'checkout', cartValue: cart.subtotal, itemCount: cart.items.length, currency: cart.currency, ...metadata, }, }); } /** * Track when user enters shipping information. */ export function trackShippingInfo( client: AnalyticsClient, shipping: ShippingInfo, cart: Cart, ): void { trackCheckoutStep(client, 'shipping_info', cart, { country: shipping.country, region: shipping.region, // Don't include city/postalCode for privacy }); } /** * Track shipping method selection. */ export function trackShippingMethodSelect( client: AnalyticsClient, method: ShippingMethod, cart: Cart, ): void { trackCheckoutStep(client, 'shipping_method', cart, { shippingMethodId: method.id, shippingMethodName: method.name, shippingCost: method.price, estimatedDays: method.estimatedDays, }); } /** * Track payment method selection. */ export function trackPaymentMethodSelect( client: AnalyticsClient, method: PaymentMethod, cart: Cart, ): void { trackCheckoutStep(client, 'payment_info', cart, { paymentType: method.type, // Never include card details, only type }); } // ───────────────────────────────────────────────────────────────────────────── // Purchase Tracking // ───────────────────────────────────────────────────────────────────────────── /** * Track successful purchase. * * This is the primary conversion event for e-commerce. */ export function trackPurchase(client: AnalyticsClient, order: Order): void { client.trackEngagement({ type: 'ecommerce', action: 'purchase', metadata: { orderId: order.id, subtotal: order.subtotal, shipping: order.shipping, tax: order.tax, total: order.total, currency: order.currency, itemCount: order.items.length, paymentType: order.paymentMethod.type, shippingMethod: order.shippingMethod.name, // Key conversion metrics isConversion: true, conversionValue: order.total, funnelId: 'checkout', funnelStep: 'purchase_complete', // Product breakdown products: order.items.map((item) => ({ productId: item.id, productName: item.name, quantity: item.quantity, price: item.price, itemTotal: item.price * item.quantity, })), }, }); } /** * Track checkout abandonment. * * Call when user leaves checkout without completing purchase. */ export function trackCheckoutAbandonment( client: AnalyticsClient, lastStep: CheckoutStep, cart: Cart, reason?: 'navigation' | 'error' | 'timeout' | 'browser_close', ): void { const stepIndex = STEP_ORDER.indexOf(lastStep); client.trackEngagement({ type: 'ecommerce', action: 'checkout_abandonment', metadata: { lastStep, stepNumber: stepIndex + 1, totalSteps: STEP_ORDER.length, funnelId: 'checkout', abandonedValue: cart.subtotal, itemCount: cart.items.length, currency: cart.currency, reason, products: cart.items.map((item) => ({ productId: item.id, quantity: item.quantity, })), }, }); } /** * Track payment failure. */ export function trackPaymentFailure( client: AnalyticsClient, cart: Cart, errorType: 'declined' | 'invalid' | 'network' | 'fraud' | string, paymentMethod: PaymentMethod, ): void { client.trackEngagement({ type: 'ecommerce', action: 'payment_failure', metadata: { errorType, paymentType: paymentMethod.type, cartValue: cart.subtotal, currency: cart.currency, itemCount: cart.items.length, }, }); } // ───────────────────────────────────────────────────────────────────────────── // React Hook // ───────────────────────────────────────────────────────────────────────────── /** * Hook for checkout funnel tracking. * * @example * ```tsx * function CheckoutPage() { * const { trackStep, trackPurchase, trackAbandonment } = useCheckoutFunnel(cart); * * return ( * trackStep(step)} * onPurchase={(order) => trackPurchase(order)} * /> * ); * } * ``` */ export function createCheckoutTracker(client: AnalyticsClient, cart: Cart) { return { trackStep: (step: CheckoutStep, metadata?: Record) => { trackCheckoutStep(client, step, cart, metadata); }, trackShipping: (info: ShippingInfo) => { trackShippingInfo(client, info, cart); }, trackShippingMethod: (method: ShippingMethod) => { trackShippingMethodSelect(client, method, cart); }, trackPaymentMethod: (method: PaymentMethod) => { trackPaymentMethodSelect(client, method, cart); }, trackPurchase: (order: Order) => { trackPurchase(client, order); }, trackAbandonment: ( lastStep: CheckoutStep, reason?: 'navigation' | 'error' | 'timeout' | 'browser_close', ) => { trackCheckoutAbandonment(client, lastStep, cart, reason); }, trackPaymentError: ( errorType: 'declined' | 'invalid' | 'network' | 'fraud' | string, paymentMethod: PaymentMethod, ) => { trackPaymentFailure(client, cart, errorType, paymentMethod); }, }; }