Blog Kaynaklar Hakkında Ara Konular
Eğitmenlik

Node.js'da Polar.sh Ödemeleri: Tam Arka Plan Kılavuzu

Güncellenme 3 Ocak 2026

Kategori: Eğitmenlik
Paylaş

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:

Terminal
npm install @polar-sh/sdk standardwebhooks

Polar istemcisini başlatın:

src/lib/polar.ts
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:

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

src/routes/checkout.ts
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.
  • products bir dizi alır—bir ödeme sırasında birden fazla ürünü paket olarak sunabilirsiniz.

Express rotası örneği:

src/routes/api.ts
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:

src/webhooks/polar.ts
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:

src/app.ts
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);
src/routes/webhooks.ts
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:

src/services/polar-events.ts
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):

src/services/subscriptions.ts
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:

src/services/subscriptions.ts
export async function getCustomerPortalUrl(polarCustomerId: string) {
    const session = await polar.customerSessions.create({
        customerId: polarCustomerId,
    });

    return session.customerPortalUrl;
}

Express rotası:

src/routes/api.ts
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ı ödeme
  • 4000 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.active olayı kullanıcı erişimini etkinleştirir
  • Müşteri portalı doğru yüklenir
  • İptal çalışır ve cancelAtPeriodEnd ayarlar

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.

Kategori Eğitmenlik
Paylaş

En son AI içgörülerini gelen kutunuza teslim alın

En son eğilimler, öğreticiler ve endüstri içgörüleriyle güncel kalın. Bültenimize güvenen geliştirici topluluğuna katılın.

Yalnızca yeni hesaplar. E-postanızı göndererek Gizlilik Politikası