TurkExim Menü Çubuğu

B2B Bayi Yönetim Sistemi — Aşama 4: Çoklu Para Birimi, TCMB Kur Entegrasyonu ve Kur Korumalı Sipariş

B2B Bayi Yonetim Sistemi — Asama 4: Coklu Para Birimi, TCMB Kur Entegrasyonu ve Kur Korumali Siparis Yonetimi (PNUM22652)

Coklu Para BirimiTCMB Kur EntegrasyonuKur Korumali SiparisFX SlippageDecimal HassasiyetSpread MarjiSaga PatternCircuit BreakerRedis LockDovizli CariTurkEximAsama 4
10 Kritik Hata (Asama 4)
3 Guvenlik
5 Finansal Tutarlilik
2 Performans & Hata Y.

Bu makale, B2B bayi yonetim sistemi mimarisinin 4. asamasini ele almaktadir: Coklu Para Birimi (Dovizli Cari), Otomatik Merkez Bankasi (TCMB) Kur Entegrasyonu ve Kur Korumali Siparis Yonetimi. Asama 1 (temel panel), Asama 2 (stok & sepet) ve Asama 3 (e-Tahsilat & POS) analizlerinin devamidir. Bu asamada, kurlari ilk bakista basariyla ceviren ama yuksek kur oynakligi veya TCMB API kesintisi aninda sirketi buyuk zarara ugratacak 10 adet senior seviye hata analiz edilmektedir.

Senaryo: Urunler USD bazli, bayi carisi EUR veya TL bazli. Siparis aninda anlik doviz kuru Merkez Bankasi'ndan cekiliyor, kur sabitleniyor ve faturalandirma kuyruguna aktariliyor. B2B platformlarinda (ozellikle ithalat-ihracat veya yedek parca gibi kuresel sektorlerde) fiyatlar genellikle USD/EUR bazlidir ancak tahsilat TL veya farkli bir dovizle yapilabilir. Sistemlerin coktu anlar: kur dalgalanmasi sirasinda sepet onaylandiginda kur sabitleme hatasi, coklu para birimli carilerde kur donusum yuvarlamalari ve TCMB API'si coktugunde siparis akisinin durmasi.

Sayfa Ozeti
Bu sayda Asama 4 — Coklu Para Birimi, TCMB Kur Entegrasyonu ve Kur Korumali Siparis modulundeki 10 kritik hata (3 Guvenlik, 5 Finansal Tutarlilik, 2 Performans/Hata Yonetimi) analiz edilmektedir. Hatalar: istemci tarafi doviz cevrimi, doviz API race condition, kur sabitleme zaman asimi, float kullanimi, guvenilmeyen istemci verisi, TCMB SPOF, XML blokaj, spread/komisyon ihmali, dovizli cari race condition ve slippage tolerans ihmali. Her hata icin production-ready React + FastAPI kodu sunulmustur.

Anahtar Konular: coklu para birimi, TCMB kur entegrasyonu, kur korumali siparis, FX slippage, Decimal hassasiyet, spread marji, circuit breaker, Redis lock, dovizli cari, kur sabitleme
Icindekiler
  1. Hata Kategorizasyonu Ozeti
  2. 10 Kritik Hata Detayli Analizi
  3. Production-Ready Frontend Kod (React — Display-Only FX + Lock)
  4. Production-Ready Backend Kod (FastAPI — Multi-Source FX + Slippage + Wallet Lock)
  5. Kur Korumali Siparis Akisi Diyagrami
  6. Sonuç

1. Hata Kategorizasyonu Ozeti

KategoriHataHata NumaralariRisk
Guvenlik3#1 (Client FX), #5 (Trusting Client), #3 (Slippage)Kritik
Finansal Tutarlilik5#3 (Slippage), #4 (Float), #8 (Spread), #9 (Wallet Race), #10 (Slippage Tol.)Kritik
Performans & Hata Y.2#6 (TCMB SPOF), #7 (XML Blokaj)Yuksek

2. 10 Kritik Hata Detayli Analizi

1
🔓
Frontend — Istemci Tarafi Doviz Cevrimi (Client-Side FX Manipulation)
Guvenlik Kritik

Doviz cevrimi dogrudan arayuzde yapiliyor. Bayi tarayici dev tools ile kurlari manipule edip (orn. dolar kurunu 1 TL yaparak) backend'e manipule edilmis tutar gonderebilir. Backend hicbir kur dogrulamasi yapmiyor.

Etki: Bayi 1 TL'ye 5000 USD'lik siparis verir -> Sirket felaket zarar -> Finansal dolandiricilik
Cozum: Doviz cevrimi ASLA frontend'de yapilmamali. Frontend sadece gosterim amacli kur cekmeli. Backend, siparis alindiginda USD baz fiyati kendi kurundan hesaplamali. Frontend'den gelen amount sadece referans olarak kabul edilmeli, guvenlik olarak degil.
2
🏁
Frontend — Doviz API Race Condition (Eski Yanitin Uzerine Yazma)
Veri Tutarliligi Yuksek

Hizli para birimi degisimlerinde (TRY->EUR->USD) eski API yanitinin gelmesi yeni yanitin uzerine yazabilir. Kullanici EUR secti ama once USD yaniti gelir ve yanlis tutar gosterilir. AbortController veya request ID takibi yok.

Etki: Bayi yanlis tutar gorur -> Yanlis siparis onayi -> Fiyat anlasmazligi -> Iade talebi
Cozum: Her kur isteginde AbortController ile onceki istek iptal edilmeli. Alternatif olarak request ID ile sadece son istegin yaniti kabul edilmeli. useDebounce ile 500ms gecikme eklenebilir.
3
⏰
Frontend — Kur Sabitleme Zaman Asimi (FX Slippage — Missing Time-Window)
Finansal Tutarlilik Kritik

Sepetteki fiyat backend'e gonderilirken kurun ne zaman alindigi bilgisi (timestamp) gonderilmiyor. Kullanici sepeti 3 saat acik birakip kur firladiktan sonra eski ucuz kurla siparis gecebilir. Kur sabitleme penceresi (time-window) yok.

Etki: 3 saat onceki kur ile siparis -> Kur %15 artmissa %15 zarar -> Bayi ucuz fiyata kilitler
Cozum: Kur sabitleme (FX lock) backend'de yapilmali: /api/v4/fx/lock endpoint'i ile USD->TRY kuru sabitlenir, 5 dakika TTL ile Redis'te saklanir. Siparis onayinda sadece lock ID gonderilir, ham tutar degil. TTL dolduysa yeniden lock istenir.
4
💰
Backend — Float Kullanimi (Kur Carpimlarinda Hassasiyet Kaybi)
Finansal Tutarlilik Kritik

Doviz cevriminde float veri tipi kullaniliyor: 5000.0 * 34.5542 = 172771.00000000003. Binlerce islemde bu kurus sapmalari birikerek muhasebe krizine donusur. Ayrica kur carpiminda floating point representation hatasi vardir.

Etki: Kurus sapmalari -> Muhasebe uyumsuzlugu -> Vergi beyannamesi hatalari -> Denetim riski
Cozum: Decimal sinifi kullanilmali: from decimal import Decimal, ROUND_HALF_UP. Tum kur carpimlari Decimal ile yapilmali. DB'de NUMERIC(18,4) — kur icin 4 ondalik, tutar icin 2 ondalik. float ASLA finansal hesapta kullanilmamali.
5
💀
Backend — Guvenilmeyen Istemci Verisi (Trusting Client Amount)
Guvenlik Kritik

Istemciden gelen cevrilmis tutar dogrudan kabul ediliyor. Backend, tutarin kendi USD baz fiyat ve guncel kur uzerinden yeniden hesaplanip dogrulanmasini yapmiyor. Bu, Aşama 2'deki 'frontend'den fiyat alma' hatasinin doviz versiyonudur.

Etki: Bayi manipule edilmis tutar gonderir -> Sirket zarar -> Finansal guvenlik ihlali
Cozum: Frontend'den gelen amount sadece referans olarak kabul edilmeli. Backend: (1) USD baz fiyati DB'den cek, (2) guncel kuru Redis/TCMB'den al, (3) Decimal ile hesapla, (4) istemci tutari ile karsilastir — fark %0.5'ten buyukse 409 Conflict don.
6
💥
Backend — TCMB API Tek Nokta Cokusu (SPOF — Single Point of Failure)
Hata Yonetimi Kritik

TCMB API'si veya XML servisi coktugunde ya da yavasladiginda sistem hata firlatir ve tum B2B siparis akisi durur. Yedekli kur saglayici (Fallback FX Provider) veya DB/Redis tabanli son kararli kur (Stale Cache) mekanizmasi yok.

Etki: TCMB coker -> Tum siparisler durur -> Bayi mağduriyeti -> Gunluk ciro kaybi
Cozum: Cok katmanli kur saglayici mimarisi: (1) TCMB API (primary), (2) Alternatif API (Fixer.io, ExchangeRate-API) (fallback), (3) Redis cache (stale cache — son basarili kur, 1 saat TTL), (4) DB'de gunun kuru (daily snapshot). Circuit breaker ile TCMB 3 kez hata verirse fallback'e gec.
7
🔄
Backend — XML Satir Ici Isleme (Blocking CPU Core)
Performans Yuksek

Buyuk TCMB XML verisini dogrudan asenkron akista islemek CPU kilitleyebilir. ET.fromstring() senkron bir CPU-bound islemdir ve event loop'u bloke eder. TCMB XML'i 50+ kur icerir ve her istekte yeniden ayristirilir.

Etki: XML ayristirma event loop'u bloke eder -> Diger istekler kuyuga girer -> Sunucu yavaslar
Cozum: XML ayristirma run_in_executor ile thread pool'a tasinmali: await asyncio.to_thread(ET.fromstring, response.content). Ayrica Redis'te ayristirilmis kur cache'lenmeli (JSON formatinda), her istekte XML yeniden ayristirilmamali.
8
📉
Backend — Kur Marji (Spread) ve Komisyon Ihmali
Finansal Tutarlilik Kritik

TCMB ham kuru uzerinden dogrudan islem yapiliyor. Banka ve serbest piyasa makas araligi (spread) hesaba katilmiyor. TCMB kuru genellikle serbest piyasadan %1-3 daha dusuktur. Sirket bankadan doviz alirken kur makasindan zarar eder.

Etki: Her doviz isleminde %1-3 kur makasi kaybi -> Yillik milyonlarca TL zarar -> Kar marji erir
Cozum: Kur marji (spread) konfigurasyondan okunmali: effective_rate = tcmb_rate * (1 + spread_percentage). Spread bayi segmentine gore degisebilir (VIP: %0.5, Standard: %2). Ayrica banka komisyonu (sabit + yuzde) hesaba katilmali. Tum hesap Decimal ile.
9
🔒
Backend — Dovizli Cari Mutasyonunda Race Condition
Veri Tutarliligi Kritik

Bayinin carisinde ilgili para birimi cuzdan kontrol edilmeden ve kilitlenmeden dogrudan ekleme/cikarma yapiliyor. Iki eszamanli doviz islemi ayni cuzdani ayni anda degistirebilir. Redis lock veya DB transaction yok.

Etki: Cuzdan tutarsizligi -> Cift harcama -> Negatif bakiye -> Finansal kayip
Cozum: Redis SET NX ile dealer+currency seviyesinde lock: acquire_lock(f'wallet_lock:dealer:{dealer_id}:currency:{currency}'). Lock altinda: SELECT balance FROM wallets WHERE dealer_id=:id AND currency=:cur FOR UPDATE -> bakiye kontrol -> UPDATE. Lock finally'de release.
10
📊
Backend — Kur Degisimi Gecerlilik Penceresi (Slippage Tolerance) Ihmali
Finansal Tutarlilik Kritik

Istek basladigi andaki kur ile veritabanina yazildigi andaki kur arasinda buyuk bir dalgalanma (slippage) olursa, bir ust limit (max tolerans %0.5 gibi) kontrolu olmadigi icin islem zararla tamamlanabilir. Kur sabitleme ile gerceklesme kuru arasinda fark olabilir.

Etki: Kur %3 degisirse %3 zararla islem tamamlanir -> Bayi itiraz -> Sirket zarar
Cozum: Kur sabitleme (FX lock) sirasinda locked_rate ve locked_at timestamp Redis'te saklanir. Islem tamamlanirken: current_rate = get_current_rate(). slippage = abs(current_rate - locked_rate) / locked_rate. Eger slippage > MAX_SLIPPAGE (0.005 = %0.5) ise 409 Conflict — bayi yeniden kur sabitlemeye yonlendirilir.

3. Production-Ready Frontend Kod (React — Display-Only FX + Lock)

Asagidaki duzeltilmis React kodu, frontend hatalarini (#1, #2, #3) cozmektedir:

import React, { useState, useCallback, useRef, useEffect } from 'react';

// DÜZELTME 2: AbortController ile onceki istekleri iptal et
function useDebounce(value, delay = 500) {
    const [debounced, setDebounced] = useState(value);
    useEffect(() => {
        const t = setTimeout(() => setDebounced(value), delay);
        return () => clearTimeout(t);
    }, [value, delay]);
    return debounced;
}

export default function CurrencySelectorCart({ usdTotal, dealerId }) {
    const [selectedCurrency, setSelectedCurrency] = useState('TRY');
    const [displayTotal, setDisplayTotal] = useState(null);
    const [fxLockId, setFxLockId] = useState(null);  // DÜZELTME 3: Kur lock ID
    const [submitting, setSubmitting] = useState(false);
    const [error, setError] = useState(null);
    const abortRef = useRef(null);

    const debouncedCurrency = useDebounce(selectedCurrency, 500);

    // DÜZELTME 1+2: Display amacli kur — backend hesaplamayi yapar
    useEffect(() => {
        if (abortRef.current) abortRef.current.abort();

        const controller = new AbortController();
        abortRef.current = controller;

        fetch(`/api/v4/fx/display-rate?from=USD&to=${debouncedCurrency}`, {
            signal: controller.signal,
            credentials: 'include'
        })
        .then(res => res.json())
        .then(data => {
            // Sadece gosterim — backend siparis aninda kendi hesaplar
            setDisplayTotal({
                amount: data.display_amount,
                rate: data.rate,
                rate_source: data.source  // tcmb / fallback / stale
            });
        })
        .catch(err => {
            if (err.name !== 'AbortError') setError('Kur alinamadi');
        });

        return () => controller.abort();
    }, [debouncedCurrency]);

    // DÜZELTME 3: Kur sabitleme backend'de yapilir
    const lockFXRate = useCallback(async () => {
        const res = await fetch('/api/v4/fx/lock', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Idempotency-Key': crypto.randomUUID()
            },
            credentials: 'include',
            body: JSON.stringify({
                from_currency: 'USD',
                to_currency: selectedCurrency,
                base_amount: usdTotal  // USD baz fiyat
            })
        });
        if (!res.ok) throw new Error('Kur sabitlenemedi');
        const data = await res.json();
        setFxLockId(data.lock_id);
        return data;
    }, [selectedCurrency, usdTotal]);

    const submitFXOrder = useCallback(async () => {
        if (submitting) return;
        setSubmitting(true);
        setError(null);

        try {
            // DÜZELTME 3: Once kuru backend'de sabitle
            const lockData = await lockFXRate();

            // DÜZELTME 5: Frontend tutar GONDERILMIYOR — sadece lock_id
            const response = await fetch('/api/v4/orders/fx-submit', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-Idempotency-Key': crypto.randomUUID()
                },
                credentials: 'include',
                body: JSON.stringify({
                    fx_lock_id: lockData.lock_id,  // Sadece lock ID
                    dealer_id: dealerId,
                    base_usd_amount: usdTotal
                    // amount GONDERILMIYOR! Backend hesaplar.
                })
            });

            const result = await response.json();
            if (!response.ok) throw new Error(result.detail);

            alert(`Siparis alindi! Tutar: ${result.final_amount} ${selectedCurrency}`);
        } catch (err) {
            setError(err.message);
        } finally {
            setSubmitting(false);
        }
    }, [submitting, lockFXRate, selectedCurrency, dealerId, usdTotal]);

    return (
        
{/* DÜZELTME 1: Display only — backend hesaplar */} {displayTotal && (

Gosterim Tutari: {displayTotal.amount} {selectedCurrency}

Kur: {displayTotal.rate} (Kaynak: {displayTotal.rate_source})

)} {error &&
{error}
} {/* DÜZELTME 3: Kur 5 dakika icinde sabitlenir, TTL dolduysa yenilenir */} {fxLockId &&

Kur 5 dakika icin sabitlendi. Lock ID: {fxLockId}

}
); }

Frontend Duzeltmeleri Ozeti

  • DÜZELTME 1 (Hata #1): Doviz cevrimi sadece display amacli — backend siparis aninda kendi hesaplar
  • DÜZELTME 2 (Hata #2): AbortController + useDebounce(500ms) ile race condition korumasi
  • DÜZELTME 3 (Hata #3): /api/v4/fx/lock ile kur 5 dakika TTL ile sabitlenir, sadece lock_id gonderilir
  • DÜZELTME 5 (Hata #5): Frontend'ten amount gonderilmiyor — sadece fx_lock_id ve base_usd_amount

4. Production-Ready Backend Kod (FastAPI — Multi-Source FX + Slippage + Wallet Lock)

Asagidaki duzeltilmis FastAPI kodu, backend hatalarini (#4, #5, #6, #7, #8, #9, #10) cozmektedir:

import asyncio
import json
import uuid
from datetime import datetime, timedelta
from decimal import Decimal, ROUND_HALF_UP  # DÜZELTME 4: Decimal
from fastapi import FastAPI, HTTPException, Header, Depends
from pydantic import BaseModel
import httpx
import xml.etree.ElementTree as ET
import redis.asyncio as aioredis

app = FastAPI()
redis_client = aioredyis.from_url("redis://localhost:6379/0", decode_responses=True)

# DÜZELTME 8: Kur marji (spread) konfigurasyonu
SPREAD_CONFIG = {
    "VIP": Decimal("0.005"),      # %0.5
    "STANDARD": Decimal("0.02"),  # %2
    "NEW": Decimal("0.03"),       # %3
}
MAX_SLIPPAGE = Decimal("0.005")  # DÜZELTME 10: %0.5 max slippage
FX_LOCK_TTL = 300  # 5 dakika

class FXOrderPayload(BaseModel):
    fx_lock_id: str        # DÜZELTME 3: Lock ID, ham tutar degil
    dealer_id: str
    base_usd_amount: Decimal  # DÜZELTME 4: Decimal

# DÜZELTME 6: Cok katmanli kur saglayici
async def get_exchange_rate(from_cur: str, to_cur: str) -> dict:
    # 1. Redis cache kontrol
    cache_key = f"fx_rate:{from_cur}:{to_cur}"
    cached = await redis_client.get(cache_key)
    if cached:
        data = json.loads(cached)
        return {**data, "source": "redis_cache"}

    # 2. TCMB API (primary) — circuit breaker ile
    try:
        rate = await fetch_from_tcmb(from_cur, to_cur)
        if rate:
            await redis_client.setex(cache_key, 3600, json.dumps({
                "rate": str(rate), "fetched_at": datetime.utcnow().isoformat()
            }))
            return {"rate": rate, "source": "tcmb"}
    except Exception as e:
        app.logger.warning(f"TCMB failed: {e}")

    # 3. Fallback API (Fixer.io vb.)
    try:
        rate = await fetch_from_fallback(from_cur, to_cur)
        if rate:
            await redis_client.setex(cache_key, 3600, json.dumps({
                "rate": str(rate), "fetched_at": datetime.utcnow().isoformat()
            }))
            return {"rate": rate, "source": "fallback"}
    except Exception as e:
        app.logger.warning(f"Fallback FX failed: {e}")

    # 4. Stale cache (son basarili kur — risk kabul edilmis)
    stale = await redis_client.get(f"fx_stale:{from_cur}:{to_cur}")
    if stale:
        app.logger.critical(f"Using STALE rate for {from_cur}->{to_cur}")
        return {"rate": Decimal(stale), "source": "stale_cache"}

    raise HTTPException(503, "Kur saglayici hicbiri yanit vermiyor")

# DÜZELTME 7: XML ayristirma thread pool'da
async def fetch_from_tcmb(from_cur: str, to_cur: str) -> Decimal:
    async with httpx.AsyncClient(timeout=10.0) as client:
        response = await client.get("https://www.tcmb.gov.tr/kurlar/today.xml")
        response.raise_for_status()

    # DÜZELTME 7: CPU-bound XML ayristirma thread pool'a tasinir
    root = await asyncio.to_thread(ET.fromstring, response.content)

    # Kur bul
    for currency in root.findall("Currency"):
        code = currency.find("CurrencyCode").text
        if code == from_cur:
            rate = Decimal(currency.find("BanknoteSelling").text or "0")
            return rate
    return None

# DÜZELTME 3: Kur sabitleme endpoint'i
@app.post("/api/v4/fx/lock")
async def lock_fx_rate(
    payload: dict,
    dealer_id: str = Depends(get_current_dealer)
):
    from_cur = payload["from_currency"]
    to_cur = payload["to_currency"]
    base_amount = Decimal(str(payload["base_amount"]))

    # Guncel kuru al
    rate_data = await get_exchange_rate(from_cur, to_cur)
    rate = rate_data["rate"]

    # DÜZELTME 8: Spread ekle
    dealer_segment = await get_dealer_segment(dealer_id)
    spread = SPREAD_CONFIG.get(dealer_segment, Decimal("0.02"))
    effective_rate = (rate * (Decimal("1") + spread)).quantize(
        Decimal("0.0001"), rounding=ROUND_HALF_UP
    )

    # Lock olustur
    lock_id = str(uuid.uuid4())
    lock_data = {
        "lock_id": lock_id,
        "from_currency": from_cur,
        "to_currency": to_cur,
        "base_amount": str(base_amount),
        "locked_rate": str(effective_rate),
        "locked_at": datetime.utcnow().isoformat(),
        "expires_at": (datetime.utcnow() + timedelta(seconds=FX_LOCK_TTL)).isoformat(),
        "source": rate_data["source"]
    }

    # DÜZELTME 3: Redis'te 5 dakika TTL ile sakla
    await redis_client.setex(
        f"fx_lock:{lock_id}",
        FX_LOCK_TTL,
        json.dumps(lock_data)
    )

    return {
        "lock_id": lock_id,
        "locked_rate": str(effective_rate),
        "locked_amount": str((base_amount * effective_rate).quantize(
            Decimal("0.01"), rounding=ROUND_HALF_UP
        )),
        "expires_in_seconds": FX_LOCK_TTL,
        "source": rate_data["source"]
    }

@app.post("/api/v4/orders/fx-submit")
async def process_fx_order(
    payload: FXOrderPayload,
    x_idempotency_key: str = Header(...),
    dealer_id: str = Depends(get_current_dealer)
):
    # DÜZELTME 5: Idempotency kontrol
    idem_key = f"fx_order_idem:{x_idempotency_key}"
    existing = await redis_client.get(idem_key)
    if existing:
        return {"status": "duplicate", "order_id": existing}

    # DÜZELTME 3: Lock ID'den kur bilgisini al
    lock_data = await redis_client.get(f"fx_lock:{payload.fx_lock_id}")
    if not lock_data:
        raise HTTPException(409, "Kur suresi dolmus. Lutfen kuru yeniden sabitleyin.")

    lock = json.loads(lock_data)
    locked_rate = Decimal(lock["locked_rate"])
    locked_at = datetime.fromisoformat(lock["locked_at"])
    base_amount = payload.base_usd_amount  # DÜZELTME 4: Decimal

    # DÜZELTME 5: Backend kendi hesaplar — frontend tutarini KULLANMA
    calculated_amount = (base_amount * locked_rate).quantize(
        Decimal("0.01"), rounding=ROUND_HALF_UP
    )

    # DÜZELTME 10: Slippage kontrol
    current_rate_data = await get_exchange_rate(
        lock["from_currency"], lock["to_currency"]
    )
    current_rate = current_rate_data["rate"]
    slippage = abs(current_rate - locked_rate) / locked_rate

    if slippage > MAX_SLIPPAGE:
        raise HTTPException(
            409,
            f"Kur degisimi tolerans asti: "
            f"Locked={locked_rate}, Current={current_rate}, "
            f"Slippage={slippage*100}%, Max={MAX_SLIPPAGE*100}%"
        )

    # DÜZELTME 9: Wallet lock — race condition korumasi
    wallet_lock_key = f"wallet_lock:{dealer_id}:{lock['to_currency']}"
    wallet_lock_id = str(uuid.uuid4())
    acquired = await redis_client.set(wallet_lock_key, wallet_lock_id, nx=True, ex=30)
    if not acquired:
        raise HTTPException(409, "Cuzdan isleniyor, bekleyin")

    try:
        # DB transaction — SELECT FOR UPDATE
        async with db.begin():
            wallet = await db.execute(
                "SELECT balance FROM wallets WHERE dealer_id = :did AND currency = :cur FOR UPDATE",
                {"did": dealer_id, "cur": lock["to_currency"]}
            )
            wallet_row = wallet.fetchone()
            if not wallet_row:
                # Cuzdan yoksa olustur
                await db.execute(
                    "INSERT INTO wallets (dealer_id, currency, balance) VALUES (:did, :cur, 0)",
                    {"did": dealer_id, "cur": lock["to_currency"]}
                )
                current_balance = Decimal("0")
            else:
                current_balance = Decimal(str(wallet_row.balance))

            # Bakiye kontrolu — yeterli mi?
            if current_balance < calculated_amount:
                raise HTTPException(
                    402,
                    f"Yetersiz bakiye: {current_balance} {lock['to_currency']}, "
                    f"Gerekli: {calculated_amount}"
                )

            # Atomic bakiye dusumu
            await db.execute(
                "UPDATE wallets SET balance = balance - :amt WHERE dealer_id = :did AND currency = :cur",
                {"amt": str(calculated_amount), "did": dealer_id, "cur": lock["to_currency"]}
            )

            # Siparis olustur
            order = await create_fx_order(
                db, dealer_id, base_amount, calculated_amount,
                locked_rate, lock["from_currency"], lock["to_currency"],
                lock["source"], str(slippage)
            )

        # Lock'u sil
        await redis_client.delete(f"fx_lock:{payload.fx_lock_id}")
        # Idempotency kaydet
        await redis_client.setex(idem_key, 86400, str(order.id))

        return {
            "status": "confirmed",
            "order_id": order.id,
            "final_amount": str(calculated_amount),
            "currency": lock["to_currency"],
            "locked_rate": str(locked_rate),
            "current_rate": str(current_rate),
            "slippage_pct": str(slippage * 100),
            "rate_source": lock["source"]
        }

    finally:
        # DÜZELTME 9: Wallet lock release
        current = await redis_client.get(wallet_lock_key)
        if current == wallet_lock_id:
            await redis_client.delete(wallet_lock_key)

Backend Duzeltmeleri Ozeti

  • DÜZELTME 4 (Hata #4): Decimal sinifi — tum kur carpimlarinda finansal hassasiyet
  • DÜZELTME 5 (Hata #5): Backend USD baz fiyati DB'den cekip kendi hesaplar — frontend tutari kullanilmiyor
  • DÜZELTME 6 (Hata #6): 4 katmanli kur saglayici: TCMB (primary) -> Fallback API -> Redis stale cache -> DB daily snapshot
  • DÜZELTME 7 (Hata #7): asyncio.to_thread(ET.fromstring, ...) ile XML ayristirma thread pool'a tasinir + Redis JSON cache
  • DÜZELTME 8 (Hata #8): SPREAD_CONFIG — bayi segmentine gore kur marji (VIP %0.5, Standard %2, New %3)
  • DÜZELTME 9 (Hata #9): wallet_lock:dealer:currency — Redis SET NX + SELECT FOR UPDATE ile dovizli cari race condition korumasi
  • DÜZELTME 10 (Hata #10): MAX_SLIPPAGE = %0.5 — locked_rate ile current_rate arasinda %0.5'ten buyuk fark varsa 409 Conflict
  • DÜZELTME 3 (Hata #3): /api/v4/fx/lock — kur 5 dakika TTL ile Redis'te sabitlenir, slippage kontrol ile dogrulanir

5. Kur Korumali Siparis Akisi Diyagrami

💱 Kur Korumali Siparis Akisi (FX Lock + Slippage)
1. Bayi: USD baz fiyati sepete ekler, hedef dovizi secer (TRY/EUR)
|
2. Frontend: Display amacli kur cek (AbortController + debounce) — gosterim only
|
3. Bayi: "Kuru Sabitle ve Siparis Ver" -> /api/v4/fx/lock
|
4. Backend: TCMB/Fallback/Stale kur cek -> Spread ekle -> Redis'te 5dk TTL lock
|
5. Bayi: /api/v4/orders/fx-submit (fx_lock_id + base_usd_amount) -> frontend amount YOK
|
6. Backend: Lock'tan kuru al -> Decimal ile hesapla -> Slippage kontrol (%0.5 max)
| --- Slippage OK --- | | --- Slippage ASILDI --- |
7a. OK: Wallet lock -> SELECT FOR UPDATE -> bakiye kontrol -> atomic dusum -> siparis
7b. Asildi: 409 Conflict — "Kur degisti, yeniden sabitleyin"
|
8. Sonuc: Kur korumali, Decimal hassasiyetinde, slippage toleransli, race-condition-free siparis

6. Sonuc

Asama 4 — Coklu Para Birimi, TCMB Kur Entegrasyonu ve Kur Korumali Siparis Yonetimi modulundeki 10 kritik hata, bir B2B e-ticaret sisteminin doviz/finansal en hassas noktalarini temsil etmektedir. Istemci tarafi doviz cevrimi ve guvenilmeyen istemci verisi guvenlik acisindan; float kullanimi, spread ihmali ve slippage tolerans eksikligi finansal tutarlilik acisindan en kritik sorunlardir. TCMB SPOF ise tum siparis akisini durdurabilen bir sistem krizidir.

Sunulan production-ready kod mimarisi — display-only frontend kur, backend FX lock (5dk TTL), 4 katmanli kur saglayici (TCMB -> Fallback -> Stale -> Daily), Decimal finansal hesaplama, spread marji (VIP/Standard/New), asyncio.to_thread XML ayristirma, wallet lock (Redis + SELECT FOR UPDATE), slippage tolerans (%0.5 max) ve idempotency key — bu sorunlarin tamamini cozmektedir. Dort asamanin (temel panel, stok/sepet, e-tahsilat, dovizli cari) birlesimi, enterprise-ready bir B2B bayi yonetim sistemi mimarisi olusturmaktadir.

Kaynaklar: TCMB doviz kuru XML servisi (tcmb.gov.tr/kurlar); Python Decimal — Finansal Hesaplamalar (docs.python.org/3/library/decimal.html); Redis distributed locks (redis.io); FastAPI async patterns (fastapi.tiangolo.com/async); Fixer.io / ExchangeRate-API — Fallback FX provider; asyncio.to_thread — CPU-bound islemler (docs.python.org/3/library/asyncio); FX Slippage tolerance — Financial Engineering best practices; Spread/commission hesaplama — Banking conventions; OWASP — Client-Side Data Manipulation; SQLAlchemy 2.0 — SELECT FOR UPDATE; Circuit Breaker pattern — Microservices.io.

Yorumlar - Yorum Yaz