Payment Orchestrator
Subscription
Müşteri Portalı

Müşteri Portalı

Müşteri portalı, son müşterilerinizin aboneliklerini destek ekibinize dokunmadan kendilerinin yönetmesini sağlar — fatura görüntüleme, kart güncelleme, plan değiştirme, iptal. Erişim, cp_xxx token'ı taşıyan bir portal linki ile başlar ve e-posta OTP ile sıkılaştırılır: linki elinde tutmak tek başına oturum açmaya yetmez.

Portal endpoint'leri https://api-gateway.lunixi.com/api/portal/v1 altındadır ve müşterinin tarayıcısı tarafından çağrılır (hosted portal veya sizin embed'iniz) — asla secret key'inizle değil.

Nasıl çalışır

Portal linki oluşturun

Portal linkleri merchant panelinden, belirli bir müşteri için oluşturulur. Link, kapsamı sınırlı ve süreli bir cp_xxx token'ı taşır.

Token'ı doğrulayın

Portal (hosted veya gömülü), token ile POST /api/portal/v1/validate çağırır. Yanıt, markanızı ve bu portal oturumunun kullanabileceği izin kümesini döner.

Müşteriyi OTP ile doğrulayın

POST /api/portal/v1/otp/request müşterinin kayıtlı e-postasına tek kullanımlık kod gönderir ve bir otpChallengeId döner; POST /api/portal/v1/otp/verify challenge id + kodu bir session JWT (sessionToken) ile takas eder.

Oturumu kullanın

Okuma endpoint'leri (profil, planlar, abonelikler, faturalar, kartlar) ve session/revoke JWT'yi x-portal-session header'ında gönderir. Self-service yazma aksiyonları (subscriptions/start, subscriptions/cancel, subscriptions/change-plan, cards/update) ise aynı JWT'yi istek gövdesinde sessionToken olarak taşır.

Portal token'ını doğrulama

curl -X POST https://api-gateway.lunixi.com/api/portal/v1/validate \
  -H "Content-Type: application/json" \
  -d '{ "token": "cp_9f3ab27c41d0" }'
{
  "success": true,
  "requireEmailVerification": true,
  "effectivePlanSelectionMode": "SELF_SERVICE",
  "allowedPlanIds": ["plan_01JX2G3M8V"],
  "permissions": {
    "canStartSubscription": true,
    "canCancelSubscription": true,
    "canChangePlan": true,
    "canUpdatePaymentMethod": true,
    "canViewInvoices": true,
    "canDownloadInvoicePdf": true
  },
  "brandLogoUrl": "…",
  "brandPrimaryColor": "#4F46E5",
  "supportEmail": "[email protected]",
  "customerFirstName": "Ayşe",
  "customerEmailMasked": "a***@example.com",
  "hasActiveSubscription": false
}

permissions, link için yapılandırdığınız izinleri yansıtan can* boolean'larıdır — yalnızca true olan aksiyonları gösterin.

OTP doğrulaması

# 1) kodu müşterinin kayıtlı e-postasına gönderin
curl -X POST https://api-gateway.lunixi.com/api/portal/v1/otp/request \
  -H "Content-Type: application/json" \
  -d '{ "token": "cp_9f3ab27c41d0" }'
# → { "otpChallengeId": "…", "channel": "EMAIL",
#     "destinationMasked": "a***@example.com", "expiresAt": "…" }
 
# 2) challenge id + kodu session JWT ile takas edin
curl -X POST https://api-gateway.lunixi.com/api/portal/v1/otp/verify \
  -H "Content-Type: application/json" \
  -d '{
    "token": "cp_9f3ab27c41d0",
    "otpChallengeId": "1f0c7f2e-3f4a-4b1e-9a34-1f4f0b6c2d81",
    "otpCode": "482913"
  }'

OTP, müşterinin sistemde kayıtlı e-posta adresine gider — portal linki tek başına hesap üzerinde işlem yapmaya asla yetmez. Oturumlar kısa ömürlü JWT'lerdir; süresi dolduğunda OTP adımını tekrarlayın.

Portal verilerini okuma

Tüm okuma endpoint'leri session JWT'yi x-portal-session header'ında alır:

MethodPathDöner
GET/api/portal/v1/meMüşterinin profili
GET/api/portal/v1/plansAbone olunabilecek / geçilebilecek planlar
GET/api/portal/v1/subscriptionsMüşterinin abonelikleri
GET/api/portal/v1/invoicesFatura geçmişi
GET/api/portal/v1/cardsKayıtlı kartlar (maskeli)
curl https://api-gateway.lunixi.com/api/portal/v1/subscriptions \
  -H "x-portal-session: <session-jwt>"

Self-service aksiyonlar

Dört yazma aksiyonunun tamamı session JWT'yi istek gövdesinde sessionToken olarak alır (x-portal-session header'ında değil):

MethodPathNe yapar
POST/api/portal/v1/subscriptions/startKart formundan gelen paymentMethodToken ile yeni abonelik başlatır — idempotencyKey zorunlu
POST/api/portal/v1/subscriptions/cancelİptal eder — emailConfirmationToken gerektirir (aşağıda)
POST/api/portal/v1/subscriptions/change-planBaşka bir plana geçirir (newPlanId) — o da emailConfirmationToken gerektirir
POST/api/portal/v1/cards/updateÖdeme kartını değiştirir (paymentMethodToken)
POST/api/portal/v1/session/revokeÇıkış — session JWT'yi anında geçersiz kılar (header ile)

Abonelik başlatma

curl -X POST https://api-gateway.lunixi.com/api/portal/v1/subscriptions/start \
  -H "Content-Type: application/json" \
  -d '{
    "sessionToken": "<session-jwt>",
    "planId": "plan_01JX2G3M8V",
    "paymentMethodToken": "pmt_01JX7D2W5K",
    "idempotencyKey": "portal-start-84172"
  }'

İptal — e-posta onayıyla

⚠️

İptal, aktif oturumun üstüne bir de emailConfirmationToken gerektirir. Müşteri e-postasına bir onay linki/token'ı alır ve cancel çağrısı bunu sunmak zorundadır. Bu bilinçli bir fraud korumasıdır: ele geçirilmiş bir tarayıcı oturumu — veya sızmış bir portal linki — müşterinin posta kutusuna erişemeden bir aboneliği sessizce sonlandıramaz.

curl -X POST https://api-gateway.lunixi.com/api/portal/v1/subscriptions/cancel \
  -H "Content-Type: application/json" \
  -d '{
    "sessionToken": "<session-jwt>",
    "subscriptionId": "sub_01JX5B8R3M",
    "cancelAtPeriodEnd": true,
    "emailConfirmationToken": "ect_01JX8E4Y7N"
  }'

Hosted portal vs SDK embed

Hosted portalSDK embed
Nerede çalışırPortal linkinin doğrudan açtığı hazır sayfaKendi uygulamanızın/sitenizin içinde
EforuSıfır front-end işi; markanız validate yanıtından uygulanırYerleşimi siz kontrol edersiniz; akışları SDK yürütür
En uygunHızlı çıkış, e-posta odaklı self-servicePortalın ürün arayüzünüzün doğal parçası olması

Gömme için JS SDK şunları sunar: Lunixi.portal.mount() (hazır portal UI), Lunixi.portal.create() (headless — aynı akışların üstüne kendi arayüzünüzü kurarsınız) ve Lunixi.portal.plans.mount() / cardForm.mount() gibi hibrit parçalar. Embed, sunucu tarafında secret key'inizle POST /api/v1/subscriptions/portal/sdk-session üzerinden başlatılır (customerId, origin, sdkMode: FULL | HEADLESS | HYBRID). Tam SDK referansı: Pricing & Portal SDK.

Hatalar

DurumAnlamıNe yapmalı
400Bozuk token veya eksik alanlarcp_ token'ını ve gövde şeklini kontrol edin
401Geçersiz/süresi dolmuş cp_ token, yanlış OTP veya süresi dolmuş session JWTPortal linkini yeniden oluşturun ya da OTP adımını tekrarlayın
403Aksiyon linkin permissions kümesinde yok, ya da cancel'da emailConfirmationToken eksik/geçersizYalnızca izinli aksiyonları sunun; iptal için e-posta onayını yeniden başlatın
404Bu müşteriye ait abonelik/kart bulunamadıPortal oturumu müşteri kapsamlıdır — id'leri portal okumalarından alın

Webhooks

Portal aksiyonları, merchant-API karşılıklarıyla aynı event'leri üretir; backend'iniz self-service değişiklikleri otomatik duyar:

Portal aksiyonuEvent'ler
subscriptions/startsubscription.created, subscription.activated, invoice.created/invoice.paid
subscriptions/cancelsubscription.canceled
subscriptions/change-plansubscription.upgraded / subscription.downgraded / subscription.plan_change_scheduled
cards/updatecard.added, card.set_default, subscription.payment_card_swapped

Sonraki adımlar