fix(kredi): paketliden liste ve revizyon kredisi düşmez

/paket "liste ve revizyon dâhil" diyor, kod her üretimde 3 kredi
düşüyordu. Tek `maliyet = user.hasPaket ? 0 : RAPOR_KREDI` değişkeni
harcama, iade ve dönen bakiye satırlarını birden kapatıyor.

spendCredits/grantCredits çağrıları bilerek duruyor: ledger'daki
UNIQUE(reason, ref_id) üretimin tek idempotency çapası ve DUPLICATE
dalındaki "önceki deneme başarısızdı" çıkarımı refund satırının
varlığına dayanıyor. İade tutarı da koşullu — yoksa harcaması 0 olan
paketli her başarısız denemede bedava 3 kredi kazanırdı.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-22 06:06:52 +03:00
parent 9bd448bd16
commit 4acfd27ab1

View File

@@ -149,10 +149,18 @@ async function listeOlusturKilitli(
params: RaporParams, params: RaporParams,
requestId: string, requestId: string,
): Promise<ListeSonuc> { ): Promise<ListeSonuc> {
// Paket vaadi (docs/urun/vizyon.md): liste ve revizyon pakete dâhil; krediler
// yalnız danışman sorusu için. Çağrı BİLEREK duruyor — ledger'daki
// UNIQUE(reason, ref_id) (appdb/schema.ts:133) üretimin tek idempotency
// çapası; paketliye delta 0 satır yazılır, aynı requestId ikinci kez
// DUPLICATE döner. hasPaket tek yerden okunur (zaten elimizdeki user
// nesnesi) ki harcama ile iade aynı değeri görsün.
const maliyet = user.hasPaket ? 0 : RAPOR_KREDI;
// 3 kredi düş (idempotent: aynı requestId ile retry çifte harcamaz) // 3 kredi düş (idempotent: aynı requestId ile retry çifte harcamaz)
const harcama = await spendCredits({ const harcama = await spendCredits({
userId: user.id, userId: user.id,
amount: RAPOR_KREDI, amount: maliyet,
reason: "report_generate", reason: "report_generate",
refId: requestId, refId: requestId,
}); });
@@ -208,10 +216,13 @@ async function listeOlusturKilitli(
try { try {
sonuc = await raporUret(params); sonuc = await raporUret(params);
} catch (err) { } catch (err) {
// Üretim başarısız → krediyi iade et // Üretim başarısız → krediyi iade et. Satır paketlide de yazılır (delta 0):
// yukarıdaki DUPLICATE dalı "önceki deneme başarısız mıydı" sorusunu tam
// olarak bu refund kaydının varlığıyla cevaplıyor. Tutar koşullu, yoksa
// harcaması 0 olan paketliye her başarısız denemede bedava kredi yazardık.
await grantCredits({ await grantCredits({
userId: user.id, userId: user.id,
delta: RAPOR_KREDI, delta: maliyet,
reason: "refund", reason: "refund",
refId: requestId, refId: requestId,
}).catch(() => {}); }).catch(() => {});
@@ -240,7 +251,7 @@ async function listeOlusturKilitli(
rapor: user.hasPaket ? sonuc : raporMaskele(sonuc), rapor: user.hasPaket ? sonuc : raporMaskele(sonuc),
params, params,
revisionCount: 0, revisionCount: 0,
kredi: user.creditBalance - RAPOR_KREDI, kredi: user.creditBalance - maliyet,
hasPaket: user.hasPaket, hasPaket: user.hasPaket,
}; };
} }
@@ -282,9 +293,15 @@ export async function listeRevize(input: {
const params = rapor.params as RaporParams; const params = rapor.params as RaporParams;
const onceki = rapor.result as RaporSonuc; const onceki = rapor.result as RaporSonuc;
// Revizyon pakete dâhil (bkz. listeOlusturKilitli'deki aynı not). Yukarıdaki
// hasPaket kapısı yüzünden burada maliyet her zaman 0; ifade yine de koşullu
// yazılıyor ki kapı ileride gevşerse paketsiz kullanıcıdan ücret düşmeye
// devam etsin. Revizyon hakkını MAX_REVIZYON sayacı sınırlıyor, kredi değil.
const maliyet = user.hasPaket ? 0 : RAPOR_KREDI;
const harcama = await spendCredits({ const harcama = await spendCredits({
userId: user.id, userId: user.id,
amount: RAPOR_KREDI, amount: maliyet,
reason: "report_revision", reason: "report_revision",
refId: input.requestId, refId: input.requestId,
}); });
@@ -336,9 +353,11 @@ export async function listeRevize(input: {
listeOzetiCikar(onceki, params.sira), listeOzetiCikar(onceki, params.sira),
); );
} catch (err) { } catch (err) {
// İade satırı burada da yazılır (paketlide delta 0) — DUPLICATE dalındaki
// "önceki deneme başarısızdı" çıkarımı buna dayanıyor.
await grantCredits({ await grantCredits({
userId: user.id, userId: user.id,
delta: RAPOR_KREDI, delta: maliyet,
reason: "refund", reason: "refund",
refId: input.requestId, refId: input.requestId,
}).catch(() => {}); }).catch(() => {});
@@ -360,7 +379,7 @@ export async function listeRevize(input: {
rapor: sonuc, rapor: sonuc,
params, params,
revisionCount: rapor.revisionCount + 1, revisionCount: rapor.revisionCount + 1,
kredi: user.creditBalance - RAPOR_KREDI, kredi: user.creditBalance - maliyet,
hasPaket: true, hasPaket: true,
}; };
} }