Skip to main content

Plugin Examples

Complete, production-ready examples for common funnel patterns.
Need to initialize a checkout programmatically? Check out the Initialize Checkout guide to learn how to create checkout sessions with line items.

Simple Checkout Funnel

2-Step: Checkout → Thank You

The most basic funnel with order data flow.

Manifest

{
  "name": "Simple Checkout",
  "pluginId": "simple-checkout",
  "version": "1.0.0",
  "mode": "direct-mode",
  "pages": [
    {
      "path": "/checkout",
      "features": [{ "type": "checkout" }]
    },
    {
      "path": "/thankyou/:orderId",
      "features": [{
        "type": "thankyou",
        "requirements": [{
          "resource": "order",
          "params": [{ "name": "id", "type": "path" }]
        }]
      }]
    }
  ]
}

Checkout Page

import { useFunnel, useCheckout, useCurrency } from '@tagadapay/plugin-sdk/v2';

export default function CheckoutPage() {
  const funnel = useFunnel();
  const { data: checkout } = useCheckout();
  const { formatMoney } = useCurrency();

  const handlePayment = async (paymentResult) => {
    await funnel.next({
      type: 'payment_success',
      data: {
        resources: {
          order: {
            id: paymentResult.orderId,
            amount: checkout.total,
            currency: checkout.currency
          }
        }
      }
    });
  };
  
  return <div>{/* Your UI */}</div>;
}

Thank You Page

import { useFunnel } from '@tagadapay/plugin-sdk/v2';

export default function ThankYouPage() {
  const funnel = useFunnel();
  const order = funnel.context?.resources?.order;

  return (
    <div>
      <h1>Thank You!</h1>
      <p>Order #{order.id} confirmed</p>
    </div>
  );
}

Upsell Funnel

3-Step: Checkout → Upsell → Thank You

Track multiple orders with named resources.

Manifest

{
  "pages": [
    {
      "path": "/checkout",
      "features": [{ "type": "checkout" }]
    },
    {
      "path": "/upsell/:orderId",
      "features": [{
        "type": "upsell",
        "requirements": [{
          "resource": "order",
          "params": [{ "name": "id", "type": "path" }]
        }]
      }]
    },
    {
      "path": "/thankyou",
      "features": [{ "type": "thankyou" }]
    }
  ]
}

Checkout Page

export default function CheckoutPage() {
  const funnel = useFunnel();
  
  const handlePayment = async (result) => {
    await funnel.next({
      type: 'payment_success',
      data: {
        resources: {
          // Generic (for next step)
          order: { id: result.orderId, amount: 100 },
          // Named (persists through funnel)
          mainOrder: { id: result.orderId, amount: 100 },
          customer: { id: result.customerId, email: result.email }
        }
      }
    });
  };
  
  return <div>{/* Checkout UI */}</div>;
}

Upsell Page

export default function UpsellPage() {
  const funnel = useFunnel();
  
  // Access main order
  const mainOrder = funnel.context?.resources?.mainOrder;

  const handleAccept = async () => {
    await funnel.next({
      type: 'offer_accepted',
      data: {
        resources: {
          // New order (overwrites generic)
          order: { id: 'upsell_123', amount: 50 },
          // Named upsell order
          upsellOrder: { id: 'upsell_123', amount: 50 }
        }
      }
    });
  };

  const handleDecline = async () => {
    await funnel.next({
      type: 'offer_declined'
    });
  };

  return (
    <div>
      <h1>Special Offer!</h1>
      <p>You ordered: {mainOrder.id}</p>
      <button onClick={handleAccept}>Accept $50 Upsell</button>
      <button onClick={handleDecline}>No Thanks</button>
    </div>
  );
}

Thank You Page

export default function ThankYouPage() {
  const funnel = useFunnel();
  
  // Access both orders
  const mainOrder = funnel.context?.resources?.mainOrder;
  const upsellOrder = funnel.context?.resources?.upsellOrder;
  
  const totalRevenue = (mainOrder?.amount || 0) + (upsellOrder?.amount || 0);

  return (
    <div>
      <h1>Thank You!</h1>
      <p>Main Order: ${mainOrder.amount}</p>
      {upsellOrder && <p>Upsell: ${upsellOrder.amount}</p>}
      <p>Total: ${totalRevenue}</p>
    </div>
  );
}

Multi-Offer Funnel

4-Step: Checkout → Upsell 1 → Upsell 2 → Thank You

Use collections for multiple offers.

Checkout Page

export default function CheckoutPage() {
  const funnel = useFunnel();
  
  const handlePayment = async (result) => {
    await funnel.next({
      type: 'payment_success',
      data: {
        resources: {
          order: { id: result.orderId, amount: 100, type: 'main' },
          mainOrder: { id: result.orderId, amount: 100, type: 'main' },
          // Initialize orders collection
          orders: [
            { id: result.orderId, amount: 100, type: 'main' }
          ]
        }
      }
    });
  };

  return <div>{/* Checkout UI */}</div>;
}

Upsell Page 1

export default function Upsell1Page() {
  const funnel = useFunnel();
  const existingOrders = funnel.context?.resources?.orders || [];

  const handleAccept = async () => {
    await funnel.next({
      type: 'offer_accepted',
      data: {
        resources: {
          order: { id: 'up1_123', amount: 50, type: 'upsell1' },
          upsell1Order: { id: 'up1_123', amount: 50, type: 'upsell1' },
          // Add to collection
          orders: [
            ...existingOrders,
            { id: 'up1_123', amount: 50, type: 'upsell1' }
      ]
        }
      }
    });
  };

  return <div>{/* Upsell 1 UI */}</div>;
}

Upsell Page 2

export default function Upsell2Page() {
  const funnel = useFunnel();
  const existingOrders = funnel.context?.resources?.orders || [];
  
  const handleAccept = async () => {
    await funnel.next({
      type: 'offer_accepted',
      data: {
        resources: {
          order: { id: 'up2_456', amount: 30, type: 'upsell2' },
          upsell2Order: { id: 'up2_456', amount: 30, type: 'upsell2' },
          // Add to collection
          orders: [
            ...existingOrders,
            { id: 'up2_456', amount: 30, type: 'upsell2' }
          ]
        }
      }
    });
  };
  
  return <div>{/* Upsell 2 UI */}</div>;
}

Thank You Page

export default function ThankYouPage() {
  const funnel = useFunnel();
  const orders = funnel.context?.resources?.orders || [];
  
  const totalRevenue = orders.reduce((sum, order) => sum + order.amount, 0);

  return (
    <div>
      <h1>Thank You!</h1>
      <h2>Your Orders:</h2>
      {orders.map(order => (
        <div key={order.id}>
          <p>{order.type}: ${order.amount}</p>
        </div>
      ))}
      <h3>Total Revenue: ${totalRevenue}</h3>
    </div>
  );
}

Conditional Funnel

Dynamic: Checkout → [Upsell OR Thank You]

Navigate to different steps based on conditions.

Checkout Page

export default function CheckoutPage() {
  const funnel = useFunnel();
  const { data: checkout } = useCheckout();
  
  const handlePayment = async (result) => {
    const isHighValue = checkout.total > 100;
    
    await funnel.next({
      // Event type determines next step (configured in CRM)
      type: isHighValue ? 'high_value_purchase' : 'standard_purchase',
      data: {
        resources: {
          order: { id: result.orderId, amount: checkout.total },
          customer: { id: result.customerId }
        }
      }
    });
  };
  
  return <div>{/* Checkout UI */}</div>;
}
The CRM’s funnel configuration determines which page loads next based on the event type. High-value purchases go to upsell, standard purchases go straight to thank you.

Form Wizard

Multi-Step Form: Step 1 → Step 2 → Step 3 → Submit

Collect data across multiple pages.

Step 1: Personal Info

export default function Step1() {
  const funnel = useFunnel();
  const [formData, setFormData] = useState({ name: '', email: '' });
  
  const handleNext = async () => {
    await funnel.next({
      type: 'step_completed',
      data: {
        resources: {
          formData: {
            id: 'form_1',
            step1: formData
          }
        }
      }
    });
  };
  
  return <div>{/* Form fields */}</div>;
}

Step 2: Address

export default function Step2() {
  const funnel = useFunnel();
  const existingData = funnel.context?.resources?.formData;
  const [address, setAddress] = useState({ street: '', city: '' });
  
  const handleNext = async () => {
    await funnel.next({
      type: 'step_completed',
      data: {
        resources: {
          formData: {
            id: 'form_1',
            ...existingData,
            step2: address
          }
        }
      }
    });
  };
  
  return <div>{/* Address fields */}</div>;
}

Step 3: Review & Submit

export default function Step3() {
  const funnel = useFunnel();
  const formData = funnel.context?.resources?.formData;
  
  const handleSubmit = async () => {
    // Submit to API
    const result = await api.post('/submit', formData);
    
    await funnel.next({
      type: 'form_submitted',
      data: {
        resources: {
          submission: {
            id: result.id,
            status: 'completed'
          }
        }
      }
    });
  };

  return (
    <div>
      <h1>Review Your Information</h1>
      <p>Name: {formData.step1.name}</p>
      <p>Email: {formData.step1.email}</p>
      <p>Address: {formData.step2.street}, {formData.step2.city}</p>
      <button onClick={handleSubmit}>Submit</button>
    </div>
  );
}

Error Handling

Handle Payment Failures

export default function CheckoutPage() {
  const funnel = useFunnel();
  const [error, setError] = useState(null);
  
  const handlePayment = async (paymentData) => {
    try {
      const result = await processPayment(paymentData);
      
      if (result.success) {
        await funnel.next({
          type: 'payment_success',
          data: {
            resources: {
              order: { id: result.orderId }
            }
          }
        });
      } else {
        // Stay on page, show error
        setError(result.error);
}
    } catch (error) {
      setError('Payment failed. Please try again.');
      
      // Optionally track failure
      await funnel.next({
        type: 'payment_failed',
        data: {
          resources: {
            error: {
              id: 'err_' + Date.now(),
              message: error.message
            }
          }
        }
      });
    }
  };

  return (
    <div>
      {error && <div className="error">{error}</div>}
      {/* Checkout form */}
    </div>
  );
}

TypeScript Support

Typed Resources

import { FunnelResourceData } from '@tagadapay/plugin-sdk/v2';

interface Order extends FunnelResourceData {
  amount: number;
  currency: string;
  items: OrderItem[];
}

interface Customer extends FunnelResourceData {
  email: string;
  name: string;
}

interface MyResources {
  order?: Order;
  customer?: Customer;
  mainOrder?: Order;
  upsellOrder?: Order;
}

// Use with funnel
export default function CheckoutPage() {
  const funnel = useFunnel<MyResources>();
  
  // Full type safety!
  const order = funnel.context?.resources?.order;
  const amount = order?.amount; // TypeScript knows this is a number
  
  return <div>{/* ... */}</div>;
}

Best Practices

// ✅ Good - Both generic and named
resources: {
  order: { id: 'ord_123' },      // Hot context
  mainOrder: { id: 'ord_123' }   // Persistent
}

// ❌ Bad - Only generic (can be lost)
resources: {
  order: { id: 'ord_123' }
}
// ✅ Good
resources: {
  order: { id: 'ord_123', amount: 100 }
}

// ❌ Bad
resources: {
  order: { amount: 100 } // No ID!
}
// ✅ Good
resources: {
  orders: [
    { id: 'ord_1', amount: 100 },
    { id: 'ord_2', amount: 50 }
  ]
}

// ❌ Bad
resources: {
  order1: { id: 'ord_1' },
  order2: { id: 'ord_2' }
}
// ✅ Good
const { data, isLoading } = useCheckout();
if (isLoading) return <Spinner />;

// ❌ Bad
const { data } = useCheckout();
return <div>{data.total}</div>; // Crash if loading!

Next Steps