/** Баланс счёта SMS-сервиса — для предупреждения в интерфейсе. * * Зачем: 10–13 сентября рассылки падали с кодом 201 («не хватает средств»), * и об этом никто не знал. Ключ доступа к SMS живёт только на сервере, поэтому * баланс отдаём отдельной функцией, а не читаем ключ в браузере. */ import { createClient } from "npm:@supabase/supabase-js@2"; const ALLOWED_ORIGINS = [ "https://dost.supersamsev.ru", "https://supa.supersamsev.ru", "https://dev.mkn8n.ru", "http://localhost:5173", ]; const BALANCE_URL = "https://sms.ru/my/balance"; // Ниже этого остатка предупреждаем заранее, до остановки отправки. const LOW_BALANCE_RUB = 100; const cors = (origin: string) => ({ "Access-Control-Allow-Origin": ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0], "Access-Control-Allow-Methods": "POST,GET,OPTIONS", "Access-Control-Allow-Headers": "Content-Type,Authorization,apikey", }); Deno.serve(async (req: Request) => { const origin = req.headers.get("origin") || ""; const headers = { ...cors(origin), "Content-Type": "application/json" }; if (req.method === "OPTIONS") return new Response(null, { headers }); try { const supabaseUrl = Deno.env.get("SUPABASE_URL") || ""; const serviceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") || ""; const sb = createClient(supabaseUrl, serviceKey); const { data: settings } = await sb .from("sms_campaign_settings") .select("sms_api_id") .eq("campaign_type", "first_sms") .single(); const apiId = settings?.sms_api_id || Deno.env.get("SMS_API_ID") || ""; if (!apiId) { return new Response(JSON.stringify({ ok: false, error: "api_id not configured" }), { status: 500, headers }); } const resp = await fetch(`${BALANCE_URL}?api_id=${encodeURIComponent(apiId)}&json=1`); const text = await resp.text(); let payload: Record = {}; try { payload = JSON.parse(text); } catch { return new Response(JSON.stringify({ ok: false, error: "unexpected response", raw: text.slice(0, 200) }), { status: 200, headers }); } if (payload.status !== "OK") { return new Response(JSON.stringify({ ok: false, code: payload.status_code ?? null, raw: text.slice(0, 200) }), { status: 200, headers }); } const balance = Number(payload.balance); return new Response(JSON.stringify({ ok: true, balance, low: balance < LOW_BALANCE_RUB, threshold: LOW_BALANCE_RUB, }), { headers }); } catch (e) { return new Response(JSON.stringify({ ok: false, error: String(e) }), { status: 500, headers }); } });