Guía paso a paso para migrar un SaaS Next.js de Stripe a Polar: abstracción de pagos, webhooks, sandbox y deploy en sombra.
Israel Palma
4 min de lectura
Si tienes un SaaS indie vendiendo a Europa, probablemente has notado el tirón de migrar de Stripe a
Polar. La razón principal: Polar es Merchant of Record y se ocupa del IVA por ti.
La migración técnica no es tan dolorosa como suena. Si tu integración de Stripe está bien
modularizada, son ~2 horas de trabajo. Esta guía explica cómo lo haríamos en una app Next.js
existente.
## Antes de migrar: qué NO se mueve
- **Suscripciones activas**: no migran automáticamente. Polar no importa de Stripe. Los clientes
existentes siguen en Stripe hasta que renueven o cambien de plan.
- **Historial de pagos**: no se transfiere. Déjalo en Stripe como archivo.
- **Métodos de pago guardados**: tampoco. Polar pedirá la tarjeta de nuevo.
Por eso la migración funciona mejor con un SaaS joven (< 100 suscripciones) o con un cambio gradual:
clientes nuevos en Polar, antiguos siguen en Stripe hasta que churneen.
## Paso 1: instalar Polar
```bash
bun add @polar-sh/sdk
```
Mantén `stripe` por ahora; lo eliminas al final.
## Paso 2: variables de entorno
Mantén las de Stripe y añade las de Polar:
```
POLAR_ACCESS_TOKEN=polar_oat_...
POLAR_WEBHOOK_SECRET=whsec_...
POLAR_SERVER=production
```
## Paso 3: crear productos en Polar Dashboard
Replica los productos que tienes en Stripe. Ojo con:
- **Precios**: Polar trabaja en céntimos como Stripe. Pero si tienes precios con IVA incluido en
Stripe, en Polar pones el precio neto y Polar añade IVA al cliente final. Decide si tu pricing
visible cambia.
- **Recurring intervals**: monthly, yearly. Polar los soporta.
- **One-time products**: también, igual que Stripe.
## Paso 4: abstraer la lógica de pagos
Si ya tienes una capa de abstracción tipo `PaymentService` o `lib/payments/`, esto lo tienes hecho.
Si no, ahora es el momento.
`src/lib/payments/types.ts`:
```ts
export interface PaymentProvider {
createCheckout(params: CheckoutParams): Promise<{ url: string }>;
cancelSubscription(subscriptionId: string): Promise;
verifyWebhook(body: string, signature: string): Promise;
}
```
Implementa `StripeProvider` y `PolarProvider`. Cambias entre ellos con una env var:
```ts
const provider =
process.env.PAYMENT_PROVIDER === 'polar' ? new PolarProvider() : new StripeProvider();
```
## Paso 5: nuevo endpoint de webhook Polar
Stripe y Polar firman webhooks de forma distinta. Crea un endpoint nuevo, no modifiques el de
Stripe:
`src/app/api/webhooks/polar/route.ts`:
```ts
import { polar } from '@/lib/polar/client';
import { db } from '@/lib/db/client';
export async function POST(req: Request) {
const body = await req.text();
const signature = req.headers.get('webhook-signature')!;
const event = polar.webhooks.verify({
body,
signature,
secret: process.env.POLAR_WEBHOOK_SECRET!,
});
// Reusa la lógica del webhook de Stripe, adaptada a eventos de Polar
switch (event.type) {
case 'subscription.created':
// crear en DB
break;
case 'subscription.canceled':
// marcar como canceled
break;
}
return new Response('OK');
}
```
## Paso 6: estrategia de cohortes
En tu DB, añade un campo `paymentProvider` a la tabla de suscripciones:
```prisma
model Subscription {
id String @id
userId String
paymentProvider String // 'stripe' | 'polar'
externalId String // ID en Stripe o Polar
status String
...
}
```
Cuando un usuario cancela y se vuelve a suscribir, su nueva suscripción va a Polar. Las antiguas se
quedan en Stripe hasta que churneen.
## Paso 7: probar en sandbox
Polar tiene sandbox separado. Antes de tocar producción:
1. Crea cuenta sandbox + productos en sandbox
2. Configura `POLAR_SERVER=sandbox`
3. Haz una compra de prueba con tarjeta `4242 4242 4242 4242`
4. Verifica que el webhook llega y la DB se actualiza
5. Verifica la cancelación
Cuando todo funciona en sandbox → cambia a `production`.
## Paso 8: shadow deploy
No anuncies la migración el día 1. Despliega Polar en producción con:
- Botón "Pagar con Polar" oculto detrás de un feature flag
- Activas el flag para tu cuenta de prueba
- Haces una compra real
- Verificas que Polar te transfiere el dinero a tu cuenta bancaria
- Activas para todos los signups nuevos
## Errores comunes
**1. Webhook duplicado**: si reproduces el mismo evento, asegúrate de que tu DB no crea
suscripciones duplicadas. Indexa por `externalId`.
**2. IVA en pricing visible**: si Stripe mostraba "10€ IVA incluido", en Polar el cliente verá
"10€ + IVA" porque Polar lo calcula. O bajas el precio neto, o subes el visible. Comunica.
**3. Reembolsos y disputes**: la lógica es distinta. Lee la documentación de Polar antes del primer
reembolso.
## Conclusión
Migrar Stripe → Polar son ~2 horas de código si tienes la abstracción hecha, o ~1 día desde cero. La
parte más larga es probar en sandbox y hacer el shadow deploy.
A cambio: te olvidas del IVA, de la liquidación OSS, del gestor discutiendo qué tipo de servicio es
un SaaS. Si vendes a Europa, vale la pena.
¿Te gustó este artículo?
Suscríbete para más tutoriales y tips sobre crear productos con IA