analytics/examples/ecommerce/checkout-analytics.ts

292 lines
8.2 KiB
TypeScript
Raw Permalink Normal View History

/**
* 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<string, unknown>,
): 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 (
* <CheckoutWizard
* onStepComplete={(step) => trackStep(step)}
* onPurchase={(order) => trackPurchase(order)}
* />
* );
* }
* ```
*/
export function createCheckoutTracker(client: AnalyticsClient, cart: Cart) {
return {
trackStep: (step: CheckoutStep, metadata?: Record<string, unknown>) => {
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);
},
};
}