İçeriğe atla
Tüm makaleler

Next.js'te İdempotent Stripe ve Lemon Squeezy Webhook'ları

Ham webhook imzalarını doğrulama, yinelenen teslimatları veritabanı sınırında tekilleştirme ve çakışan aboneliklerin ücretli erişimi iptal etmesini önleme rehberi.

supastack ekibi2 dk okuma

How do I handle Stripe and Lemon Squeezy webhooks idempotently in Next.js?

It depends on your billing providers, but the core architecture is identical: verify the raw payload signature before parsing JSON, normalize provider events into a shared internal contract, and enforce idempotency at the database boundary using the provider's event or order ID. Never grant product entitlements from a client-side checkout redirect; let the verified webhook transactionally update subscription state and entitlements once. supastack prewires this pipeline for Stripe, Lemon Squeezy, and mobile RevenueCat, though single-plan apps can start with a simpler handler.

JSON'a dokunmadan önce ham yükü doğrulayın

Her ödeme sağlayıcısı kablo üzerinden gönderdiği bayt dizisinin tamamını imzalar. Bir rota işleyicisi imzayı kontrol etmeden önce await request.json() ile veriyi ayrıştırıp nesneyi yeniden serileştirirse, anahtar sırası veya boşluk farkları geçerli webhook'ları geçersiz kılar ya da geliştiricileri hazırlık ortamında doğrulamayı atlamaya iter.

Next.js App Router POST işleyicinizde ham gövdeyi await request.text() ile bir kez okuyun, kriptografik imzayı yalnızca sunucuda bulunan webhook gizli anahtarıyla doğrulayın ve tek bir veritabanı sorgusu bile çalıştırmadan önce başarısız olan her isteği HTTP 400 ile reddedin. Faturalandırma modülü rehberi, supastack'in imza kontrollerini tek bir BFF giriş noktası arkasında nasıl izole ettiğini gösterir.

export async function POST(request: Request) {
  const rawBody = await request.text();
  const signature = request.headers.get("stripe-signature");
  if (!signature) return new Response("Missing signature", { status: 400 });

  const event = stripe.webhooks.constructEvent(
    rawBody,
    signature,
    env.STRIPE_WEBHOOK_SECRET,
  );
  await handleNormalizedBillingEvent(event);
  return Response.json({ received: true });
}
  • Ham istek gövdesini request.text() ile okuyun — ayrıştırılmış JSON'u asla yeniden dizge haline getirmeyin.
  • STRIPE_WEBHOOK_SECRET ve Lemon Squeezy imzalama sırlarını yalnızca sunucu yapılandırmasında tutun.
  • HMAC imzalarını manuel olarak doğrularken sabit zamanlı karşılaştırma (crypto.timingSafeEqual) kullanın.

Tekilleştirmeyi bellekte değil, veritabanı sınırında yapın

Stripe ve Lemon Squeezy; bir dağıtım yeniden başladığında, sunucusuz bir fonksiyon zaman aşımına uğradığında veya bir ağ sıçraması yanıtı düşürdüğünde webhook'ları yeniden dener. Süreç içi kümeler veya kısa ömürlü önbellek anahtarları örnekler arasında kaybolur ve iki eşzamanlı yeniden denemenin çift lisans vermesini veya iki makbuz e-postası göndermesini engellemez.

Sağlayıcının olay kimliğini veya sipariş kapatma durumunu, abonelik erişimini değiştiren aynı Postgres işlemi içinde kaydedin. Atomik bir INSERT ... ON CONFLICT DO NOTHING veya status != 'paid' koşullu güncellemesi, eşzamanlı bir tekrarı güvenli bir etkisiz işleme dönüştürür. Bu webhook'ların yerine getirdiği planları tasarlarken, SaaS fiyatlandırma sayfası oluşturucu kademeleri ve koltuk kurallarını önceden modellemenize yardımcı olur.

const settled = await db.transaction(async (tx) => {
  const [inserted] = await tx
    .insert(billingWebhookEvents)
    .values({ providerEventId: event.id, provider: "stripe" })
    .onConflictDoNothing({ target: billingWebhookEvents.providerEventId })
    .returning({ id: billingWebhookEvents.id });

  if (!inserted) return false; // Already processed
  await applyWorkspaceEntitlement(tx, event);
  return true;
});

Sırası karışmış olayları ve çakışan abonelikleri yönetin

Webhook'ların kullanıcının tıkladığı sırayla ulaşacağı garanti edilmez. customer.subscription.updated olayı checkout.session.completed olayından önce gelebilir ve yanlışlıkla iki ödeme sekmesi açan bir müşteri aynı çalışma alanında kısa süreliğine iki aboneliğe sahip olabilir.

İptalleri ve ödeme hatalarını olayı tetikleyen belirli abonelik kimliğiyle sınırlandırın. Bir çalışma alanının ücretli erişim hakkını iptal etmeden önce, başka bir etkin aboneliğin o çalışma alanını hâlâ kapsayıp kapsamadığını kontrol edin; aksi takdirde yinelenen bir aboneliği iptal etmek, müşterinin ödemeye devam ettiği erişimi sessizce ortadan kaldırır.

  • Abonelik durumunu yalnızca workspaceId ile değil, providerSubscriptionId ile anahtarlayın.
  • En az bir abonelik active veya trialing olduğu sürece çalışma alanının ücretli erişim hakkını etkin tutun.
  • Tek seferlik siparişleri karşılamadan önce ödenen tutarın ve para biriminin beklenen SKU ile eşleştiğini doğrulayın.

SaaS faturalandırma webhook'ları hakkında sık sorulan sorular

Bu sorular, bir ekip faturalandırmayı sandbox ödeme testlerinden gerçek üretim trafiğine taşıdığında ortaya çıkar.

  • Ödeme başarı yönlendirmesinde ücretli özellikleri açabilir miyim? — Hayır. Yönlendirme URL'leri yer imlerine eklenebilir, paylaşılabilir veya eşzamansız ödeme yöntemleri tamamlanmadan önce ziyaret edilebilir. Doğrulanmış webhook erişimi verirken başarı sayfasını sunucu durumunu yoklamak için kullanın.
  • Yinelenen bir webhook hangi durum kodunu döndürmelidir? — Veritabanınız olayın zaten işlendiğini doğruladığı anda 200 OK döndürün. 4xx veya 5xx döndürmek sağlayıcıya yeniden denemeye devam etmesini söyler.
  • Webhook yeniden denemelerini yerel olarak nasıl test ederim? — Birebir aynı imzalı yükü iki kez gönderen deterministik birim testleri yazın ve yalnızca tek bir lisans, erişim hakkı veya işlemsel e-posta üretildiğini doğrulayın.
  • Mobil uygulama içi satın alımlar aynı modele nasıl uyar? — Web ve mobil satın alımların birbirinin üzerine yazmaması için RevenueCat sunucu bildirimlerini aynı idempotent erişim hakkı tablosu üzerinden yönlendirin; yayın öncesi kontroller için lansman kontrol listesine bakın.

Sık sorulan sorular

14 soru

supastack nedir?

supastack, YZ destekli SaaS ürünleri oluşturmak için modüler bir temeldir. Ortak altyapıyı bir araya getirir, böylece ürüne özgü özelliklere odaklanabilirsiniz.

Web ve mobil destekliyor mu?

Evet. supastack, web ve mobil deneyimler için başlangıç noktası olarak Next.js ve Expo uygulamaları içerir.

Hangi kimlik doğrulama dahil?

Better Auth; e-posta/parola, OTP ve sosyal girişi kapsar. Kişisel çalışma alanları hesapla birlikte sağlanır, böylece ürün kayıtları ilk günden itibaren sınırlı kalır.

YZ'yi nasıl destekliyor?

supastack; yapılandırılabilir çok sağlayıcılı YZ, kullanım takibi, bütçeler ve şifreli kendi-anahtarını-getir desteği içerir.

Abonelikler nasıl ele alınıyor?

Kit üzerine kurulu ürünler için Stripe faturalama, abonelikler ve yetkilendirmeler ücretli erişim temelini oluşturur. Sağlayıcı gizli anahtarları yalnızca sunucuda kalır.

Ürüne özgü kodum nerede durur?

Ürüne özgü özellikler ayrı bir kod tabanında kalırken ürün, supastack'in temel modüllerini yapılandırır ve entegre eder.

supastack fiyatlandırması nasıl çalışır?

Kit, ömür boyu güncellemeli tek seferlik bir satın alımdır: 249 $'a Single Stack — Next.js (Web), Expo (iOS ve Android) veya Tauri (macOS ve Windows Masaüstü) depolarından biri —, 398 $'a istediğiniz iki depoyla Dual Stack ya da üç deponun tamamını ve tam monorepo'yu içeren 399 $'lık Full Bundle; ek geliştirici koltuğu başına 75 $. Kit aboneliği yok. Bu sitedeki ödeme, lisans anahtarınızı e-postayla teslim eder ve depo erişimi GitHub davetiyle sağlanır.

Çalışma alanları verilerimi nasıl sınırlar?

Kullanıcıya ait kayıtlar workspaceId ile sınırlanır. Kimlik, yetkilendirmeler, YZ kullanımı, faturalama olayları ve denetim kayıtları kişisel çalışma alanı sınırının içinde kalır.

Arka plan işleri nasıl çalışır?

Trigger.dev açık iş çalışma zamanıdır. JobRunner sözleşmesi yalnızca enqueue, status ve cancel vaat eder — iş akışı semantiği Trigger.dev'e özgü ve açık kalır.

E-posta, tanıtım yazılarından nasıl farklı?

İşlemsel e-posta, tamamlanma bildirimleri gibi ürün iş akışlarını kapsar. Soğuk tanıtım ve kampanyalar, bu kitin e-posta modülünün dışında kalan ürün konularıdır.

Mobil satın almalar nasıl çalışır?

Mobil uygulama RevenueCat ile yerel satın alma ve geri yükleme sunar; erişimi sunucunuz doğrular. Yayınlamadan önce mağaza hesaplarını yapılandırın ve cihaz testlerini tamamlayın. Web faturalandırması ayrı kalır.

YZ sağlayıcı anahtarları nerede durur?

Kendi-anahtarını-getir kimlik bilgileri şifrelidir ve yalnızca sunucuda kalır. Anahtarlar günlüklerden gizlenir; kullanım ve bütçeler çalışma alanı düzeyinde görünür kalır.

Ödeme hangi ödeme yöntemlerini kabul ediyor?

Stripe tarafından barındırılan ödeme sayfası kartları kabul eder; cüzdanlar Stripe hesabında etkinleştirildiğinde Alipay ve WeChat Pay de sunabilir — Çin'deki alıcılar için kullanışlı. Cüzdan yetkilendirmeleri eşzamansız onaylanır; ödeme tamamlanır tamamlanmaz lisans sayfası güncellenir.

Geri ödeme alabilir miyim?

Depoya erişim kazandıktan sonra supastack sonsuza kadar sizin olur, bu nedenle geri ödeme yapılamaz. Ancak içiniz rahat olsun: kendiniz veya müşterileriniz için sınırsız sayıda ürün geliştirip yayınlamak üzere ömür boyu güncellemeler içeren eksiksiz kaynak koduna sahip olursunuz. Yalnızca kodu yeniden satamaz veya depoyu bir başlangıç kiti ya da şablon olarak yeniden dağıtamazsınız. Tek bir istisna vardır: lansman nakit iadesi — supastack ile geliştirilmiş canlı bir ürün yayınlayın, paylaşın ve 60 gün içinde 5.000 organik görüntüleme veya 50 organik beğeniye ulaşarak ödediğiniz tutarın %50'sini geri alın; ayrıntılar için koşullara bakın.

TR