Payment Orchestrator
Subscription
Pricing & Portal SDK

Pricing & Portal SDK

Abonelikleri kendi web sitenizden satın. İki yapı taşı bunu mümkün kılar:

  1. Public Catalog API — plan, ürün ve marka bilgilerinizi publishable key ile sunan, tarayıcıdan güvenle çağrılabilen salt-okunur bir API. Pricing tablonuzu canlı veriden render edin; fiyatı tek yerden değiştirin, siteniz otomatik takip etsin.
  2. Portal JS SDK — cdn.lunixi.com üzerinden tek script; ziyaretçiyi "plan seç"ten "kart kaydedildi, abonelik aktif"e taşır. Hazır portal UI'ı kullanın, kendi komponentlerinizle tamamen headless çalışın ya da ikisini karıştırın.

Harici checkout'a yönlendirme yok. Kart verisi SDK'nın güvenli formunda toplanır ve sunucunuza asla uğramaz.

Tutarlar minor-unit (kuruş) integer'dır: "price": 49900 = ₺499,00.

Parçalar nasıl birleşir

Anonim ziyaretçi ve kimliği belli müşteriMimari: anonim ziyaretçi tarayıcıdan yayınlanabilir anahtarla herkese açık katalog ucuna gider; kimliği belli müşteri için oturum sizin sunucunuzda gizli anahtarla açılır. İkisi de aynı fiyat tablosunu besler.Anonim ziyaretçitarayıcı · publishable keySizin backend’inizsecret key · sunucu-sunucuGET /api/public/v1/subscription/*POST /subscriptions/portal/sdk-sessionFiyat tablosuAÇIKLAMATARAYICIUÇORTAK ÇIKTI
Gizli anahtar tarayıcıya inmez. Anonim tarafta yalnız yayınlanabilir anahtarla okunan herkese açık katalog vardır; müşteriye özel fiyat ve oturum sizin sunucunuzdan geçer.
  • Kayıt öncesi ziyaretçinin tek ihtiyacı fiyatlardır: publishable key ile çalışan catalog katmanı.
  • Abonelik anında ziyaretçi müşteriye dönüşür: backend'iniz o müşteri için bir SDK oturumu açar ve işi tarayıcıdaki SDK bitirir — ya da aboneliği backend'iniz Merchant API ile kendisi oluşturur.

Katman 1 — Public Catalog API

Doğrudan tarayıcıdan çağrılmak üzere tasarlanmış salt-okunur endpoint'ler:

MethodPathDöndürdüğü
GET/api/public/v1/subscription/plansAktif planlar (fiyat, para birimi, deneme, faturalama ritmi, vitrin özellikleri)
GET/api/public/v1/subscription/productsAktif ürünler
GET/api/public/v1/subscription/brandingLogo, marka renkleri, kullanım koşulları / gizlilik URL'leri

Kimlik doğrulama: publishable key + Origin allow-list

Catalog çağrıları Authorization header'ında publishable key'inizle (mk_live_pk_…) doğrulanır. Client-side koda gömülmesi güvenlidir — yalnızca public kataloğu okuyabilir; asla para hareketi yapamaz, müşteri verisi açamaz.

Tüm yüzeyi iki anahtar korur; ikisi de merchant panelinden yönetilir (Portal → SDK ayarları):

  • Origin allow-list — API'yi çağıracak her origin'i ekleyin (örn. https://www.example.com). Listede olmayan origin'lerden gelen istekler reddedilir.
  • SDK enabled — gömülü yüzeyin ana şalteri.
⚠️

Publishable key tasarım gereği herkese açıktır; başka sitelerin kataloğunuzu kendi sitesiymiş gibi kullanmasını engelleyen şey Origin allow-list'tir. Listeyi dar tutun — yalnızca pricing sayfanızı sunduğunuz origin'leri ekleyin. Ed25519 özel anahtarınız ve ondan bastığınız erişim token'ı (at_live_…) tarayıcıda asla görünmemelidir.

İstek

curl "https://api-gateway.lunixi.com/api/public/v1/subscription/plans?locale=tr" \
  -H "Authorization: Bearer mk_live_pk_xxxxxxxxxxxx" \
  -H "Origin: https://www.example.com"

locale parametresi katalog çevirilerini döndürür — locale=tr gönderin, tanımladığınız yerlerde yerelleştirilmiş ad ve açıklamalar gelsin.

Yanıt

Planlar data dizisinde döner; cursor pagination için nextPageToken gelir:

{
  "success": true,
  "data": [
    {
      "id": "pln_9f3a1c",
      "productId": "prd_4b2e88",
      "code": "pro-monthly",
      "name": "Pro Aylık",
      "description": "Büyüyen ekipler için",
      "price": 49900,
      "currency": "TRY",
      "trialPeriodDays": 7,
      "billing": { "frequency": "MONTHLY", "interval": 1 }
    }
  ],
  "nextPageToken": ""
}

Katman 2 — Portal JS SDK

Tek script, dört mod:

<script src="https://cdn.lunixi.com/lunixi-portal.min.js"></script>
ModGiriş noktasıNe verirOturum
0 — CatalogLunixi.portal.catalog(cfg)Pricing veri client'ı (plans / products / branding)Yok — sadece publishable key
1 — Tam portalLunixi.portal.mount(target, cfg)Sayfanıza render edilen eksiksiz, markalı portal UIsdkMode: 'FULL'
2 — HeadlessLunixi.portal.create(cfg)Saf API client — UI'ın %100'ü sizdesdkMode: 'HEADLESS'
3 — HibritLunixi.portal.plans.mount(...) / Lunixi.portal.cardForm.mount(...)Kendi layout'unuz içinde tekil widget'lar (plan seçici, güvenli kart formu)sdkMode: 'HYBRID'

Mod 0 — pricing verisi, sıfır backend

const catalog = Lunixi.portal.catalog({
  publishableKey: 'mk_live_pk_xxxxxxxxxxxx',
  locale: 'tr',
});
 
const plans    = await catalog.plans();     // tüm aktif planlar
const products = await catalog.products();  // tüm aktif ürünler
const branding = await catalog.branding();  // logo, renkler, koşul URL'leri

Bu, aynı Public Catalog API'sinin sarılmış hâlidir — ziyaretçi henüz hiçbir yerde oturum açmamışken pricing tablosunu render etmek için idealdir.

Oturum bootstrap'ı — oturumu backend'iniz üretir

Mod 1–3 belirli bir müşteri adına çalışır; bu yüzden yalnızca backend'inizin oluşturabileceği bir oturuma ihtiyaç duyar. Tarayıcı secret key'inizi asla görmez; onun yerine kısa ömürlü, origin'e bağlı bir oturum alır.

POST /api/v1/subscriptions/portal/sdk-session — Bearer (at_live_…), yalnızca server-to-server:

curl -X POST https://api-gateway.lunixi.com/api/v1/subscriptions/portal/sdk-session \
  -H "Authorization: Bearer at_live_5f3e2b1a..." \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "<customer-id>",
    "origin": "https://www.example.com",
    "sdkMode": "HYBRID"
  }'

Yanıt — { portalToken, sessionProof, proofNonce, proofExpiresAt } — tarayıcıdaki SDK'ya iletilir. İstenen origin allow-list'inizde olmalı; sdkMode da panelde etkinleştirdiğiniz modlardan biri olmalıdır.

En basit bağlantı: SDK'ya bootstrap endpoint'inizin URL'sini verin, oturumu kendisi çeksin:

const portal = Lunixi.portal.create({
  sessionBootstrapUrl: '/portal-session', // yukarıdaki backend endpoint'iniz
});
await portal.init(); // oturumu doğrular, branding ve izinleri yükler

Mod 1 — hazır portal UI

<div id="portal"></div>
<script>
  Lunixi.portal.mount('#portal', {
    sessionBootstrapUrl: '/portal-session',
    locale: 'tr',
  });
</script>

Self-servis portalın tamamını render eder — planlar, aktif abonelik, faturalar, kart yönetimi — yapılandırdığınız marka görünümüyle. Tek div, bitti.

Mod 2 — headless

Tüm portal yetenekleri düz async metotlar olarak; her şeyi siz render edersiniz:

const portal = Lunixi.portal.create({ sessionBootstrapUrl: '/portal-session' });
await portal.init();
 
// Müşteri sahipliğini tek kullanımlık kodla (e-posta/SMS) kanıtlar:
await portal.requestOtp();
await portal.verifyOtp(inputtanGelenKod);   // → session JWT client içinde tutulur
 
const me       = await portal.me();
const plans    = await portal.plans();
const subs     = await portal.subscriptions();
const invoices = await portal.invoices();
 
// Kayıtlı kart / kart token'ı ile abonelik başlatın:
await portal.startSubscription({
  planId: secilenPlan.id,
  paymentMethodToken: cardToken,
});

Portal oturumları OTP doğrulamalıdır: init() sonrasında müşteri, hesap verisine veya paraya dokunan herhangi bir çağrıdan önce tek kullanımlık kodu onaylar. İptal gibi hassas işlemler ek olarak e-posta onay token'ı ister — bkz. Müşteri Portalı.

Mod 3 — hibrit widget'lar

Sayfa düzeniniz sizde kalsın, yalnızca zor parçaları ödünç alın:

// Kendi layout'unuz içinde hazır plan seçici:
Lunixi.portal.plans.mount('#plans', {
  sessionBootstrapUrl: '/portal-session',
  onSelect: (plan) => showCheckout(plan),
});
 
// PCI-güvenli kart formu — kart verisi kodunuza hiç değmez:
Lunixi.portal.cardForm.mount('#card', {
  sessionBootstrapUrl: '/portal-session',
  onSuccess: (cardToken) => subscribe(cardToken),
});

Kart formu asla kendiniz yazmamanız gereken parçadır: PAN'ı SDK'nın güvenli bağlamında toplar ve size yalnızca bir token geri verir.

Uçtan uca: pricing tablosu → plan → aktif abonelik

Pricing tablosunu render edin (Mod 0)

const catalog = Lunixi.portal.catalog({ publishableKey: 'mk_live_pk_xxxxxxxxxxxx' });
const [plans, branding] = await Promise.all([catalog.plans(), catalog.branding()]);
 
for (const plan of plans) {
  renderPlanCard(plan, {
    onSubscribe: () => startSignup(plan), // ₺: plan.price / 100 olarak biçimlendirin
  });
}

Müşteriyi kimliklendirin

Ziyaretçi bir plan seçtiğinde onu kendi kayıt/giriş akışınızdan geçirin. Ardından backend'iniz müşteriyi upsert eder (PUT /api/v1/subscriptions/customers, kendi externalId'nizle) ve bir SDK oturumu üretir (POST /api/v1/subscriptions/portal/sdk-session).

Kartı toplayın (Mod 3 kart formu)

Lunixi.portal.cardForm.mount('#card', {
  sessionBootstrapUrl: '/portal-session',
  onSuccess: (cardToken) => activate(plan, cardToken),
});

Aboneliği başlatın — eşit derecede geçerli iki yol

Müşteri her şeyi tarayıcıda, OTP doğrulamalı portal oturumu içinde tamamlar:

const portal = Lunixi.portal.create({ sessionBootstrapUrl: '/portal-session' });
await portal.init();
await portal.requestOtp();
await portal.verifyOtp(inputtanGelenKod);
 
const sub = await portal.startSubscription({
  planId: plan.id,
  paymentMethodToken: cardToken,
});
// sub.status → `subscription.activated` düştüğü anda erişimi açın

Son müşteri başındayken ve onay + doğrulamayı uçtan uca platformun yönetmesini istediğinizde en iyi seçenek.

Webhook'larla teyit edin

Erişimi HTTP yanıtına göre değil, subscription.created / subscription.activated event'lerine göre açın. Kullanıcı akışın ortasında sekmeyi kapatsa bile gerçeğin kaynağı webhook'tur.

Hatalar

DurumNeredeAnlamı / ne yapmalı
401CatalogPublishable key eksik veya geçersiz — Authorization: Bearer mk_live_pk_… header'ını kontrol edin.
403Catalog / SDKOrigin allow-list'te değil ya da SDK yüzeyi / istenen sdkMode kapalı — panelin portal ayarlarından düzeltin.
401Portal çağrılarıOturum süresi doldu veya OTP doğrulanmadı — init() → requestOtp() → verifyOtp() akışını yeniden çalıştırın.
400sdk-sessioncustomerId / origin eksik — ikisi de zorunludur.
404sdk-sessionBilinmeyen customerId — önce müşteriyi upsert edin.
409startSubscriptionIdempotent tekrar çakışması — SDK idempotency key'leri işlem başına kapsamlar; güvenle yeniden deneyin ya da kendi idempotencyKey'inizi geçin.

Webhook'lar

EventNe zaman
subscription.createdPortal start veya Merchant API ile abonelik oluşturuldu.
subscription.activatedAbonelik canlıya geçti — erişimi şimdi açın.
card.addedKart formu tamamlandı, kart vault'a girdi.
invoice.created / invoice.paidİlk faturalama döngüsü çalıştı.

Sonraki adımlar

  • Başlarken — aynı zincir, tamamen server-side
  • Planlar — deneme süreleri, billing kuralları, dunning
  • Müşteri Portalı — hosted portal linkleri, OTP akışı, izinler
  • Ürünler — çok dilli pricing sayfaları için katalog çevirileri