Build your own Parity Pricing for SaaS with Polar and Astro
LaunchFast Logo LaunchFast

Build your own Parity Pricing for SaaS with Polar and Astro

Rishi Raj Jain
Parity pricing with Polar and Astro

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).
Next.js Starter Kit
SvelteKit Starter Kit
  • 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

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 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:

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:

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:

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

Learn More Build a Verified Recent Sales Notification with Polar and Astro
Build a Verified Recent Sales Notification with Polar and Astro April 5, 2026
Live Collaborative Editing in Astro with Cloudflare Durable Objects
Live Collaborative Editing in Astro with Cloudflare Durable Objects December 9, 2025
Bot Protection in Astro with Cloudflare Turnstile
Bot Protection in Astro with Cloudflare Turnstile December 9, 2025