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
percentOffand 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 2: Detect the visitor’s country on the server
- Step 3: Create parity discounts in Polar (manual or via API)
- Step 4: Return either “percent off” or “coupon code” from an Astro server route
- Step 5: Render a parity banner in Astro (client-side)
- Guardrails to prevent abuse
Prerequisites
You’ll need:
- Node.js 20 or later
- A Polar 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 advertisecouponCode: a single code shared by all countries in that tiercountries: the ISO 3166-1 alpha-2 codes that belong to that tier
Your core types can look like this:
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:
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:
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:
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:
- Keep a tier definition list in your code (
PARITY_TIERS) - 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
percentOffmatches what you expect
- 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:
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
eligibleandpercentOff - Builds a short sentence and optionally appends the code if provided
<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: falsefor 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.