Hızlı başlangıç
Hedef: uçtan uca gerçek bir test-modu ödemesi almak. Sonunda sunucunuzdan oluşturulmuş bir oturumunuz, alıcının tarayıcısında açılan bir checkout’unuz ve localhost’unuza teslim edilmiş imzalı bir webhook’unuz olacak.
Bir test API anahtar çiftine (pk_test_… + sk_test_…) ve bir
webhook imzalama secret’ına (whsec_…) ihtiyacınız var. Bunları
satıcı panelinde Geliştiriciler → API
anahtarları ve Geliştiriciler → Webhook’lar altında oluşturun.
1. Tarayıcı SDK’sını yükleyin
npm
npm install @lartech/infraio-checkout-js2. Bir checkout oturumu oluşturun (sunucu)
Oturumlar sunucunuzdan oluşturulur ve secret anahtarınızla HMAC-SHA256
kullanılarak imzalanır. sk_’yi asla tarayıcıya koymayın.
Kanonik imzalama dizesini oluşturun
METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODYGövdesi {...} olan ve 1715990400 unix timestamp’inde yapılan bir
POST /b2b/v1/checkout-sessions/quick isteği için imzalama girdisi:
POST
/b2b/v1/checkout-sessions/quick
1715990400
{"items":[...],"currency":"USD","success_url":"..."}HMAC-SHA256(secret_key, signingString) ile imzalayın, hex olarak çıktı alın.
İsteği gönderin
import { fetch } from "undici";
import { createHmac } from "node:crypto";
const PUBLIC_KEY = process.env.INFRAIO_PUBLIC_KEY!; // pk_test_…
const SECRET_KEY = process.env.INFRAIO_SECRET_KEY!; // sk_test_…
const BASE_URL = "https://api-dev.infraio.xyz";
const path = "/b2b/v1/checkout-sessions/quick";
const body = JSON.stringify({
items: [
{
sku: "tee-large-blue",
label: "Indigo Tee — L",
unit_price: "49.00",
quantity: 1,
currency: "USD",
image_url: "https://your-shop.test/img/tee.png",
},
],
currency: "USD",
customer_email: "[email protected]",
success_url: "https://your-shop.test/return?status=success",
cancel_url: "https://your-shop.test/return?status=cancel",
external_ref: "ord_1234", // sizin sipariş id'niz; siparişte saklanır, webhook'larda yansıtılmaz — order_id üzerinden arayın
expires_in: 1800, // saniye; varsayılan 30 dk
idempotency_key: crypto.randomUUID(), // yeniden denemek güvenlidir
});
const timestamp = Math.floor(Date.now() / 1000).toString();
const signingInput = ["POST", path, timestamp, body].join("\n");
const signature = createHmac("sha256", SECRET_KEY)
.update(signingInput)
.digest("hex");
const res = await fetch(`${BASE_URL}${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Client-ID": PUBLIC_KEY,
"X-Timestamp": timestamp,
"X-Signature": signature,
},
body,
});
// Her API yanıtı bir zarfa sarılır — asıl yük `data` altındadır.
// Üst düzeydeki `code`/`message`'ı sonuç işaretçisi olarak değerlendirin
// (başarı için `201` / `"created"`).
const { code, message, data } = await res.json() as {
code: number;
message: string;
data: { session_key: string; checkout_url: string; order_id: string; expires_at: string };
};
const { session_key, checkout_url, order_id, expires_at } = data;
// session_key + checkout_url'yi tarayıcıya gönderin. order_id ve expires_at
// kendi kayıt tutmanız içindir.Saat sapması önemlidir. Geçit, sunucu saatinden 5 dakikadan fazla
sapan X-Timestamp’e sahip istekleri reddeder. Sunucularınızı NTP ile
senkronize edin; uzun süre çalışan bir cron’un duvar saatine güvenmeyin.
3. Checkout’u açın (tarayıcı)
Popup
import { loadInfraIo } from "@lartech/infraio-checkout-js";
const sdk = await loadInfraIo(process.env.NEXT_PUBLIC_INFRAIO_PK!);
sdk.checkout({
sessionId: session_key,
checkoutUrl: checkout_url,
mode: "popup", // varsayılan
onReady: () => console.log("checkout etkileşimli"),
onSuccess: ({ sessionId }) => {
// Yalnızca UX — aşağıdaki uyarıya bakın. Webhook yetkili olandır.
location.assign("/thanks");
},
onCancel: () => console.log("alıcı popup'ı kapattı"),
onError: (err) => console.error(err.code, err.message),
});4. Webhook’u işleyin (sunucu)
Webhook, para hareketinin tek yetkili sinyalidir. Tarayıcı callback’leri
(onSuccess) bazı testnet zamanlamalarında gerçek bir ödeme olmadan
tetiklenebilir — siparişi tarayıcı tarafı bir event’ten asla karşılamayın.
import { createHmac, timingSafeEqual } from "node:crypto";
const WEBHOOK_SECRET = process.env.INFRAIO_WEBHOOK_SECRET!; // whsec_…
export async function POST(req: Request) {
const raw = await req.text(); // HAM bayt, parse edilmemiş
const signature = req.headers.get("x-signature") ?? "";
const timestamp = req.headers.get("x-timestamp") ?? "";
// İmza `${timestamp}.${raw}` üzerinedir — yalnızca gövde DEĞİLDİR.
const expected = "sha256=" + createHmac("sha256", WEBHOOK_SECRET)
.update(`${timestamp}.${raw}`)
.digest("hex");
const a = Buffer.from(signature);
const b = Buffer.from(expected);
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return new Response("bad signature", { status: 400 });
}
// HTTP gövdesi event başına veri nesnesinin TA KENDİSİDİR (Stripe stili
// dış zarf yok). Event tipi + gönderim ID + timestamp başlıklarda yaşar.
const eventType = req.headers.get("x-event") ?? ""; // örn. "payment.settled"
const deliveryId = req.headers.get("idempotency-key") // X-Delivery'i yansıtır
?? req.headers.get("x-delivery") ?? "";
const payload = JSON.parse(raw);
switch (eventType) {
case "payment.settled": {
// Payload order_id, payment_intent_id, tx_hash, amount_received,
// confirmations, settlement token + network, vb. içerir.
// Siparişten gelen `external_ref` DAHİL DEĞİLDİR — geriye eşleştirmek
// gerekiyorsa sunucu tarafında order_id üzerinden arayın.
const { order_id, tx_hash, amount_received, confirmations } = payload;
// Siparişi idempotent şekilde ödendi olarak işaretleyin. Dedup
// anahtarı olarak deliveryId (X-Delivery) kullanın — aynı gönderimin
// tüm yeniden denemelerinde stabildir. 2xx olmayan yanıtlarda 6 kez
// (1m / 5m / 15m / 1h / 6h) yeniden deneriz.
break;
}
case "checkout.expired": { /* oturum TTL'i doldu — stoğu serbest bırakın */ break; }
case "payment.refund.requested": { /* admin kuyruğunda gösterin */ break; }
default:
// İleriye dönük uyumluluk: event ekleyebiliriz. Kabul edin ve
// hiçbir şey yapmayın.
}
return new Response("ok", { status: 200 });
}Uç durumlar (gövde normalizasyonu, secret rotation, replay koruması) için Webhook’lar → İmza doğrulama sayfasına bakın.
5. Bir test ödemesi tetikleyin
Test modu oturumları gerçek testnet’lere karşı tahsil edilir (Sepolia, Base Sepolia, BSC Testnet, vb.) — mock chain yoktur. İlgili faucet’ten test fonu alın, ardından checkout sayfasının gösterdiği depozito adresine gönderin. Onaylar temizlendiğinde webhook tetiklenir (çoğu testnet’te 1 onay).
Sırada ne var
- Kavramlar → Oturumlar — üç katmanlı veri modeli (CheckoutSession + Order + PaymentIntent).
- Kavramlar → Zincirler ve varlıklar — hangi ağ ve tokenlerin canlı ve test ortamında desteklendiği.
- Webhook’lar → Genel bakış — her event tipi ve onu neyin tetiklediği.
- Güvenlik → API anahtarları — scope’lar, rotation, rate limit’ler.