Node.js'da Polar.sh Ödemeleri: Tam Arka Plan Kılavuzu
Güncellenme 3 Ocak 2026
Node.js arka planı ile Polar.sh ödeme entegrasyonu
Bu yazı, Polar.sh ödemeleri hakkındaki X dizimdeki konuyu, tam kod örnekleri, hata yönetimi ve üretimden edinilen derslerle genişletiyor.
Bir SaaS veya dijital ürün satıyorsanız, bir ödeme sağlayıcısına ihtiyacınız var. Polar.sh, bağımsız projelerim için tercih ettiğim platform oldu—Node.js arka planına nasıl entegre edeceğinizi işte burada bulabilirsiniz.
Neden Polar.sh? #why-polar-sh
Koda dalmadan önce, neden alternatifler yerine Polar.sh’ı seçtiğime bir göz atalım:
Stripe’a kıyasla: Polar kutudan çıkar çıkmaz daha fazlasını sunar. Kendi barındırdığı ödeme sayfaları, bir müşteri portalı ve abonelik yaşam döngüsü yönetimi size sunulur; bunları kendiniz oluşturmanız gerekmez. Size daha fazla kontrol sunan Stripe ise daha fazla kod gerektirir.
Paddle/Lemon Squeezy’ye kıyasla: Benzer tüccar kaydı avantajları (VAT/vergiyi onlar halleder), ancak Polar özellikle geliştiriciler ve açık kaynak projeleri için tasarlanmıştır. DX (geliştirici deneyimi) belirgin şekilde daha iyidir.
Polar’ın sizin için hallettikleri:
- Kendi barındırdığı ödeme sayfaları
- Ödeme işleme
- Müşteri portalı
- Abonelik yaşam döngüsü
- Vergi uyumluluğu (tüccar kaydı olarak)
Sizin hallettikleriniz:
- Webhook olayları
- Kullanıcı yetkileri
- Kullanım takibi
Proje Kurulumu #project-setup
SDK’yı ve webhook doğrulama kütüphanesini kurun:
npm install @polar-sh/sdk standardwebhooks
Polar istemcisini başlatın:
import { Polar } from '@polar-sh/sdk';
export const polar = new Polar({
accessToken: process.env.POLAR_ACCESS_TOKEN!,
server: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox',
});
Gerekli ortam değişkenleri:
POLAR_ACCESS_TOKEN=your_access_token
POLAR_WEBHOOK_SECRET=your_webhook_secret
Bunları Polar kontrol panelinizden alın. Geliştirme sırasında sandbox modunu kullanın—test kartları sağlar ve gerçek para tahsil etmez.
Ödeme Oturumları Oluşturma #creating-checkout-sessions
Kullanıcı “Abone Ol” veya “Satın Al” düğmesine tıkladığında, bir ödeme oturumu oluşturun ve onu yönlendirin:
import { polar } from '../lib/polar';
export async function createCheckout(user: { id: string; email: string }, productId: string) {
const checkout = await polar.checkouts.create({
products: [productId],
successUrl: 'https://yourapp.com/success?session_id={CHECKOUT_SESSION_ID}',
customerEmail: user.email,
metadata: {
userId: String(user.id),
},
});
return checkout.url;
}
Ana noktalar:
metadata.userId, Polar müşterisini sizin kullanıcınıza bağlar. Buna webhook’larda ihtiyacınız olacak.successUrl, Polar’ın gerçek kimlikle değiştireceği{CHECKOUT_SESSION_ID}içerebilir.productsbir dizi alır—bir ödeme sırasında birden fazla ürünü paket olarak sunabilirsiniz.
Express rotası örneği:
router.post('/create-checkout', async (req, res) => {
const user = req.user; // kimlik doğrulama ara yazılımınızdan
const { productId } = req.body;
if (!productId) {
return res.status(400).json({ error: 'Ürün Kimliği gerekli' });
}
try {
const checkoutUrl = await createCheckout(user, productId);
res.json({ url: checkoutUrl });
} catch (error) {
console.error('Ödeme oluşturma başarısız:', error);
res.status(500).json({ error: 'Ödeme oluşturulamadı' });
}
});
Webhook Yönetimi #webhook-handling
Webhook’lar, Polar’ın ödeme olayları hakkında uygulamanıza haber verme şeklidir. Bu, doğru şekilde ele alınması gereken en kritik kısımdır.
İmza Doğrulama
Polar Standard Webhooks belirtimini kullanır. Sahte istekleri önlemek için imzaları doğrulamanız gerekir:
import { Webhook } from 'standardwebhooks';
const secret = 'whsec_' + Buffer.from(
process.env.POLAR_WEBHOOK_SECRET!
).toString('base64');
const wh = new Webhook(secret);
export function verifyWebhook(rawBody: string, headers: Record<string, string>) {
try {
return wh.verify(rawBody, headers);
} catch (error) {
console.error('Webhook doğrulama başarısız:', error);
throw new Error('Geçersiz webhook imzası');
}
}
Rota Kurulumu (Kritik!)
Webhook rotası, imza doğrulama için ham isteme gövdesine ihtiyaç duyar. JSON ara yazılımınızdan önce kaydedin:
import express from 'express';
import { webhookRouter } from './routes/webhooks';
const app = express();
// Webhook rotaları ÖNCE - ham gövdeye ihtiyaç var
app.use('/api/webhooks', webhookRouter);
// JSON ayrıştırma webhook'lardan SONRA
app.use(express.json());
// Diğer rotalar...
app.use('/api', apiRouter);
import { Router } from 'express';
import express from 'express';
import { verifyWebhook } from '../webhooks/polar';
import { handlePolarEvent } from '../services/polar-events';
const router = Router();
router.post('/polar',
express.raw({ type: 'application/json' }),
async (req, res) => {
try {
const event = verifyWebhook(
req.body.toString(),
req.headers as Record<string, string>
);
await handlePolarEvent(event);
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook hatası:', error);
res.status(400).json({ error: 'Webhook işleme başarısız' });
}
}
);
export { router as webhookRouter };
Olay İşleyicileri
Ana abonelik olaylarını ele almanın yolu:
import { db } from '../lib/db';
interface PolarEvent {
type: string;
data: {
id: string;
status: string;
customer_id: string;
metadata?: { userId?: string };
current_period_end?: string;
};
}
export async function handlePolarEvent(event: PolarEvent) {
const { type, data } = event;
switch (type) {
case 'subscription.created':
// Polar müşterisini kullanıcınıza bağlayın
await db.user.update({
where: { id: data.metadata?.userId },
data: {
polarCustomerId: data.customer_id,
subscriptionId: data.id,
subscriptionStatus: 'pending',
},
});
break;
case 'subscription.active':
// Ödeme başarılı - aboneliği etkinleştirin
await db.user.update({
where: { polarCustomerId: data.customer_id },
data: {
subscriptionStatus: 'active',
currentPeriodEnd: new Date(data.current_period_end!),
// Plan özel sınırları ayarlayın
apiCallsLimit: 10000,
storageLimit: 5 * 1024 * 1024 * 1024, // 5GB
},
});
break;
case 'subscription.updated':
// Plan değişikliği, yenileme vb.
await db.user.update({
where: { polarCustomerId: data.customer_id },
data: {
subscriptionStatus: data.status,
currentPeriodEnd: new Date(data.current_period_end!),
},
});
break;
case 'subscription.canceled':
// Kullanıcı iptal etti - dönem sonuna kadar hala aktif
await db.user.update({
where: { polarCustomerId: data.customer_id },
data: {
subscriptionStatus: 'canceled',
canceledAt: new Date(),
},
});
break;
case 'subscription.revoked':
// Anında iptal (ödeme başarısız, iade vb.)
await db.user.update({
where: { polarCustomerId: data.customer_id },
data: {
subscriptionStatus: 'revoked',
apiCallsLimit: 0,
storageLimit: 0,
},
});
break;
case 'order.paid':
// Tek seferlik satın alma tamamlandı
await handleOneTimePurchase(data);
break;
default:
console.log(`İşlenmemiş olay türü: ${type}`);
}
}
async function handleOneTimePurchase(data: PolarEvent['data']) {
// Ömür boyu erişim, krediler vb. verin
await db.user.update({
where: { id: data.metadata?.userId },
data: {
lifetimeAccess: true,
},
});
}
Abonelik Yönetimi #subscription-management
Aboneliği İptal Et
Kullanıcıların uygulamanızdan iptal etmesine izin verin (dönem sonuna kadar erişimleri devam eder):
import { polar } from '../lib/polar';
export async function cancelSubscription(subscriptionId: string) {
await polar.subscriptions.update({
id: subscriptionId,
subscriptionUpdate: {
cancelAtPeriodEnd: true,
},
});
}
Müşteri Portalı
Polar, müşterilerin faturaları yönetebileceği, ödeme yöntemlerini güncelleyebileceği ve faturaları görüntüleyebileceği barındırılan bir portal sunar:
export async function getCustomerPortalUrl(polarCustomerId: string) {
const session = await polar.customerSessions.create({
customerId: polarCustomerId,
});
return session.customerPortalUrl;
}
Express rotası:
router.post('/billing-portal', async (req, res) => {
const user = req.user;
if (!user.polarCustomerId) {
return res.status(400).json({ error: 'Abonelik bulunamadı' });
}
try {
const portalUrl = await getCustomerPortalUrl(user.polarCustomerId);
res.json({ url: portalUrl });
} catch (error) {
console.error('Portal oluşturma başarısız:', error);
res.status(500).json({ error: 'Portal oturumu oluşturulamadı' });
}
});
Yaygın Hatalar #common-pitfalls
Polar’ı üretimde entegre ettikten sonra, gördüğüm (ve yaptığım) hatalar:
1. JSON ara yazılımının webhook’lardan önce olması
İmza doğrulama hataları görüyorsanız, neredeyse her zaman sebep budur. Ham gövde, webhook işleyicinizden önce express.json() tarafından ayrıştırılır.
Çözüm: Webhook rotalarını express.json()’dan önce kaydedin.
2. Tüm abonelik durumlarını ele almama
Bir abonelik şu durumlarda olabilir: active, canceled, past_due, incomplete, trialing veya revoked. Sadece active kontrol etmeyin—tam yaşam döngüsünü ele alın.
3. Eksik meta veri
Ödeme meta verilerinde userId’yi iletmeyi unutursanız, Polar müşterisini kullanıcınıza bağlayamazsınız. Her zaman tanımlayıcı bilgileri ekleyin.
4. İdempotent olmayan webhook işleyicileri
Polar webhook’ları yeniden deneyebilir. İşleyicileriniz idempotent olmalıdır—aynı olayı iki kez işlemek bir şeyi bozmamalıdır.
// Kötü - yinelenen kayıtlar oluşturur
await db.payment.create({ data: { orderId: event.data.id, ... } });
// İyi - benzersiz kimliğe göre upsert
await db.payment.upsert({
where: { polarOrderId: event.data.id },
create: { polarOrderId: event.data.id, ... },
update: { ... },
});
5. Webhook yanıtlarını engelleme
Webhook uç noktaları hızlı yanıt vermelidir. Ağır bir işleme yapmanız gerekiyorsa, hemen onaylayın ve asenkron olarak işleyin:
router.post('/polar', express.raw({ type: 'application/json' }), async (req, res) => {
const event = verifyWebhook(req.body.toString(), req.headers);
// Hemen yanıt ver
res.status(200).json({ received: true });
// Asenkron olarak işle (üretimde uygun bir iş kuyruğu kullanın)
setImmediate(() => handlePolarEvent(event));
});
Entegrasyonunuzu Test Etme #testing-your-integration
Sandbox Modunu Kullanın
Polar’ın sandbox ortamı, gerçek ücretler olmadan tam akışı test etmenizi sağlar:
const polar = new Polar({
accessToken: process.env.POLAR_ACCESS_TOKEN,
server: 'sandbox', // Test modu
});
Test Kartları
Sandbox’ta bu test kartı numaralarını kullanın:
4242 4242 4242 4242- Başarılı ödeme4000 0000 0000 0002- Kart reddedildi
Yerel Webhook Testi
Yerel sunucunuzu açmak için ngrok veya benzerini kullanın:
ngrok http 3000
Ardından Polar kontrol panelinizde webhook URL’nizi şu şekilde ayarlayın: https://your-ngrok-url.ngrok.io/api/webhooks/polar
Kurulumunuzu Doğrulayın
Canlıya geçmeden önce kontrol listesi:
- Ödeme başarıyla oluşturulur ve Polar’a yönlendirilir
- Başarılı URL uygulamanıza geri döner
- Webhook imza doğrulaması geçer
-
subscription.activeolayı kullanıcı erişimini etkinleştirir - Müşteri portalı doğru yüklenir
- İptal çalışır ve
cancelAtPeriodEndayarlar
Bu, Polar.sh ödeme entegrasyonunun tamamı. Ana fikir: Polar’ın iyi olduğu şeyleri (ödeme UI’ı, ödeme işleme, müşteri portalı) ona bırakın ve kodunuzu webhook yönetimi ve kullanıcı yetkilerine odaklayın.
Daha fazla detay için resmi Polar belgelerine göz atın.