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:
| Method | Path | Döner |
|---|---|---|
GET | /api/portal/v1/me | Müşterinin profili |
GET | /api/portal/v1/plans | Abone olunabilecek / geçilebilecek planlar |
GET | /api/portal/v1/subscriptions | Müşterinin abonelikleri |
GET | /api/portal/v1/invoices | Fatura geçmişi |
GET | /api/portal/v1/cards | Kayı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):
| Method | Path | Ne yapar |
|---|---|---|
POST | /api/portal/v1/subscriptions/start | Kart 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-plan | Baş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 portal | SDK embed | |
|---|---|---|
| Nerede çalışır | Portal linkinin doğrudan açtığı hazır sayfa | Kendi uygulamanızın/sitenizin içinde |
| Eforu | Sıfır front-end işi; markanız validate yanıtından uygulanır | Yerleşimi siz kontrol edersiniz; akışları SDK yürütür |
| En uygun | Hızlı çıkış, e-posta odaklı self-service | Portalı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
| Durum | Anlamı | Ne yapmalı |
|---|---|---|
400 | Bozuk token veya eksik alanlar | cp_ token'ını ve gövde şeklini kontrol edin |
401 | Geçersiz/süresi dolmuş cp_ token, yanlış OTP veya süresi dolmuş session JWT | Portal linkini yeniden oluşturun ya da OTP adımını tekrarlayın |
403 | Aksiyon linkin permissions kümesinde yok, ya da cancel'da emailConfirmationToken eksik/geçersiz | Yalnızca izinli aksiyonları sunun; iptal için e-posta onayını yeniden başlatın |
404 | Bu 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 aksiyonu | Event'ler |
|---|---|
subscriptions/start | subscription.created, subscription.activated, invoice.created/invoice.paid |
subscriptions/cancel | subscription.canceled |
subscriptions/change-plan | subscription.upgraded / subscription.downgraded / subscription.plan_change_scheduled |
cards/update | card.added, card.set_default, subscription.payment_card_swapped |
Sonraki adımlar
- Pricing & Portal SDK — portalı veya pricing kataloğunu gömün
- Abonelikler — aynı yaşam döngüsünün merchant tarafı
- Faturalar — müşterinin fatura sekmesinde gördükleri