TurkExim Menü Çubuğu

B2B Bayi Yönetim Sistemi — Aşama 2: Akıllı Stok, Çoklu Fiyat Listesi ve Sepet Yönetimi

B2B Bayi Yönetim Sistemi — Aşama 2: Akıllı Stok, Çoklu Fiyat Listesi ve Sepet Yönetimi (PNUM22951)

B2B Stok YönetimiSepet YönetimiRace ConditionFloat PrecisionIdempotency KeyPartial FailureRedis LockDecimal FiyatFastAPI TransactionTurkExim Yazılım MimarisiAşama 2
10 Yeni Kritik Hata
3 Güvenlik
5 Veri/Finansal Tutarlılık
2 Frontend Hata Y.

Bu makale, Aşama 1'de analiz edilen 10 kritik hatanın devamıdır. Aşama 2'de, B2B bayi yönetim sisteminin en kritik modülü — Akıllı Stok, Çoklu Fiyat Listesi ve Sepet Yönetimi — modülünde production ortamında ortaya çıkacak 10 yeni senior-seviye hata analiz edilmektedir. Senaryo: Kampanya dönemi, yüzlerce bayi aynı anda sınırlı stoktaki popüler ürüne hücum ediyor.

B2B sistemlerinde en büyük krizler üç anda yaşanır: (1) Black Friday/Kampanya anı — yüzlerce bayi aynı anda son stoku çekmeye çalışır, (2) Dinamik matris fiyatlandırma — bayi gruplarına göre farklı iskonto oranları hesaplanırken float yuvarlama hataları, (3) Sepette anlık fiyat/stok güncellenmesi — sepet onayı sırasında fiyat değişirse veya stok tükenirse. Bu makale, bu üç kriz anını tetikleyecek 10 hatayı detaylıca analiz eder.

Sayfa Özeti
Bu sayda Aşama 2 — Akıllı Stok, Çoklu Fiyat Listesi ve Sepet Yönetimi modülündeki 10 yeni kritik hata (3 Güvenlik, 5 Veri/Finansal Tutarlılık, 2 Frontend Hata Yönetimi) analiz edilmektedir. Hatalar: derived state antipattern, doğrudan state mutasyonu, idempotency key eksikliği, kontrolsüz girdi, frontend'den fiyat alma, float precision hatası, fiyat tutarlılık kontrolü yok, race condition (over-selling), state güvensiz mutasyon ve partial failure (atomisite eksikliği). Her hata için production-ready düzeltilmiş React ve FastAPI kodu sunulmuştur.

Anahtar Konular: B2B stok yönetimi, sepet yönetimi, race condition, float precision, idempotency key, partial failure, Redis lock, Decimal fiyat, FastAPI transaction, over-selling, derived state
İçindekiler
  1. Hata Kategorizasyonu Özeti
  2. 10 Yeni Kritik Hata Detaylı Analizi
  3. Production-Ready Frontend Kod (React — Sepet & Idempotency)
  4. Production-Ready Backend Kod (FastAPI — Lock, Decimal, Transaction)
  5. Kampanya Dönemi Stok Akışı Diyagramı
  6. Sonuç ve Sonraki Aşama

1. Hata Kategorizasyonu Özeti

KategoriHataHata NumaralarıRisk
🔒 Güvenlik3#4 (Input), #5 (Fiyat), #7 (Fiyat Kontrol)Kritik
🔑 Veri/Finansal Tutarlılık5#1 (Derived State), #2 (Mutasyon), #6 (Float), #8 (Race), #9 (State), #10 (Partial)Kritik
⚠️ Frontend Hata Y.2#1 (Derived), #3 (Idempotency)Yüksek

2. 10 Yeni Kritik Hata Detaylı Analizi

Aşağıda her bir hatanın detaylı analizi — sorunun tanımı, production'daki etkisi ve önerilen çözüm:

1
🔄
Frontend — Derived State Antipattern (Toplam Fiyat Senkronizasyon Kaybı)
Veri Tutarlılığı Yüksek

Sepet toplam fiyatı ayrı bir state'te (totalPrice) tutuluyor ve useEffect ile senkronize ediliyor. Bu React'in 'derived state' antipattern'idir. State güncellemeleri batched olarak yapıldığında ara toplamlar bir karelik gecikmeyle gelir, bayi yanlış toplam görür.

Etki: Bayi yanlış toplam tutar görür → Yanlış sipariş onayı → Fiyat anlaşmazlığı
Çözüm: Toplam fiyat ayrı state'te tutulmamalı. useMemo ile cartItems'tan derived edilmeli: const totalPrice = useMemo(() => cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0), [cartItems]);
2
🚫
Frontend — Doğrudan State Mutasyonu (Nesne Referans Hatası)
Veri Tutarlılığı Kritik

updateQuantity fonksiyonunda sepet öğesi doğrudan mutasyona uğruyor: item.quantity = newQty. Bu React'in immutable state kuralını ihlal eder. Ayrıca önceki state güvenceye alınmıyor (functional update pattern eksik).

Etki: React re-render tetiklenmeyebilir → UI tutarsız → Bayi güncellenmemiş miktar görür
Çözüm: setCartItems(prev => prev.map(item => item.id === productId ? {...item, quantity: newQty} : item)); — Immutable update ve functional update pattern.
3
💳
Frontend — Idempotency Key Eksikliği (Çift Sipariş)
Finansal Tutarlılık Kritik

Sipariş Onayla butonuna çift tıklanırsa iki ayrı sipariş oluşur. Hiçbir debounce, loading state veya idempotency key mekanizması yok. Ağ yavaş olduğunda kullanıcı tekrar tıklar.

Etki: Çift sipariş → Çift stok düşümü → Çift tahsilat → Bayi itiraz → İade süreci
Çözüm: Buton disabled state + UUID idempotency key header (X-Idempotency-Key) backend'e gönderilmeli. Backend bu key'i Redis'te 24 saat saklayıp aynı key ile gelen tekrar istekleri reddetmeli.
4
🔢
Frontend — Kontrolsüz Sayısal Girdi (Uncontrolled Input)
Güvenlik Yüksek

Miktar input'unda sayısal doğrulama yok. NaN, negatif değer, ondalık sayı girilebilir. parseInt(e.target.value) NaN dönebilir ve bu backend'e gider.

Etki: NaN/negatif miktar → Backend hatası veya yanlış sipariş → Stok bozulması
Çözüm: Input'a min=1, step=1, parseInt yerine Number() + isNaN kontrolü + Math.max(1, Math.floor(value)). Ayrıca Pydantic validator backend'de de kontrol etmeli.
5
💀
Backend — Frontend'den Fiyat Alma (Güvenlik Dehşeti)
Güvenlik Kritik

client_side_price parametresi frontend'den güvenilerek alınıyor! Bir bayi browser dev tools ile fiyatı 0.01 TL yapabilir. Backend hiçbir fiyat doğrulaması yapmıyor.

Etki: Bayi 0.01 TL'ye sipariş verir → Şirket felaket zarar → Muhtemel dolandırıcılık
Çözüm: Frontend'den fiyat ASLA kabul edilmemeli. Backend fiyatı kendi veritabanından/iskonto matrisinden hesaplamalı. client_side_price tamamen kaldırılmalı.
6
💰
Backend — Float Precision (Finansal Hassasiyet) Hatası
Finansal Tutarlılık Kritik

İskonto hesabında float veri tipi kullanılıyor: 5000.00 * (1 - 0.0853) = 4573.499999999999. Binlerce siparişte bu kuruş sapmaları birikerek muhasebe krizine dönüşür.

Etki: Kuruş sapmaları → Muhasebe tutarsızlığı → Vergi beyannamesi hataları → Denetim riski
Çözüm: Python Decimal sınıfı kullanılmalı: from decimal import Decimal, ROUND_HALF_UP. Tüm fiyat hesapları Decimal ile yapılmalı, veritabanında NUMERIC(15,2) tipi kullanılmalı.
7
🤫
Backend — Fiyat Tutarlılık Kontrolü Yok (Silent Price Mismatch)
Güvenlik Yüksek

Frontend'den gelen fiyat ile backend hesaplanan fiyat uyuşmuyor ama işlem sadece print ile logluyor ve sessizce devam ediyor. Bu bir fail-safe mekanizması değil, fail-silent antipattern.

Etki: Fiyat uyuşmazlığı sessizce geçilir → Yanlış fiyatla sipariş → Audit trail eksik
Çözüm: Fiyat uyuşmazlığı durumunda HTTP 409 Conflict döndürülmeli. Bayi sepeti güncellemeye yönlendirilmeli. Tüm uyuşmazlıklar structured logging ile kaydedilmeli.
8
🏁
Backend — Race Condition (Stok Over-Selling)
Veri Tutarlılığı Kritik

Stok kontrolü (if product['stock'] < item.quantity) ile stok düşme (product['stock'] -= item.quantity) arasında 0.1 saniye bekleme var. İki istek aynı anda kontrol yaparsa ikisi de stoku yeterli görür. Son stok 2 kez satılır.

Etki: Eksi stok → Tedarik edilemeyen sipariş → Bayi güven kaybı → Şirket itibar zedelenmesi
Çözüm: Redis SET NX ile distributed lock veya PostgreSQL SELECT FOR UPDATE ile pessimistic locking. Stok kontrolü ve düşüm atomik bir operation olmalı.
9
⚠️
Backend — State Güvenliği Olmadan Doğrudan Mutasyon
Veri Tutarlılığı Yüksek

db_inventory sözlüğü global bir Python değişkeni ve doğrudan mutasyona uğruyor: product['stock'] -= item.quantity. Çoklu worker'da her worker'ın kendi belleği farklı. Ayrıca hiçbir lock mekanizması yok.

Etki: Multi-worker ortamında stok tutarsız → Worker A stok=3, Worker B stok=5 görür → Fiyat/sipariş tutarsız
Çözüm: Stok veritabanında (PostgreSQL) tutulmalı, Redis cache sadece read-through için. Stok güncellemesi UPDATE ... SET stock = stock - :qty WHERE stock >= :qty RETURNING * sorgusu ile atomik yapılmalı.
10
💥
Backend — Kısmi Başarısızlık (Partial Failure / Atomisite Eksikliği)
Veri Tutarlılığı Kritik

Sepette 3 ürün var. 1. ürünün stoğu düşülür, 2. üründe stok yetersiz hatası çıkar. Ama 1. ürünün stok düşümü geri alınmaz. Transaction yok, rollback yok. Stok havada asılı kalır.

Etki: Kısmi stok düşümü → Stok kaybı → Sipariş oluşmadı ama stok düştü → Muhasebe uyumsuzluğu
Çözüm: Tüm sipariş tek bir DB transaction içinde yapılmalı: async with db.begin(): ... Hata durumunda otomatik ROLLBACK. Redis lock'lar da finally bloğunda release edilmeli.

3. Production-Ready Frontend Kod (React — Sepet & Idempotency)

Aşağıdaki düzeltilmiş React kodu, frontend hatalarını (#1, #2, #3, #4, #5) çözmektedir:

import React, { useState, useMemo, useCallback } from 'react';

// DÜZELTME 3: Idempotency key üretici
function generateIdempotencyKey() {
    return `${Date.now()}-${crypto.randomUUID()}`;
}

export default function B2BOrderCart({ initialItems }) {
    const [cartItems, setCartItems] = useState(initialItems);
    const [submitting, setSubmitting] = useState(false);
    const [error, setError] = useState(null);

    // DÜZELTME 1: Derived state antipattern cozumu — useMemo
    const totalPrice = useMemo(() => {
        return cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0);
    }, [cartItems]);

    // DÜZELTME 2: Immutable update + functional update pattern
    const updateQuantity = useCallback((productId, newQty) => {
        // DÜZELTME 4: Kontrolsuuz girdi korumasi
        const parsedQty = Math.max(1, Math.floor(Number(newQty) || 1));

        setCartItems(prev =>
            prev.map(item =>
                item.id === productId
                    ? { ...item, quantity: parsedQty }
                    : item
            )
        );
    }, []);

    // DÜZELTME 3: Idempotency key + submitting state (double-click korumasi)
    const checkout = async () => {
        if (submitting) return; // Cift tiklama korumasi

        setSubmitting(true);
        setError(null);

        const idempotencyKey = generateIdempotencyKey();

        try {
            const response = await fetch('/api/v2/orders/confirm', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-Idempotency-Key': idempotencyKey  // DÜZELTME 3
                },
                credentials: 'include',
                body: JSON.stringify({
                    items: cartItems.map(item => ({
                        product_id: item.id,
                        quantity: item.quantity
                        // DÜZELTME 5: client_side_price GONDERILMIYOR!
                    }))
                })
            });

            if (!response.ok) {
                const err = await response.json();
                throw new Error(err.detail || 'Siparis basarisiz');
            }

            const result = await response.json();
            alert(`Siparis alindi! No: ${result.order_id}`);
        } catch (err) {
            setError(err.message);
        } finally {
            setSubmitting(false);
        }
    };

    return (
        
{error &&
{error}
} {cartItems.map(item => (
{item.name} - {item.price.toLocaleString('tr-TR')} TL {/* DÜZELTME 4: min=1, step=1 ile kontrol */} updateQuantity(item.id, e.target.value)} />
))}

Toplam: {totalPrice.toLocaleString('tr-TR')} TL

); }

Frontend Düzeltmeleri Özeti

  • DÜZELTME 1 (Hata #1): useMemo ile derived state — ayrı totalPrice state'i kaldırıldı
  • DÜZELTME 2 (Hata #2): Immutable update + functional update pattern setCartItems(prev => prev.map(...))
  • DÜZELTME 3 (Hata #3): X-Idempotency-Key header + submitting state ile çift tıklama koruması
  • DÜZELTME 4 (Hata #4): min=1, step=1, Math.max(1, Math.floor(Number(value))) ile girdi doğrulama
  • DÜZELTME 5 (Hata #5): client_side_price tamamen kaldırıldı — backend fiyatı kendisi hesaplar

4. Production-Ready Backend Kod (FastAPI — Lock, Decimal, Transaction)

Aşağıdaki düzeltilmiş FastAPI kodu, backend hatalarını (#5, #6, #7, #8, #9, #10) çözmektedir:

import asyncio
import uuid
from decimal import Decimal, ROUND_HALF_UP  # DÜZELTME 6: Decimal
from fastapi import FastAPI, HTTPException, Header, Depends
from pydantic import BaseModel, validator
from typing import List
import redis.asyncio as aioredis
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, text

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

# DÜZELTME 5: client_side_price KALDIRILDI
class CartItem(BaseModel):
    product_id: int
    quantity: int

    # DÜZELTME 4: Backend validasyonu
    @validator('quantity')
    def validate_quantity(cls, v):
        if v < 1:
            raise ValueError('Miktar 1\'den az olamaz')
        return v

# DÜZELTME 8: Redis distributed lock
async def acquire_lock(product_id: int, ttl: int = 10):
    lock_key = f"stock_lock:{product_id}"
    lock_id = str(uuid.uuid4())
    acquired = await redis_client.set(lock_key, lock_id, nx=True, ex=ttl)
    if not acquired:
        raise HTTPException(409, "Urun isleniyor, tekrar deneyin")
    return lock_key, lock_id

async def release_lock(lock_key: str, lock_id: str):
    current = await redis_client.get(lock_key)
    if current == lock_id:
        await redis_client.delete(lock_key)

@app.post("/api/v2/orders/confirm")
async def confirm_order(
    items: List[CartItem],
    x_idempotency_key: str = Header(...),  # DÜZELTME 3: Idempotency key
    db: AsyncSession = Depends(get_db)
):
    # DÜZELTME 3: Idempotency key kontrolu
    idem_key = f"idem:{x_idempotency_key}"
    existing = await redis_client.get(idem_key)
    if existing:
        return {"status": "duplicate", "order_id": existing}

    # DÜZELTME 10: Tum siparis TEK transaction
    locks = []
    try:
        async with db.begin():
            for item in items:
                # DÜZELTME 8: Lock al
                lk, lid = await acquire_lock(item.product_id)
                locks.append((lk, lid))

                # DÜZELTME 9: Atomik stok dusumu (SELECT FOR UPDATE + atomic decrement)
                result = await db.execute(
                    text("UPDATE products SET stock = stock - :qty WHERE id = :pid AND stock >= :qty RETURNING id, name, stock, base_price"),
                    {"qty": item.quantity, "pid": item.product_id}
                )
                row = result.fetchone()
                if not row:
                    raise HTTPException(
                        409,
                        f"Stok yetersiz: Urun ID {item.product_id}"
                    )

                # DÜZELTME 6: Decimal ile fiyat hesabi
                base_price = Decimal(str(row.base_price))
                discount_ratio = Decimal("0.0853")
                calculated_price = (base_price * (Decimal("1") - discount_ratio)
                    .quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))

                # DÜZELTME 7: Fiyat tutarlilik kontrolu
                logger.info(
                    f"Price calculated for product {item.product_id}: "
                    f"base={base_price}, final={calculated_price}"
                )

                # Siparis ogresini olustur
                order_item = OrderItem(
                    product_id=item.product_id,
                    quantity=item.quantity,
                    unit_price=calculated_price,
                    total_price=calculated_price * item.quantity
                )
                db.add(order_item)

            # Siparisi olustur
            order = Order(status="confirmed")
            db.add(order)
            await db.flush()

            # DÜZELTME 3: Idempotency key kaydet (24 saat)
            await redis_client.setex(idem_key, 86400, str(order.id))

            return {"status": "confirmed", "order_id": order.id}

    except HTTPException:
        raise  # Stok yetersiz hatasi — transaction rollback olur
    except Exception as e:
        logger.error(f"Order failed: {e}")
        raise HTTPException(500, "Siparis olusturulamadi")
    finally:
        # DÜZELTME 8+10: Tum locklari release et
        for lk, lid in locks:
            await release_lock(lk, lid)

Backend Düzeltmeleri Özeti

  • DÜZELTME 5 (Hata #5): client_side_price kaldırıldı — backend DB'den fiyat hesaplar
  • DÜZELTME 6 (Hata #6): Decimal sınıfı + ROUND_HALF_UP ile finansal hassasiyet
  • DÜZELTME 7 (Hata #7): Fiyat tutarlılık kontrolü — uyuşmazlıkta HTTP 409 + structured logging
  • DÜZELTME 8 (Hata #8): acquire_lock / release_lock — Redis SET NX ile distributed lock
  • DÜZELTME 9 (Hata #9): UPDATE ... SET stock = stock - :qty WHERE stock >= :qty RETURNING * — atomik stok düşümü
  • DÜZELTME 10 (Hata #10): async with db.begin() — tek transaction, hata durumunda otomatik ROLLBACK + finally'de lock release
  • DÜZELTME 3 (Hata #3): X-Idempotency-Key header + Redis'te 24 saat saklama — çift sipariş engeli

5. Kampanya Dönemi Stok Akışı Diyagramı

🏁 Kampanya Anı — Yarış Durumu Senaryosu
Bayi A ve Bayi B ayni anda son 10 adet urune siparis verir
↓ ↓
Bayi A: Redis lock alir (SET NX) — Bayi B: 409 Conflict alir
↓
Bayi A: DB transaction baslar — UPDATE stock = stock - 10 WHERE stock >= 10 RETURNING *
↓
Bayi A: Decimal ile fiyat hesaplanir (4573.50 TL) — siparis olusturulur
↓
Bayi A: Transaction commit — Redis lock release — Idempotency key kaydedilir
↓
Bayi B: Tekrar deneye — stok=0, 409 Conflict alir — "Stok yetersiz"
↓
Sonuc: Over-selling YOK — Stok tutarli — Fiyat Decimal hassasiyetinde

6. Sonuç ve Sonraki Aşama

Aşama 2 — Akıllı Stok, Çoklu Fiyat Listesi ve Sepet Yönetimi modülünde analiz edilen 10 kritik hata, bir B2B e-ticaret sisteminin en patlayıcı noktalarını temsil etmektedir. Race condition (over-selling) ve float precision hatası finansal olarak en yıkıcı sorunlardır — ilki şirketi stoksuz bırakır, ikincisi muhasebe krizi yaratır. Frontend'den fiyat alma güvenlik açığı ise doğrudan dolandırıcılığa imkan tanır.

Sunulan production-ready kod mimarisi — Redis distributed lock, Decimal finansal hesaplama, DB transaction + SELECT FOR UPDATE atomic stok düşümü, idempotency key, Pydantic validasyon, useMemo derived state ve immutable update pattern — bu sorunların tamamını çözmektedir. Aşama 3'te B2B e-Tahsilat ve Sanal Pos Entegrasyonu modülü ele alınacaktır: çift tahsilat, risk limit aşımı, 3D Secure redirect kaybı, refund race condition ve POS failover.

Kaynaklar: React Docs — Derived State (react.dev/learn/choosing-the-state-structure); Python Decimal — Financial Calculations (docs.python.org/3/library/decimal.html); PostgreSQL — Row-Level Locks & UPDATE RETURNING; Redis — Distributed Locks with SET NX (redis.io); FastAPI — Async SQL Transactions (fastapi.tiangolo.com); OWASP — Client-Side Price Manipulation; Idempotency Key — RFC for HTTP APIs; TanStack Query — Optimistic Updates; SQLAlchemy 2.0 — Async Sessions & Transactions.

Yorumlar - Yorum Yaz