shipping.ts raw

   1  import { econtCalculate } from './econt';
   2  import { getDb } from './db';
   3  
   4  export function getShippingConfig() {
   5    const db = getDb();
   6    const rows = db.query("SELECT key, value FROM content WHERE key LIKE 'shipping_%' OR key = 'free_shipping_threshold'").all() as { key: string; value: string }[];
   7    const cfg: Record<string, string> = {};
   8    for (const r of rows) cfg[r.key] = r.value;
   9    return {
  10      ratePerKg: parseInt(cfg.shipping_rate_per_kg || '500') || 500,   // cents per kg
  11      freeThreshold: parseInt(cfg.free_shipping_threshold || '5000') || 5000, // cents
  12    };
  13  }
  14  
  15  // Fixed courier rates (in euro cents) for orders up to 1 kg. Anything over
  16  // 1 kg falls back to the Econt API or the per-kg rate.
  17  const FIXED_ADDRESS_RATE = 522;  // €5.22 - delivery to personal address
  18  const FIXED_OFFICE_RATE = 395;   // €3.95 - delivery to office / APS locker
  19  const FIXED_RATE_MAX_KG = 1;
  20  
  21  // shippingCostCents returns shipping cost in stotinki (euro cents).
  22  // If econt is configured, calls the Econt API. Otherwise uses
  23  // the configurable per-kg rate with free-shipping threshold.
  24  export async function shippingCostCents(
  25    totalKg: number,
  26    subtotalCents: number,
  27    city?: string,
  28    courier?: string,
  29    codAmount?: number,
  30    officeDelivery?: boolean,
  31  ): Promise<number> {
  32    try {
  33      const { freeThreshold } = getShippingConfig();
  34      if (freeThreshold > 0 && subtotalCents >= freeThreshold) return 0;
  35  
  36      // fixed rate for packages up to 1 kg (not prorated)
  37      if (totalKg <= FIXED_RATE_MAX_KG) {
  38        return officeDelivery ? FIXED_OFFICE_RATE : FIXED_ADDRESS_RATE;
  39      }
  40  
  41      // try econt if configured
  42      const econtCfg = getEcontConfigured();
  43      if (econtCfg && courier === 'econt' && city) {
  44        const result = await econtCalculate(city, totalKg, codAmount || 0, officeDelivery || false);
  45        if (!result.error) {
  46          return Math.round(result.totalPrice * 100);
  47        }
  48      }
  49  
  50      // fallback: per-kg rate
  51      const { ratePerKg } = getShippingConfig();
  52      return Math.round(ratePerKg * totalKg);
  53    } catch {
  54      const { ratePerKg, freeThreshold } = getShippingConfig();
  55      if (freeThreshold > 0 && subtotalCents >= freeThreshold) return 0;
  56      return Math.round(ratePerKg * totalKg);
  57    }
  58  }
  59  
  60  function getEcontConfigured(): boolean {
  61    try {
  62      const db = getDb();
  63      const row = db.query("SELECT value FROM content WHERE key = 'econt_username'").get() as { value: string } | null;
  64      const pw = db.query("SELECT value FROM content WHERE key = 'econt_password'").get() as { value: string } | null;
  65      return !!(row?.value && pw?.value);
  66    } catch { return false; }
  67  }
  68  
  69  // sync version for stripe payment intent creation
  70  export function shippingCostCentsSync(totalKg: number, subtotalCents: number, officeDelivery = false): number {
  71    const { ratePerKg, freeThreshold } = getShippingConfig();
  72    if (freeThreshold > 0 && subtotalCents >= freeThreshold) return 0;
  73    if (totalKg <= FIXED_RATE_MAX_KG) {
  74      return officeDelivery ? FIXED_OFFICE_RATE : FIXED_ADDRESS_RATE;
  75    }
  76    return Math.round(ratePerKg * totalKg);
  77  }
  78