> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tagada.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Build Your First Funnel

> Create a complete checkout funnel in 20 minutes - step by step

# Build Your First Checkout Funnel

Create a complete 3-step funnel: **Checkout → Upsell → Thank You**. Learn how data flows automatically between steps!

<Info>
  **What you'll learn:**

  * ✅ Setup a React plugin project
  * ✅ Create multiple pages
  * ✅ Pass data between pages
  * ✅ Use static resources (for upsells/offers)
  * ✅ Deploy to TagadaPay

  **Related Guides:**

  * [Initialize Checkout Sessions](/developer-tools/sdk/init-checkout-example) - Create checkouts programmatically
</Info>

<Tip>
  **Prefer to learn by reading real code?** We maintain **five open-source plugin examples** on GitHub —
  complete checkouts with thank-you pages in five different visual styles (editorial, neon, luxe, solar, arcade),
  all MIT-licensed and ready to fork.

  👉 **[Browse the Open-Source Examples](/developer-tools/sdk/open-source-examples)** or jump straight to
  [github.com/TagadaPay/plugins](https://github.com/TagadaPay/plugins).
</Tip>

**Time**: \~20 minutes | **Difficulty**: Beginner

***

## 🎯 What You'll Build

A 3-page checkout funnel:

1. **Checkout Page** - Collect payment → Create order
2. **Upsell Page** - Show special offer using static resources
3. **Thank You Page** - Display order confirmation

**Data Flow**:

```
Checkout → (order data) → Upsell → (order + upsell) → Thank You
```

***

## Step 1: Create Your Project

```bash theme={null}
# Create project
npm create vite@latest my-checkout -- --template react-ts
cd my-checkout

# Install SDK
npm install @tagadapay/plugin-sdk wouter

# Install dependencies
npm install
```

***

## Step 2: Create Plugin Manifest

The manifest tells TagadaPay about your plugin's pages and what data they need.

Create `plugin.manifest.json` in your project root:

```json theme={null}
{
  "name": "My First Checkout",
  "pluginId": "my-first-checkout",
  "version": "1.0.0",
  "description": "A simple checkout with upsell",
  "mode": "direct-mode",
  "category": "checkout",
  "tags": ["checkout", "upsell"],
  "configuration": {
    "schema": "./config/schema.json",
    "uiSchema": "./config/ui-schema.json",
    "presets": [
      {
        "id": "default",
        "name": "Default",
        "description": "Default configuration",
        "config": "./config/default.config.json"
      }
    ]
  },
  "pages": [
    {
      "path": "/checkout",
      "features": [
        {
          "type": "checkout",
          "requirements": [
            {
              "resource": "checkoutSession",
              "from": [
                {
                  "name": "checkoutToken",
                  "type": "query"
                }
              ]
            }
          ]
        }
      ],
      "remappable": true
    },
    {
      "path": "/upsell",
      "features": [
        {
          "type": "offer",
          "requirements": [
            {
              "resource": "order",
              "from": [
                {
                  "name": "id",
                  "type": "context"
                }
              ]
            },
            {
              "resource": "offer",
              "from": [
                {
                  "name": "id",
                  "type": "static"
                }
              ]
            }
          ]
        }
      ],
      "remappable": true
    },
    {
      "path": "/thankyou/:orderId",
      "features": [
        {
          "type": "thankyou",
          "requirements": [
            {
              "resource": "order",
              "from": [
                {
                  "name": "id",
                  "type": "path"
                }
              ]
            }
          ]
        }
      ],
      "remappable": true
    }
  ],
  "router": {
    "basePath": "/",
    "matcher": ".*",
    "excluder": null
  },
  "requirements": {
    "sdk": "^2.2.0",
    "sdkVersion": "v2"
  }
}
```

<AccordionGroup>
  <Accordion title="📖 Understanding the Manifest" icon="book">
    **Basic Info:**

    * `name`, `pluginId`, `version` - Identify your plugin
    * `mode: "direct-mode"` - Plugin runs directly (no iframe)
    * `category` - Helps organize in marketplace

    **Pages Array:**
    Each page defines:

    * `path` - The URL path (e.g., `/checkout`, `/upsell/:id`)
    * `features` - What type of page it is (`checkout`, `offer`, `thankyou`)
    * `requirements` - What data this page needs
    * `remappable` - Allows custom URLs (highly recommended!)

    **Requirements Explained:**

    ```json theme={null}
    {
      "resource": "order",           // What resource you need
      "from": [                       // Where to get it from
        {
          "name": "id",               // The field name
          "type": "context"           // Source type
        }
      ]
    }
    ```

    **Resource Types:**

    * `query` - From URL query params (`?checkoutToken=abc`)
    * `path` - From URL path (`/thankyou/:orderId`)
    * `context` - From previous step's funnel context
    * `static` - From CRM configuration (offers, products, etc.)
  </Accordion>

  <Accordion title="🎯 Static Resources Explained" icon="bullseye">
    **What are Static Resources?**

    Static resources are IDs configured by merchants in the CRM. They let you build flexible plugins where merchants choose which products/offers to show **without changing your code**.

    **Example Use Case:**
    You build an upsell page. Different merchants want to show different products. Instead of hardcoding product IDs, you use a static resource!

    **In Manifest:**

    ```json theme={null}
    {
      "resource": "offer",        // Name it "offer"
      "from": [{
        "name": "id",             // It's an ID
        "type": "static"          // From CRM config
      }]
    }
    ```

    **In Your Code:**

    ```tsx theme={null}
    const { stepConfig } = useFunnel();

    // Recommended: stepConfig.staticResources (per-variant, supports A/B testing)
    // Fallback: context.static (legacy, backend-provided)
    const offerId = stepConfig.staticResources?.offer
      || funnel.context?.static?.offer?.id;
    ```

    **For Local Testing:**
    Create `config/resources.static.json`:

    ```json theme={null}
    {
      "offer": {
        "id": "offer_test123"
      }
    }
    ```

    The SDK automatically loads this file in local development!
  </Accordion>

  <Accordion title="🔄 Configuration & Presets" icon="gear">
    **Configuration lets merchants customize your plugin without code changes.**

    **Structure:**

    * `schema.json` - Defines what settings are available
    * `uiSchema.json` - How settings appear in CRM UI
    * `presets` - Pre-made configurations merchants can choose

    **Example Preset:**

    ```json theme={null}
    {
      "id": "premium",
      "name": "Premium Theme",
      "description": "High-end checkout design",
      "config": "./config/premium.config.json"
    }
    ```

    Each config file should have metadata:

    ```json theme={null}
    {
      "meta": {
        "id": "premium",
        "name": "Premium Theme",
        "description": "High-end checkout design"
      },
      "branding": {
        "primaryColor": "#000000",
        "companyName": "Premium Store"
      }
    }
    ```

    Access in code with `usePluginConfig()` hook!
  </Accordion>
</AccordionGroup>

***

## Step 3: Setup Provider

The `TagadaProvider` wraps your app and provides all functionality.

Create `src/main.tsx`:

```tsx theme={null}
import React from 'react';
import ReactDOM from 'react-dom/client';
import { TagadaProvider } from '@tagadapay/plugin-sdk/v2';
import App from './App';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <TagadaProvider>
      <App />
    </TagadaProvider>
  </React.StrictMode>
);
```

<Tip>
  **That's it!** `TagadaProvider` automatically:

  * ✅ Creates a session
  * ✅ Initializes the funnel
  * ✅ Enables debug mode (local dev)
  * ✅ Handles redirects

  No manual initialization needed!
</Tip>

***

## Step 4: Build Checkout Page

Create `src/pages/CheckoutPage.tsx`:

```tsx theme={null}
import { useState } from 'react';
import { useFunnel, useCheckout, useCurrency } from '@tagadapay/plugin-sdk/v2';

export default function CheckoutPage() {
  const funnel = useFunnel();
  const { data: checkout, isLoading } = useCheckout();
  const { formatMoney } = useCurrency();
  
  const [email, setEmail] = useState('');
  const [name, setName] = useState('');
  
  const handlePayment = async (e: React.FormEvent) => {
    e.preventDefault();
    
    // Create mock order
    const order = {
      id: `ord_${Date.now()}`,
      amount: checkout?.total || 4900,
      currency: checkout?.currency || 'USD',
      items: checkout?.lineItems || []
    };

    const customer = {
      id: `cus_${Date.now()}`,
      email,
      name
    };

    // Navigate to upsell page with order data
    await funnel.next({
      type: 'payment_success',
      data: {
        resources: {
          order,           // ← Order data flows to next page
          customer         // ← Customer data flows too
        }
      }
    });
    
    // SDK auto-redirects to upsell page!
  };
  
  if (isLoading || !funnel.context) {
    return <div className="loading">Loading checkout...</div>;
  }
  
  return (
    <div className="checkout-page">
      <h1>Checkout</h1>
      
      {checkout && (
        <div className="order-summary">
          <h2>Order Summary</h2>
          <div className="line-items">
            {checkout.lineItems?.map((item: { id: string; quantity: number; title: string; price: number }) => (
              <div key={item.id} className="line-item">
                <span>{item.quantity}x {item.title}</span>
                <span>{formatMoney(item.price * item.quantity, checkout.currency)}</span>
              </div>
            ))}
          </div>
          <div className="total">
            <strong>Total:</strong> {formatMoney(checkout.total, checkout.currency)}
          </div>
        </div>
      )}
      
      <form onSubmit={handlePayment} className="checkout-form">
        <div className="form-group">
          <label htmlFor="email">Email</label>
          <input
            id="email"
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="your@email.com"
            required
          />
        </div>
        
        <div className="form-group">
          <label htmlFor="name">Full Name</label>
          <input
            id="name"
            type="text"
            value={name}
            onChange={(e) => setName(e.target.value)}
            placeholder="John Doe"
            required
          />
        </div>
        
        <div className="form-group">
          <label htmlFor="card">Card Number (Test: 4242...)</label>
          <input
            id="card"
            type="text"
            defaultValue="4242424242424242"
            placeholder="4242 4242 4242 4242"
            required
          />
        </div>
        
        <button type="submit" className="btn-primary">
          Pay {formatMoney(checkout?.total || 4900, checkout?.currency || 'USD')}
        </button>
      </form>
    </div>
  );
}
```

***

## Step 5: Build Upsell Page (Using Static Resources!)

This page shows an offer. The merchant configures which offer in the CRM using **static resources**.

Create `src/pages/UpsellPage.tsx`:

```tsx theme={null}
import { useFunnel, usePreviewOffer, useCurrency } from '@tagadapay/plugin-sdk/v2';

export default function UpsellPage() {
  const funnel = useFunnel();
  const { formatMoney } = useCurrency();

  // 🎯 Get offer ID from STATIC resource (configured by merchant)
  // stepConfig.resources is the current API; stepConfig.staticResources is
  // a legacy alias kept for backwards compatibility.
  const { stepConfig } = funnel;
  const offerId =
    stepConfig.resources?.offer
    || stepConfig.staticResources?.offer
    || funnel.context?.static?.offer?.id;

  // 📦 Get order from PREVIOUS page (from context)
  const order = funnel.context?.resources?.order;
  const customer = funnel.context?.resources?.customer;

  // usePreviewOffer is the recommended hook for post-checkout upsell pages.
  // It fetches the offer metadata + a precomputed pricing summary, lets the
  // user pick variants/quantities locally, and exposes pay() to charge the
  // customer's stored card. The MIT/CIT payment initiator configured per-step
  // in the CRM is auto-applied — no extra wiring required.
  const { offer, summary, pay, isPaying } = usePreviewOffer({
    offerId: offerId ?? '',
  });

  const handleAccept = async () => {
    // 1. Charge the stored card (off-session by default — MIT)
    await pay(order?.id);

    // 2. Advance the funnel
    await funnel.next({
      type: 'offer_accepted',
      data: {
        resources: {
          order: {
            ...order,
            upsell: {
              id: offer?.id,
              amount: summary?.totalAdjustedAmount,
            },
          },
          customer,
        },
      },
    });
  };

  const handleDecline = async () => {
    await funnel.next({
      type: 'offer_declined',
      data: { resources: { order, customer } },
    });
  };

  if (!funnel.context || !offerId) {
    return <div className="loading">Loading...</div>;
  }

  if (!offer || !summary) {
    // No offer configured — skip to thank you
    handleDecline();
    return <div className="loading">Loading...</div>;
  }

  const headlineItem = summary.items[0];

  return (
    <div className="upsell-page">
      <h1>🎉 Special Offer Just For You!</h1>

      <div className="offer-card">
        {headlineItem?.imageUrl && <img src={headlineItem.imageUrl} alt={headlineItem.name} />}
        <h2>{headlineItem?.name ?? offer.name}</h2>
        {headlineItem?.description && <p>{headlineItem.description}</p>}

        <div className="pricing">
          <div className="price">
            {summary.totalAmount > summary.totalAdjustedAmount && (
              <span className="original">{formatMoney(summary.totalAmount, summary.currency)}</span>
            )}
            <span className="sale">{formatMoney(summary.totalAdjustedAmount, summary.currency)}</span>
          </div>
          {summary.totalAmount > summary.totalAdjustedAmount && (
            <span className="savings">
              Save {formatMoney(summary.totalAmount - summary.totalAdjustedAmount, summary.currency)}!
            </span>
          )}
        </div>

        <button onClick={handleAccept} disabled={isPaying} className="btn-primary">
          {isPaying ? 'Processing…' : 'Yes! Add To My Order'}
        </button>
        <button onClick={handleDecline} disabled={isPaying} className="btn-secondary">
          No Thanks, Continue
        </button>
      </div>

      <div className="order-info">
        <p>Your order: {formatMoney(order.amount, order.currency)}</p>
        <p>Customer: {customer?.email}</p>
      </div>
    </div>
  );
}
```

<AccordionGroup>
  <Accordion title="🎯 How Static Resources Work" icon="question">
    **Step by Step:**

    1. **In Manifest** - Declare you need an "offer" resource:

    ```json theme={null}
    {
      "resource": "offer",
      "from": [{ "name": "id", "type": "static" }]
    }
    ```

    2. **In Code** - Access it via `stepConfig` (recommended) or `context.static` (fallback):

    ```tsx theme={null}
    const { stepConfig } = useFunnel();
    const offerId = stepConfig.staticResources?.offer
      || funnel.context?.static?.offer?.id;
    ```

    3. **Merchant Configures** - In CRM, they select which offer to show:

    ```
    Funnel Step → Upsell Page → Static Resources → Offer → (select from dropdown)
    ```

    4. **For Local Testing** - Create `config/resources.static.json`:

    ```json theme={null}
    {
      "offer": {
        "id": "offer_test123"
      }
    }
    ```

    **Why This is Powerful:**

    * Same plugin code works for all merchants
    * Merchants customize without touching code
    * Easy A/B testing (change offer ID in CRM)
    * No redeployment needed to change offers
  </Accordion>

  <Accordion title="📦 Context vs Static Resources" icon="box">
    **Context Resources** (from previous pages):

    ```tsx theme={null}
    // Data passed from checkout page
    const order = funnel.context?.resources?.order;
    const customer = funnel.context?.resources?.customer;
    ```

    * ✅ Dynamic data from previous steps
    * ✅ Changes per user/session
    * ✅ Example: order details, customer info

    **Static Resources** (configured in CRM):

    ```tsx theme={null}
    // Configured by merchant in CRM
    const offerId = funnel.context?.static?.offer?.id;
    ```

    * ✅ Fixed per funnel configuration
    * ✅ Same for all users
    * ✅ Example: offer IDs, product IDs, variant IDs
  </Accordion>
</AccordionGroup>

***

## Step 6: Build Thank You Page

Create `src/pages/ThankYouPage.tsx`:

```tsx theme={null}
import { useFunnel, useCurrency } from '@tagadapay/plugin-sdk/v2';
import { useParams } from 'wouter';

export default function ThankYouPage() {
  const { orderId } = useParams<{ orderId: string }>();
  const funnel = useFunnel();
  const { formatMoney } = useCurrency();
  
  // Get data from previous pages
  const order = funnel.context?.resources?.order;
  const customer = funnel.context?.resources?.customer;
  const upsell = order?.upsell;
  
  if (!funnel.context || !order) {
    return <div className="loading">Loading...</div>;
  }
  
  const totalAmount = order.amount + (upsell?.amount || 0);
  
  return (
    <div className="thankyou-page">
      <div className="success-icon">✅</div>
      <h1>Thank You for Your Purchase!</h1>
      <p>Order #{order.id}</p>
      
      <div className="order-details">
        <h2>Order Summary</h2>
        
        <div className="order-line">
          <span>Original Order</span>
          <span>{formatMoney(order.amount, order.currency)}</span>
        </div>
        
        {upsell && (
          <div className="order-line upsell">
            <span>✨ Special Offer</span>
            <span>{formatMoney(upsell.amount, order.currency)}</span>
          </div>
        )}
        
        <div className="order-line total">
          <strong>Total Paid</strong>
          <strong>{formatMoney(totalAmount, order.currency)}</strong>
        </div>
      </div>
      
      <div className="customer-info">
        <p><strong>Email:</strong> {customer?.email}</p>
        <p><strong>Name:</strong> {customer?.name}</p>
      </div>
      
      <p className="confirmation">
        A confirmation email has been sent to {customer?.email}
      </p>
      
      <button onClick={() => window.location.href = '/'} className="btn-primary">
        Continue Shopping
      </button>
    </div>
  );
}
```

***

## Step 7: Setup Routing

Create `src/App.tsx`:

```tsx theme={null}
import { Route, Switch } from 'wouter';
import CheckoutPage from './pages/CheckoutPage';
import UpsellPage from './pages/UpsellPage';
import ThankYouPage from './pages/ThankYouPage';

export default function App() {
  return (
    <Switch>
      <Route path="/checkout" component={CheckoutPage} />
      <Route path="/upsell" component={UpsellPage} />
      <Route path="/thankyou/:orderId" component={ThankYouPage} />
      <Route path="/" component={CheckoutPage} />
    </Switch>
  );
}
```

***

## Step 8: Create Config for Local Testing

Create `config/resources.static.json`:

```json theme={null}
{
  "offer": {
    "id": "offer_test123"
  }
}
```

<Info>
  **What this does:**
  When developing locally, the SDK automatically loads this file. This lets you test your upsell page without configuring anything in the CRM!

  The SDK detects local environment and loads static resources from this file. In production, resources come from the CRM configuration.
</Info>

***

## Step 9: Add Basic Styles

Create `src/index.css`:

```css theme={null}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: system-ui, -apple-system, sans-serif;
  line-height: 1.6;
  color: #333;
}

.checkout-page,
.upsell-page,
.thankyou-page {
  max-width: 600px;
  margin: 0 auto;
  padding: 2rem;
}

h1 {
  font-size: 2rem;
  margin-bottom: 1rem;
}

h2 {
  font-size: 1.5rem;
  margin-bottom: 0.75rem;
}

/* Order Summary */
.order-summary {
  background: #f5f5f5;
  padding: 1.5rem;
  border-radius: 8px;
  margin: 2rem 0;
}

.line-items {
  margin: 1rem 0;
}

.line-item {
  display: flex;
  justify-content: space-between;
  padding: 0.5rem 0;
  border-bottom: 1px solid #ddd;
}

.total {
  display: flex;
  justify-content: space-between;
  padding-top: 1rem;
  font-size: 1.25rem;
}

/* Forms */
.checkout-form {
  margin-top: 2rem;
}

.form-group {
  margin-bottom: 1.5rem;
}

.form-group label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 500;
}

.form-group input {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #ddd;
  border-radius: 6px;
  font-size: 1rem;
}

.form-group input:focus {
  outline: none;
  border-color: #0070f3;
  box-shadow: 0 0 0 3px rgba(0, 112, 243, 0.1);
}

/* Buttons */
.btn-primary,
.btn-secondary {
  width: 100%;
  padding: 1rem;
  border: none;
  border-radius: 6px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  margin-top: 0.5rem;
  transition: all 0.2s;
}

.btn-primary {
  background: #0070f3;
  color: white;
}

.btn-primary:hover {
  background: #0051cc;
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0, 112, 243, 0.3);
}

.btn-secondary {
  background: transparent;
  color: #666;
  border: 1px solid #ddd;
}

.btn-secondary:hover {
  background: #f5f5f5;
}

/* Upsell Page */
.offer-card {
  background: white;
  border: 2px solid #0070f3;
  border-radius: 12px;
  padding: 2rem;
  margin: 2rem 0;
  text-align: center;
  box-shadow: 0 4px 20px rgba(0, 112, 243, 0.1);
}

.offer-card img {
  max-width: 100%;
  border-radius: 8px;
  margin-bottom: 1rem;
}

.pricing {
  margin: 1.5rem 0;
}

.price {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 1rem;
  margin-bottom: 0.5rem;
}

.price .original {
  text-decoration: line-through;
  color: #999;
  font-size: 1.25rem;
}

.price .sale {
  color: #0070f3;
  font-size: 2rem;
  font-weight: bold;
}

.savings {
  display: inline-block;
  background: #10b981;
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 20px;
  font-weight: 600;
}

.order-info {
  text-align: center;
  color: #666;
  font-size: 0.9rem;
  margin-top: 2rem;
  padding-top: 2rem;
  border-top: 1px solid #ddd;
}

/* Thank You Page */
.thankyou-page {
  text-align: center;
}

.success-icon {
  font-size: 4rem;
  margin-bottom: 1rem;
}

.order-details {
  background: #f5f5f5;
  padding: 2rem;
  border-radius: 8px;
  margin: 2rem 0;
  text-align: left;
}

.order-line {
  display: flex;
  justify-content: space-between;
  padding: 0.75rem 0;
  border-bottom: 1px solid #ddd;
}

.order-line.upsell {
  color: #10b981;
  font-weight: 500;
}

.order-line.total {
  border-bottom: none;
  padding-top: 1rem;
  font-size: 1.25rem;
}

.customer-info {
  text-align: left;
  background: #f9fafb;
  padding: 1rem;
  border-radius: 6px;
  margin: 1rem 0;
}

.confirmation {
  color: #666;
  margin: 2rem 0;
}

/* Loading */
.loading {
  text-align: center;
  padding: 4rem;
  color: #666;
}
```

***

## Step 10: Test Locally

```bash theme={null}
# Start development server
npm run dev

# Visit http://localhost:5173/checkout
```

<Steps>
  <Step title="Test Checkout">
    1. Fill in email and name
    2. Use card: `4242424242424242`
    3. Click "Pay"
    4. Should redirect to upsell page ✅
  </Step>

  <Step title="Test Upsell">
    1. Should show your test offer (from `resources.static.json`)
    2. Order info from checkout should be visible
    3. Click "Accept" or "Decline"
    4. Should redirect to thank you page ✅
  </Step>

  <Step title="Test Thank You">
    1. Should show order confirmation
    2. Should show upsell if accepted
    3. Should display customer email
  </Step>
</Steps>

<Tip>
  **Check the Console!**

  The SDK logs helpful debug information:

  * `✅ Funnel Initialized` - Funnel is ready
  * `🚀 Auto-redirecting to:` - Navigation happening
  * Check `funnel.context` to see all available data
</Tip>

***

## Step 11: Deploy to TagadaPay

```bash theme={null}
# Build your plugin
npm run build

# Install CLI
npm install -g @tagadapay/plugin-cli

# Login
tgdcli login

# Deploy
tgdcli deploy
```

Follow the prompts to deploy your plugin!

***

## Step 12: Configure in CRM

1. **Go to Funnels** → Create new funnel
2. **Add Checkout Step** → Select your plugin → `/checkout` page
3. **Add Upsell Step** → Select your plugin → `/upsell` page
   * ⚠️ **Important**: Configure Static Resources:
     * Resource: `offer`
     * Select an offer from the dropdown
4. **Add Thank You Step** → Select your plugin → `/thankyou/:orderId` page
5. **Save & Test!**

<Warning>
  **Don't forget to configure the static resource for the upsell page!**

  Without it, the upsell won't show. In the CRM:

  * Funnel → Upsell Step → Static Resources section
  * Select "offer" → Choose an offer from your catalog
</Warning>

***

## 🎉 Congratulations!

You've built a complete checkout funnel with:

* ✅ Multi-step navigation
* ✅ Automatic data flow between pages
* ✅ Static resources for flexible configuration
* ✅ Professional UI
* ✅ Deployed to production

***

## 📚 What You Learned

<CardGroup cols={2}>
  <Card title="Funnel Navigation" icon="route">
    Pass data between steps using `funnel.next()` with resources
  </Card>

  <Card title="Static Resources" icon="bullseye">
    Let merchants configure offers/products without code changes
  </Card>

  <Card title="Context Resources" icon="database">
    Access data from previous steps via `funnel.context.resources`
  </Card>

  <Card title="Auto-redirect" icon="arrow-right">
    SDK handles navigation automatically
  </Card>
</CardGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Funnel Resources Guide" icon="database" href="/developer-tools/sdk/funnel-resources">
    Deep dive into passing complex data
  </Card>

  <Card title="API Reference" icon="book" href="/developer-tools/sdk/api">
    Explore all available hooks
  </Card>

  <Card title="Manifest Guide" icon="file-code" href="/developer-tools/sdk/manifest">
    Master plugin configuration
  </Card>

  <Card title="Examples" icon="code" href="/developer-tools/sdk/examples">
    See more complete examples
  </Card>
</CardGroup>

***

## Common Questions

<AccordionGroup>
  <Accordion title="How do I add more static resources?" icon="question">
    **In Manifest** - Add to requirements:

    ```json theme={null}
    {
      "resource": "product",
      "from": [{ "name": "id", "type": "static" }]
    }
    ```

    **In Code** - Access it:

    ```tsx theme={null}
    const productId = funnel.context?.static?.product?.id;
    ```

    **For Local Testing**:

    ```json theme={null}
    {
      "offer": { "id": "offer_123" },
      "product": { "id": "prod_456" }
    }
    ```
  </Accordion>

  <Accordion title="Can I pass custom data between pages?" icon="question">
    **Yes!** Add anything to resources:

    ```tsx theme={null}
    await funnel.next({
      data: {
        resources: {
          order: { ... },
          customer: { ... },
          customData: {
            selectedPlan: 'premium',
            addons: ['support', 'backup'],
            anything: 'you want!'
          }
        }
      }
    });
    ```

    Access on next page:

    ```tsx theme={null}
    const customData = funnel.context?.resources?.customData;
    ```
  </Accordion>

  <Accordion title="What if merchant doesn't configure static resource?" icon="question">
    **Always check if it exists:**

    ```tsx theme={null}
    const offerId = funnel.context?.static?.offer?.id;

    if (!offerId) {
      // Skip this page or show error
      await funnel.next({ type: 'skip_offer' });
      return;
    }
    ```

    **Or provide a default:**

    ```tsx theme={null}
    const offerId = funnel.context?.static?.offer?.id || 'default_offer_id';
    ```
  </Accordion>

  <Accordion title="How do I debug funnel context?" icon="question">
    **In your component:**

    ```tsx theme={null}
    useEffect(() => {
      console.log('Funnel Context:', funnel.context);
      console.log('Static Resources:', funnel.context?.static);
      console.log('Context Resources:', funnel.context?.resources);
    }, [funnel.context]);
    ```

    Or use the SDK debug drawer (in development mode)!
  </Accordion>
</AccordionGroup>
