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
Copy
{
"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
Copy
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
Copy
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
Copy
{
"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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Always Use Named Resources for Important Data
Always Use Named Resources for Important Data
Copy
// ✅ 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' }
}
Include IDs in All Resources
Include IDs in All Resources
Copy
// ✅ Good
resources: {
order: { id: 'ord_123', amount: 100 }
}
// ❌ Bad
resources: {
order: { amount: 100 } // No ID!
}
Use Collections for Multiple Items
Use Collections for Multiple Items
Copy
// ✅ Good
resources: {
orders: [
{ id: 'ord_1', amount: 100 },
{ id: 'ord_2', amount: 50 }
]
}
// ❌ Bad
resources: {
order1: { id: 'ord_1' },
order2: { id: 'ord_2' }
}
Handle Loading States
Handle Loading States
Copy
// ✅ Good
const { data, isLoading } = useCheckout();
if (isLoading) return <Spinner />;
// ❌ Bad
const { data } = useCheckout();
return <div>{data.total}</div>; // Crash if loading!
