supersam/supabase/functions/sms-balance/index.ts

73 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** Баланс счёта 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<string, unknown> = {};
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 });
}
});