İçeriğe geç

/rates

docs/src/snippets/modules.test.ts
// import { layeredRates, type PriceVersion } from "pricemeter/rates";
const rows: PriceVersion[] = [
{ id: "sms-list", meter: "sms", scope: "list", dims: { country: "*" }, effectiveFrom: 0, rate: flat(50_000) },
{ id: "sms-list-tr", meter: "sms", scope: "list", dims: { country: "TR" }, effectiveFrom: 0, rate: flat(20_000) },
{ id: "sms-pro-tr", meter: "sms", scope: "plan:pro", dims: { country: "TR" }, effectiveFrom: Date.parse("2026-09-01"), rate: flat(18_000) },
{ id: "sms-acc1", meter: "sms", scope: "account:acc_1", dims: { country: "DE" }, effectiveFrom: 0, rate: flat(30_000) },
{ id: "sms-acc1-tr-off", meter: "sms", scope: "account:acc_1", dims: { country: "TR" }, effectiveFrom: 0, removed: true },
];
const getRate = layeredRates<{ accountId: string; plan: string }>({
loadRows: (meter) => rows.filter((r) => r.meter === meter), // your DB query
scopes: (ctx) => [`account:${ctx.accountId}`, `plan:${ctx.plan}`, "list"], // most specific first
cacheMs: 60_000,
});
await getRate("sms", { country: "DE" }, ctx, at); // { rate: sms-acc1 (30000) } account row
await getRate("sms", { country: "TR" }, ctx, at); // { rate: sms-pro-tr (18000) } account row removed → plan
await getRate("sms", { country: "US" }, ctx, at); // { rate: sms-list (50000) } wildcard in list

pricemeter/rates isteğe bağlı bir fiyat tablosudur ve hazır bir getRate üretir. Modül testindeki dikişi “getRate üretmek”tir; çekirdek onu görmez.

resolveLayered({ rows, scopes, dims, at, meter }) şu sırayla seçer:

  1. Scope sırası: scopes(ctx)’in döndüğü dizi, en özelden en genele (account:… > plan:… > list). Scope adlarının anlamı yoktur; sırayı sen verirsin.
  2. Scope içinde en özel eşleşen satır: boyut başına tam eşleşme bir puan; eksik anahtar ya da "*" her şeyle eşleşir ama puan getirmez.
  3. Eşitlikte en geç effectiveFrom (≤ at).
  4. Kazanan satır removed: true ise bu scope düşer, sıradakine geçilir. Hiçbir scope’ta satır yoksa null → no_price.

Kazanan tarifenin id’si yoksa satırın id’si verilir; usage satırındaki rateId böylece fiyat tablosundaki satıra işaret eder.

API Ne yapar
layeredRates({ loadRows, scopes, cacheMs?, usedSoFar?, now? }) Hazır getRate. loadRows(meter, ctx) senin sorgun; (meter, scopes) başına cacheMs kadar önbellek. usedSoFar(meter, dims, ctx, at, rate) verilirse cevaba eklenir
resolveLayered({ rows, scopes, dims, at, meter? }) Saf çözümleyici; kendi getRate’inin içinde kullan
validateVersion(row) Bir fiyat satırını (tarifesiyle birlikte) doğrular; admin UI kaydetmeden önce
applyDiscount, scaleTiers, withPolicy Bir tarifeden yeni bir tarife türeten saf yardımcılar
docs/src/snippets/modules.test.ts
const list: Rate = { id: "api", model: "graduated", tiers: [{ from: 0, unitPriceMicroUsd: 1_000 }, { from: 10_000, unitPriceMicroUsd: 800 }] };
applyDiscount({ rate: list, percent: 15 }); // prices × 0.85, id "api-15pct"
scaleTiers({ rate: list, factor: 0.5 }); // the cheaper tier starts at 5,000
withPolicy({ rate: list, policy: { rounding: "cumulative" } });

applyDiscount birim fiyatları ve sabit ücretleri en yakın mikro birime yuvarlar ve id’ye -{percent}pct ekler (CRM indirimi, B19). scaleTiers kademe eşiklerini çarpar (kurumsal hesap indirimli kademeye daha erken ulaşır).

/rates fiyatın kendisini bilir, usedSoFar’ı bilmez: sayaç senin deponda. Örnekler: SaaS koltuk layeredRates + döngü penceresi; mesajlaşma resolveLayered + rota maliyeti yedeği.

getRate bir fonksiyondur; fiyat tablon zaten varsa onu doğrudan sorgula:

docs/src/snippets/modules.test.ts
// Without /rates: a getRate over your own table, in whatever order your product needs.
const byHand = async (meter: string, dims: { country: string }, c: typeof ctx, t: number): Promise<RateAnswer | null> => {
for (const scope of [`account:${c.accountId}`, `plan:${c.plan}`, "list"]) {
const candidates = rows
.filter((r) => r.meter === meter && r.scope === scope && r.effectiveFrom <= t)
.filter((r) => r.dims?.country === dims.country || r.dims?.country === "*")
.sort((a, b) => Number(b.dims?.country !== "*") - Number(a.dims?.country !== "*") || b.effectiveFrom - a.effectiveFrom);
const best = candidates[0];
if (best && !best.removed) return { rate: { ...best.rate!, id: best.id } };
}
return null;
};

Özellik tablosu, joker, sürüm ve “kaldırıldı” kuralları ürününe göre değişir; bu modül yalnızca yaygın bir biçimini verir. Tablon farklı bir sırayla çözülüyorsa (ör. kanal önce, hesap sonra) kendi getRate’ini yaz; kütüphane farkı görmez.