Hızlı Başlangıç
Sıfırdan ilk başarılı API çağrısına. En kritik adım imzalama; onu çalışır kodla veriyoruz.
Ed25519 anahtar çifti üretin
Uzun ömürlü secret'ınız budur ve sunucunuzda kalır.
openssl genpkey -algorithm ed25519 -out lunixi-private.pem
openssl pkey -in lunixi-private.pem -pubout -out lunixi-public.pemlunixi-private.pem dosyasını asla depoya, tarayıcıya veya mobil uygulamaya koymayın. Sızarsa panelden anahtarı iptal edip yenisini üretmeniz gerekir.
Public anahtarı kaydedin
Panel (opens in a new tab)'e girin, public anahtarı (lunixi-public.pem) kaydedin. Karşılığında bir kid (key id) alırsınız — imzalı her isteğe bunu koyacaksınız.
Erişim token'ı basın
Aşağıdaki kod kanonik dizeyi kurar, imzalar ve token'ı alır. Ek bağımlılık yok — Node.js'in kendi crypto modülü yeterli.
import crypto from 'crypto';
import fs from 'fs';
const BASE = 'https://api-gateway.lunixi.com';
const KID = process.env.LUNIXI_KEY_ID!;
const privateKey = crypto.createPrivateKey(
fs.readFileSync(process.env.LUNIXI_PRIVATE_KEY_PATH!),
);
export async function mintToken() {
const method = 'POST';
const url = '/api/v1/auth/token';
const date = new Date().toISOString();
const nonce = crypto.randomUUID();
// 🔴 Bu istekte GÖVDE YOK → `Digest:` satırı kanonik dizeye HİÇ EKLENMEZ.
// Boş değerle eklerseniz imza tutmaz. Gövdeli isteklerde 5. satır olarak girer.
const canonical = [
method.toUpperCase(),
url,
`X-Date:${date}`,
`X-Nonce:${nonce}`,
].join('\n');
const signature = crypto
.sign(null, Buffer.from(canonical), privateKey)
.toString('base64');
const res = await fetch(BASE + url, {
method,
headers: {
'X-Key-Id': KID,
'X-Date': date,
'X-Nonce': nonce,
'X-Signature': signature,
},
});
if (!res.ok) {
const err = await res.json();
throw new Error(`${err.code}: ${err.message}`);
}
return res.json(); // { access_token, expires_in, expires_at, session_id, ... }
}Ed25519'da hash algoritması belirtilmez. crypto.sign(null, ...) — ilk argüman null olmalı. 'sha256' yazarsanız imza üretilir ama doğrulanmaz; 401 alır ve nedenini bulamazsınız.
Token'ı önbelleğe alın
expires_in tipik olarak 300 saniye. Her çağrıda yeni token basmayın:
let cached: { token: string; expiresAt: number } | null = null;
export async function getToken(): Promise<string> {
const now = Math.floor(Date.now() / 1000);
if (cached && cached.expiresAt - 30 > now) return cached.token;
const t = await mintToken();
cached = { token: t.access_token, expiresAt: t.expires_at };
return cached.token;
}30 saniyelik pay, çağrı yoldayken token'ın sona ermesini engeller.
İlk çağrınızı yapın
Fraud kararı iyi bir başlangıçtır — para hareket ettirmez, hemen sonuç döner:
const token = await getToken();
const res = await fetch(
'https://api-gateway.lunixi.com/api/v1/fraud/decisions/evaluate',
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
subjectType: 'card_payment',
payment: { amount: 250000, currency: 'TRY', is3D: false },
customer: { id: 'cust_84213' },
}),
},
);
const decision = await res.json();
if (decision.decision === 'block') {
throw new Error(`Engellendi: ${decision.reasonCodes.join(', ')}`);
}
if (decision.decision === 'force_3d') {
// İşlemi 3DS'e yükseltin
}Bilmeniz gereken üç şey
1 — Tutarlar minör birim. 250000 = ₺2.500,00. Ondalık gönderirseniz tutar 100 kat yanlış olur.
2 — decision küçük harf. Değerler allow · force_3d · review · block. Büyük harfle karşılaştırma yapmayın.
3 — Hiç kural yayınlamadıysanız allow almazsınız. Yapılandırılmamış merchant fraud.decision.unconfigured koduyla varsayılan kararı (tipik review) alır. "Kural yazmadım" ≠ "risk yok". → Akışlar ve Çözümleme
İlk çağrı 401 dönerse
| Belirti | Sebep |
|---|---|
INVALID_DATE | X-Date sunucu saatinden ±5 dakikadan uzak — sunucunuzun saatini kontrol edin |
| İmza tutmuyor | Gövdesiz istekte Digest: satırını eklemiş olabilirsiniz — eklenmemeli. Satır sırası ve başlık yazımı da birebir olmalı |
| Gövdeli istekte tutmuyor | Digest başlığı SHA-256= önekli olmalı: SHA-256=<base64> |
| Yine tutmuyor | crypto.sign'ın ilk argümanı null mı? |
403 | IP izinli listede değil, ya da merchant askıya alınmış |
Sonraki adımlar
- Kimlik Doğrulama — kanonik dize ve Step-Up ayrıntısı
- Karar API'si — gönderebileceğiniz tüm alanlar
- Webhook'lar — olayları almak