> ## 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.

# Geo-based offers

> One checkout, three localized one-click offers — route the post-purchase offer by visitor country with customer.fromCountry and customer.fromEU

# Geo-based offers

**Time**: \~15 minutes | **Difficulty**: Beginner | **Type**: part of [Funnel demos](/developer-tools/funnel-demos/introduction)

<Info>
  **Finished project:** [`geo-offers`](https://github.com/TagadaPay/examples/tree/main/geo-offers). Clone it if you want the working app first — or keep reading and rebuild the same graph.
</Info>

Everyone buys the same tee. The one-click offer after payment depends on where the visitor is: US traffic sees a cap, EU traffic sees a beanie, everyone else sees a tote. This is the standard play when you run the same creative in several markets — localize the *offer*, not the whole funnel.

<img src="https://mintcdn.com/tagadapay/ZVdPq_Zl9I9f0_Ur/assets/images/funnel-demos/geo-offers-graph.png?fit=max&auto=format&n=ZVdPq_Zl9I9f0_Ur&q=85&s=5e02a1451b959a450683d11726478e98" alt="Geo-based offers funnel: checkout, then US cap, EU beanie, or rest-of-world tote picked by country" width="3007" height="1123" data-path="assets/images/funnel-demos/geo-offers-graph.png" />

***

## What you need

| Resource          | Role                           | Example                 |
| ----------------- | ------------------------------ | ----------------------- |
| Main product      | Cart on landing / checkout     | Essential Tee · \$29.99 |
| US offer product  | One-click offer, US visitors   | Varsity Cap · \$19.99   |
| EU offer product  | One-click offer, EU visitors   | Alpine Beanie · \$17.99 |
| Fallback product  | One-click offer, everyone else | Travel Tote · \$24      |
| 3 checkout offers | One per region                 | `type: 'upsell'`        |

Prices are **integer cents** (`2999` = \$29.99).

***

## The graph

Five steps. The split is three edges out of checkout — the **highest-priority matching edge wins**, and `always` with a low priority is the safety net so nobody gets stuck.

| Step        | `type`     | Path                  | Bound offer    |
| ----------- | ---------- | --------------------- | -------------- |
| Landing     | `landing`  | `/`                   | —              |
| Checkout    | `checkout` | `/checkout`           | —              |
| US offer    | `offer`    | `/offer-us/:orderId`  | Cap \$19.99    |
| EU offer    | `offer`    | `/offer-eu/:orderId`  | Beanie \$17.99 |
| World offer | `offer`    | `/offer-row/:orderId` | Tote \$24      |
| Thank you   | `thankyou` | `/thankyou/:orderId`  | —              |

| Edge                   | `when`                                     | `priority` |
| ---------------------- | ------------------------------------------ | ---------- |
| Checkout → US offer    | `customer.fromCountry` `{ country: 'US' }` | 10         |
| Checkout → EU offer    | `customer.fromEU`                          | 10         |
| Checkout → World offer | `always`                                   | 1          |
| Each offer → Thank you | (none — accept or decline)                 | —          |

```ts theme={null}
edges: [
  { id: 'e_land', source: 'step_landing', target: 'step_checkout' },
  {
    id: 'e_us',
    source: 'step_checkout',
    target: 'step_offer_us',
    conditions: { when: { 'customer.fromCountry': { country: 'US' } }, priority: 10 },
  },
  {
    id: 'e_eu',
    source: 'step_checkout',
    target: 'step_offer_eu',
    conditions: { when: 'customer.fromEU', priority: 10 },
  },
  {
    id: 'e_row',
    source: 'step_checkout',
    target: 'step_offer_row',
    conditions: { when: 'always', priority: 1 },
  },
  { id: 'e_us_done', source: 'step_offer_us', target: 'step_thankyou' },
  { id: 'e_eu_done', source: 'step_offer_eu', target: 'step_thankyou' },
  { id: 'e_row_done', source: 'step_offer_row', target: 'step_thankyou' },
]
```

<Note>
  More geo conditions exist: `customer.fromContinent` `{ continent: 'EU' }`, `customer.withLocale` `{ locale: 'en-US' }`. Hosted funnels resolve the visitor's location server-side — you never touch an IP.
</Note>

***

## Same type on the Headless SDK

Self-hosted, the split is just a lookup before you render the offer. The [finished project](https://github.com/TagadaPay/examples/tree/main/geo-offers) keeps `/offer` as **one route** and picks the offer id from the visitor's region — same shape as the funnel edges:

```tsx theme={null}
// /offer — one route, three possible offers
const OFFER_BY_REGION = {
  us:  { offerId: US_OFFER_ID,  title: 'Add the Varsity Cap',  priceLabel: '$19.99' },
  eu:  { offerId: EU_OFFER_ID,  title: 'Add the Alpine Beanie', priceLabel: '$17.99' },
  row: { offerId: ROW_OFFER_ID, title: 'Add the Travel Tote',   priceLabel: '$24.00' },
};

export function GeoOffer() {
  const offer = OFFER_BY_REGION[getRegion()];
  return <Offer {...offer} acceptPath="/thank-you" declinePath="/thank-you" />;
}
```

Accepting still charges the saved card in one click:

```ts theme={null}
const result = await processOfferPayment({
  offerId: offer.offerId,
  mainOrderId: orderId,
  returnUrl: window.location.href,
});
```

<Tip>
  The demo app puts **region chips (🇺🇸 / 🇪🇺 / 🌍) on the landing** so you can walk all three branches from one browser — the browser timezone sets the default. In production, detect the region server-side or let the hosted funnel edges do it for you.
</Tip>

***

## Paths the customer can take

| Visitor            | Offer shown           | If accepted  |
| ------------------ | --------------------- | ------------ |
| 🇺🇸 United States | Varsity Cap \$19.99   | Tee + cap    |
| 🇪🇺 Europe        | Alpine Beanie \$17.99 | Tee + beanie |
| 🌍 Anywhere else   | Travel Tote \$24      | Tee + tote   |

Every accepted offer appears on thank you as a **related order** (`relatedOrders[]`), exactly like the [basic post-purchase](/developer-tools/funnel-demos/basic-post-purchase#thank-you-related-orders) demo.

***

## Next

<CardGroup cols={2}>
  <Card title="Finished project on GitHub" icon="github" href="https://github.com/TagadaPay/examples/tree/main/geo-offers">
    Clone `geo-offers` and walk all three branches in a minute
  </Card>

  <Card title="Basic post-purchase" icon="shirt" href="/developer-tools/funnel-demos/basic-post-purchase">
    Start here if this is your first funnel demo
  </Card>

  <Card title="VIP tag offers" icon="star" href="/developer-tools/funnel-demos/vip-tag-offers">
    Same split, driven by CRM tags instead of geography
  </Card>

  <Card title="Funnel Orchestrator" icon="diagram-project" href="/developer-tools/funnels/funnel-orchestrator">
    Nodes, edges, conditions, priorities
  </Card>
</CardGroup>
