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:
High Quality Starter Kits with built-in authentication flow (Auth.js), object uploads (AWS, Clouflare R2, Firebase Storage, Supabase Storage), integrated payments (Stripe, LemonSqueezy), email verification flow (Resend, Postmark, Sendgrid), and much more. Compatible with any database (Redis, Postgres, MongoDB, SQLite, Firestore).
Get all 3 kits$147$99- Next.js Starter Kit
- SvelteKit Starter Kit
- Astro Starter Kit
One-time license · Lifetime updates
- 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.