---
title: "Build your own Parity Pricing for SaaS with Polar and Astro"
description: "Implement purchasing-power parity (PPP) pricing with Polar and Astro: detect a visitor’s country server-side, map it to a discount tier, and return either % off or a Polar discount code for checkout."
source: "https://www.launchfa.st/blog/parity-pricing-polar-astro"
author: "Rishi Raj Jain"
created_at: 2026-04-07T12:00:00.000Z
keywords: "astro, polar, parity pricing, ppp pricing, purchasing power parity, regional pricing, discount codes, vercel, api routes, typescript"
---

> **Site index for agents**
> Fetch https://www.launchfa.st/llms.txt to discover every page on this site.
> Every page is also available as Markdown by appending `.md` to its URL.

<img width="1600" height="900" alt="Parity pricing with Polar and Astro" decoding="async" loading="eager" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/parity-pricing-polar-astro.png" />

Parity pricing (often based on purchasing-power parity / PPP) is one of the simplest ways to make your product affordable globally without permanently lowering your headline price. The implementation is also straightforward if you already have:

- A payment provider that supports **discount codes** (Polar does)
- A way to detect a visitor’s **country** server-side (Astro API routes on Vercel make this easy)
- A clear set of **discount tiers** you’re willing to offer

This tutorial walks through a production-friendly parity system with **Polar + Astro**:

- Detect country **server-side**
- Map country → tier (e.g. 75% / 65% / 55%…)
- Return an “offer” JSON response containing **`percentOff`** and optionally a **Polar discount code**
- Render a small banner on the marketing site and/or apply the discount at checkout

## Table of Contents

- [Step 1: Define your parity tiers (country → discount tier)](#step-1-define-your-parity-tiers-country--discount-tier)
  - [Picking a mapping source](#picking-a-mapping-source)
- [Step 2: Detect the visitor’s country on the server](#step-2-detect-the-visitors-country-on-the-server)
- [Step 3: Create parity discounts in Polar (manual or via API)](#step-3-create-parity-discounts-in-polar-manual-or-via-api)
  - [Strategy: one code per tier vs one code per country](#strategy-one-code-per-tier-vs-one-code-per-country)
  - [Automating discount creation (high-level)](#automating-discount-creation-high-level)
- [Step 4: Return either “percent off” or “coupon code” from an Astro server route](#step-4-return-either-percent-off-or-coupon-code-from-an-astro-server-route)
- [Step 5: Render a parity banner in Astro (client-side)](#step-5-render-a-parity-banner-in-astro-client-side)
- [Guardrails to prevent abuse](#guardrails-to-prevent-abuse)


## Prerequisites

You’ll need:

- [Node.js 20](https://nodejs.org/en/blog/announcements/v20-release-announce) or later
- A [Polar](https://polar.sh/) account with at least one product
- Astro running in **server mode** (`output: 'server'`) so API routes execute at runtime
- (Recommended) Vercel deployment if you want built-in request geolocation via headers

## Architecture overview

```
1. Browser (marketing site)
   → GET /api/parity-offer?product=astro (optional product parameter)
   → Receives JSON: country, percentOff, eligible, optional coupon/discount code

2. Astro API route (server)
   → Resolves visitor country from request metadata (e.g. Vercel geolocation)
   → Looks up PPP tier for that country
   → Returns offer JSON for the UI + checkout flow

3. Polar (payments)
   → Discount codes exist per tier (or per product, depending on your strategy)
   → Checkout applies the code (either prefilled or user-entered)
```

The key design choice is: **the browser should never decide the discount**. The server route should compute the offer, because the server has access to trusted request metadata and can enforce guardrails.

## Step 1: Define your parity tiers (country → discount tier)

Start by deciding the tiers you want to offer. A pragmatic approach is to use a handful of percentage tiers (10%, 15%, 25%, 35%, 45%, 55%, 65%, 75%) and assign countries to each tier.

In your own Astro application, add a `src/lib/parity.ts` file where each tier has:

- `pppRange`: purely informational (useful for docs/admin screens)
- `percentOff`: the discount to advertise
- `couponCode`: a **single** code shared by all countries in that tier
- `countries`: the ISO 3166-1 alpha-2 codes that belong to that tier

Your core types can look like this:

```ts
export type ParityTierMeta = {
  pppRange: string
  percentOff: number
  couponCode: string
}

export type ParityOffer = {
  country: string
  countryName: string
  product: string
  percentOff: number
  eligible: boolean
  couponCode?: string
  pppRange?: string
}
```

Then build a fast lookup map once at module init:

```ts
function buildCountryParityMap(): Record<string, { percentOff: number; couponCode: string; pppRange: string }> {
  const map: Record<string, { percentOff: number; couponCode: string; pppRange: string }> = {}
  for (const tier of PARITY_TIERS) {
    for (const cc of tier.countries) {
      map[cc] = { percentOff: tier.percentOff, couponCode: tier.couponCode, pppRange: tier.pppRange }
    }
  }
  return map
}
```

Finally, expose a small helper that returns a single offer:

```ts
export function getParityOffer({ country, product }: { country: string; product: string }): ParityOffer {
  const upper = country.toUpperCase()
  const rule = COUNTRY_PARITY[upper]
  const percentOff = rule?.percentOff ?? 0

  return {
    country: upper,
    countryName: new Intl.DisplayNames(['en'], { type: 'region' }).of(upper) ?? upper,
    product,
    percentOff,
    eligible: percentOff > 0,
    ...(rule?.couponCode ? { couponCode: rule.couponCode } : {}),
    ...(rule?.pppRange ? { pppRange: rule.pppRange } : {}),
  }
}
```

### Picking a mapping source

You have options:

- **Hardcoded tiers** (what this example does): simplest, no external dependencies, easy to reason about.
- **Database-backed tiers**: useful if you need an admin UI or want to update mappings without redeploying.
- **Dynamic PPP indexes**: possible, but rarely worth the complexity unless your business depends on it.

Start with hardcoded tiers; you can always evolve later.

## Step 2: Detect the visitor’s country on the server

You need a country code to do parity pricing. On Vercel, you can derive it from request geolocation metadata.

This repository’s API route uses `geolocation()` from `@vercel/functions` inside an Astro `APIRoute`:

```ts title="src/pages/api/parity-offer.ts"
import { geolocation } from '@vercel/functions'
import type { APIRoute } from 'astro'
import { getParityOffer } from '../../lib/parity'

export const GET: APIRoute = async ({ request, url }) => {
  const product = url.searchParams.get('product') || 'default'
  const geo = geolocation(request)
  const country = geo.country || 'US'
  const offer = getParityOffer({ country, product })
  return new Response(JSON.stringify(offer), {
    headers: { 'content-type': 'application/json' },
  })
}
```

If you deploy somewhere else (or you want more control), you can also use provider headers (for example `x-vercel-ip-country`) or a server-side IP-to-country lookup. The key is that this happens server-side and cannot be trivially spoofed from the browser.

## Step 3: Create parity discounts in Polar (manual or via API)

Your parity tiers reference codes like `DRRBQY5P`. Those codes must exist as discounts/promotions in Polar for checkout to apply them.

You can create them in two ways:

- **Dashboard (manual):** Create one discount code per tier (recommended to start). Keep the code short, stable, and reusable.
- **Polar API (automated):** Create (and update) discounts programmatically via https://polar.sh/docs/api-reference/discounts/create so your tier table is the source of truth.

### Strategy: one code per tier vs one code per country

- **One code per tier**: easiest to manage, what most teams use.
- **One code per country**: gives you fine control but explodes operational overhead.

In most parity systems, **tier-based** is the sweet spot.

### Automating discount creation (high-level)

Polar exposes APIs for managing commerce entities. The exact endpoint names may evolve, but the architecture stays the same:

1. Keep a tier definition list in your code (`PARITY_TIERS`)
2. Add an internal-only admin route or a one-off script that:
   - Lists existing discounts from Polar
   - Creates missing discounts for each tier
   - Ensures each tier’s `percentOff` matches what you expect
3. Store the resulting discount identifiers/codes back into your tier map (or keep codes stable so you never need to write back)

If you want the site to return a **discount code**, you only need the code to exist in Polar.
If you want the site to return a **discount percentage**, you can display it in the UI even if the code is applied manually at checkout.

## Step 4: Return either “percent off” or “coupon code” from an Astro server route

Your API response shape should support both use cases:

- **Marketing UI**: show “\(X\)% discount for customers in \(Country\)” + optionally “Use code ABCD”
- **Checkout**: apply the code automatically (if your checkout supports prefilling) or at least show it prominently

This repository returns both `percentOff` and (when eligible) `couponCode` from `/api/parity-offer`.

A small but important improvement for production is to add caching:

- Geolocation-based offers do not need millisecond-level freshness.
- Cache the JSON for a short period at the edge (e.g. 1h–24h) to reduce server work.

For example:

```ts
return new Response(JSON.stringify(offer), {
  headers: {
    'content-type': 'application/json',
    // Cache on the CDN; always revalidate on the server when not cached.
    'cache-control': 'max-age=0, s-maxage=86400',
  },
})
```

## Step 5: Render a parity banner in Astro (client-side)

Once you have `/api/parity-offer`, you can add a small banner that shows the user their regional discount.

A component such as `src/components/ParityBanner.astro` can use a minimal client script:

- Fetches `/api/parity-offer`
- Checks `eligible` and `percentOff`
- Builds a short sentence and optionally appends the code if provided

```astro title="src/components/ParityBanner.astro"
<div id="parity-banner" class="hidden w-full border-b py-2.5 text-center" role="status" aria-live="polite">
  <div class="mx-auto flex max-w-7xl flex-wrap items-center justify-center gap-x-2 gap-y-1 px-4 text-sm leading-snug">
    <span id="parity-flag" class="shrink-0 text-lg leading-none" aria-hidden="true"></span>
    <p id="parity-text" class="font-sans"></p>
  </div>
</div>

<script>
  function countryCodeToFlagEmoji(code) {
    const upper = String(code).toUpperCase()
    if (upper.length !== 2 || !/^[A-Z]{2}$/.test(upper)) return ''
    return String.fromCodePoint(...[...upper].map((c) => 127397 + c.charCodeAt(0)))
  }

  const banner = document.getElementById('parity-banner')
  const flagEl = document.getElementById('parity-flag')
  const textEl = document.getElementById('parity-text')
  if (!banner || !flagEl || !textEl) {
    /* skip */
  } else {
    fetch('/api/parity-offer')
      .then((r) => r.json())
      .then((data) => {
        if (!data || !data.eligible || !data.percentOff || data.percentOff <= 0) return
        flagEl.textContent = countryCodeToFlagEmoji(data.country ?? '')

        const pct = data.percentOff
        const place = data.countryName || data.country || ''
        const parts = []
        parts.push(`<strong class="font-bold">${pct}%</strong>`)
        parts.push(` discount for customers in `)
        parts.push(`<strong class="font-bold">${place}</strong>!`)
        if (data.couponCode) {
          parts.push(` Use code `)
          parts.push(`<strong class="font-bold">${data.couponCode}</strong>`)
        }
        textEl.innerHTML = parts.join('')
        banner.classList.remove('hidden')
      })
      .catch(() => {})
  }
</script>
```

## Guardrails to prevent abuse

Parity pricing is a discount system, so you need basic guardrails:

- **Server-only decision:** never compute the tier in the browser.
- **Clamp and validate:** only return codes that exist in your tier table.
- **Product scoping:** if some products should not have parity discounts, return `eligible: false` for those.

If you need stronger enforcement (for example, preventing users from sharing a high-tier code), consider issuing **short-lived, user-specific** discount mechanisms instead of shared codes. That is more complex, but it’s the next step when parity becomes a material revenue lever.

## Conclusion

Parity pricing doesn’t need a big implementation effort when you are starting out with your SaaS. With Astro, Vercel and Polar, you can implement it as:

- A deterministic tier map (country → percent + code)
- A single API route that resolves country server-side and returns an offer JSON payload
- A tiny UI surface that communicates the discount clearly

From there, you can automate discount creation via the Polar API, add product-specific eligibility rules, and tighten enforcement as your volume grows.
