Skip to main content

Stripe vs Polar vs LemonSqueezy

·StarterPick Team
stripepolarlemonsqueezypaymentssaas-boilerplatebilling2026

Payment Processing Is Not a Commodity Decision

Choosing your payment provider is one of the highest-leverage decisions in a SaaS boilerplate. It affects:

  • Your take-home rate — fees vary from 2.9% to 9%+ depending on the provider
  • Geographic reach — not all providers serve all countries
  • Tax compliance — merchant of record services handle VAT/sales tax; Stripe does not
  • Developer experience — webhook handling, SDK quality, documentation
  • Boilerplate compatibility — most boilerplates support Stripe; fewer support others

TL;DR

  • Stripe: Use for most SaaS products. Maximum boilerplate support, best API, requires tax compliance work.
  • LemonSqueezy: Use for B2C products selling internationally. Merchant of record handles taxes. 8% fee.
  • Polar.sh: Use for open-source projects and developer tools. Usage-based billing, GitHub integration, generous free tier.
  • Paddle: Similar to LemonSqueezy for B2B international sales. Enterprise-focused.

Key Takeaways

  • Stripe has 2.9% + $0.30 base fee — the lowest transaction cost
  • LemonSqueezy is 8% per transaction — more expensive but handles all sales tax globally
  • Polar.sh has a 4% fee (with Stripe's 2.9%) — better for developer tools needing GitHub integration
  • Merchant of record (LemonSqueezy, Paddle, Polar) handle EU VAT, US sales tax, and global tax compliance — Stripe does not
  • If you sell to EU customers as a small SaaS, a merchant of record saves significant compliance work
  • All major boilerplates (ShipFast, OpenSaaS, Makerkit) support Stripe; fewer support the alternatives

Stripe: The Default

Stripe is the payment infrastructure standard in 2026. Every major SaaS boilerplate includes Stripe. The API is excellent, the documentation is best-in-class, and Stripe's ecosystem (Radar for fraud, Billing for subscriptions, Meters for usage-based billing) is unmatched.

Stripe Setup in Next.js

npm install stripe @stripe/stripe-js
// lib/stripe.ts
import Stripe from 'stripe';

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2025-10-28.acacia',
});

// Create a checkout session:
export async function createCheckoutSession({
  priceId,
  customerId,
  userId,
  successUrl,
  cancelUrl,
}: {
  priceId: string;
  customerId?: string;
  userId: string;
  successUrl: string;
  cancelUrl: string;
}) {
  return stripe.checkout.sessions.create({
    mode: 'subscription',
    payment_method_types: ['card'],
    customer: customerId,
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: successUrl,
    cancel_url: cancelUrl,
    metadata: { userId },
    allow_promotion_codes: true,
    subscription_data: {
      metadata: { userId },
    },
  });
}

// Customer portal (manage subscription):
export async function createPortalSession(customerId: string, returnUrl: string) {
  return stripe.billingPortal.sessions.create({
    customer: customerId,
    return_url: returnUrl,
  });
}
// app/api/webhooks/stripe/route.ts
import { stripe } from '@/lib/stripe';
import { headers } from 'next/headers';

export async function POST(req: Request) {
  const body = await req.text();
  const signature = headers().get('stripe-signature')!;

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return new Response('Invalid signature', { status: 400 });
  }

  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object;
      const userId = session.metadata?.userId;
      await db.user.update({
        where: { id: userId },
        data: {
          stripeCustomerId: session.customer as string,
          stripeSubscriptionId: session.subscription as string,
          plan: 'pro',
        },
      });
      break;
    }
    case 'customer.subscription.deleted': {
      const sub = event.data.object;
      await db.user.update({
        where: { stripeSubscriptionId: sub.id },
        data: { plan: 'free' },
      });
      break;
    }
  }

  return new Response('OK');
}

Stripe Downsides

  • No merchant of record — you handle EU VAT, US sales tax yourself
  • Tax compliance for EU: use Stripe Tax ($0.50 per transaction) or use TaxJar/Avalara
  • Stripe Tax is available but an add-on cost

LemonSqueezy: Merchant of Record

LemonSqueezy is a merchant of record — they handle all sales tax, VAT, and compliance globally. You sell through LemonSqueezy; they remit taxes in 100+ countries.

Fee: 8% + $0.50 per transaction (significantly higher than Stripe's 2.9%)

The 8% is worth paying when:

  • You sell to EU customers (VAT compliance is complex)
  • You are in a country with complex tax requirements
  • You want to minimize accounting overhead
npm install @lemonsqueezy/lemonsqueezy.js
// lib/lemonsqueezy.ts
import { lemonSqueezySetup, createCheckout } from '@lemonsqueezy/lemonsqueezy.js';

lemonSqueezySetup({ apiKey: process.env.LEMONSQUEEZY_API_KEY! });

export async function createLemonCheckout({
  variantId,
  userId,
  successUrl,
}: {
  variantId: string;
  userId: string;
  successUrl: string;
}) {
  const checkout = await createCheckout(
    process.env.LEMONSQUEEZY_STORE_ID!,
    variantId,
    {
      checkoutOptions: { embed: false },
      checkoutData: {
        custom: { userId },
      },
      productOptions: { redirectUrl: successUrl },
    }
  );

  return checkout.data?.data.attributes.url;
}
// Webhook handler:
export async function POST(req: Request) {
  const body = await req.text();
  const secret = process.env.LEMONSQUEEZY_WEBHOOK_SECRET!;

  const hmac = crypto.createHmac('sha256', secret);
  const digest = hmac.update(body).digest('hex');
  const signature = req.headers.get('x-signature');

  if (digest !== signature) return new Response('Invalid', { status: 401 });

  const event = JSON.parse(body);

  if (event.meta.event_name === 'subscription_created') {
    const userId = event.meta.custom_data.userId;
    await db.user.update({
      where: { id: userId },
      data: { plan: 'pro', lemonSqueezyCustomerId: event.data.attributes.customer_id },
    });
  }
}

Polar.sh: Developer Tools Billing

Polar.sh is designed for open-source maintainers and developer tools:

npm install @polar-sh/sdk
// lib/polar.ts
import { Polar } from '@polar-sh/sdk';

export const polar = new Polar({
  accessToken: process.env.POLAR_ACCESS_TOKEN!,
});

// Create checkout for Polar:
export async function createPolarCheckout({
  productPriceId,
  userId,
  successUrl,
}: {
  productPriceId: string;
  userId: string;
  successUrl: string;
}) {
  const checkout = await polar.checkouts.create({
    productPriceId,
    successUrl,
    metadata: { userId },
  });
  return checkout.url;
}

Polar.sh unique features:

  • GitHub Issues integration — link subscriptions to GitHub repositories
  • Benefit system — automatically grant benefits (Discord roles, GitHub access) on subscription
  • Usage-based billing — meter API calls or usage
  • Free for open source — lower fees for OSS projects

Fee: 4% (Polar) + 2.9% (Stripe pass-through) = ~7% total


Comparison Table

FeatureStripeLemonSqueezyPolar.sh
Transaction fee2.9% + $0.308% + $0.50~4% + Stripe
Merchant of recordNoYesYes
Tax handlingManual (or Stripe Tax)AutomaticAutomatic
Boilerplate supportUniversalManyGrowing
Usage-based billingYes (Meters)LimitedYes
Developer focusNeutralNeutralStrong
GitHub integrationNoNoYes
EU VAT complianceManualIncludedIncluded
SubscriptionsExcellentGoodGood
API qualityExcellentGoodGood

Which Boilerplates Support What?

BoilerplateStripeLemonSqueezyPolar.sh
ShipFastYesYesNo
OpenSaaSYesYesYes
MakerkitYesYesNo
SaaSBoldYesYesYes (Paddle)
SupastarterYesYesNo
T3 StackManualManualManual

Decision Framework

Choose Stripe if:
  → US-focused B2B SaaS (fewer tax complications)
  → Need maximum boilerplate compatibility
  → Want usage-based billing (Stripe Meters)
  → Lowest transaction cost matters

Choose LemonSqueezy if:
  → B2C with global customers
  → EU market (VAT compliance)
  → Digital products / content
  → Tax headaches outweigh the fee premium

Choose Polar.sh if:
  → Open-source project monetization
  → Developer tool with GitHub integration
  → Want GitHub Issues linked to subscriptions
  → Community/supporter model

Use both Stripe + LemonSqueezy if:
  → Some plans are B2B (Stripe), some B2C (LemonSqueezy)
  → Complex billing needs different tools

Methodology

Based on publicly available pricing and documentation from Stripe, LemonSqueezy, Polar.sh, and boilerplate documentation as of March 2026.


Choosing payment infrastructure for your SaaS? StarterPick helps you find boilerplates pre-configured with the right payment provider for your market.

Comments