Интеграция за 5 минут

Всё управление подпиской (привязка Telegram, оплата Stars/CryptoBot, продление) живёт в почте и в боте. Ваш сайт делает только два шага: кнопка входа и проверка isPremium для аккаунта.

Auth-домен: oauth.roxera.net (виджет, вход). API отвечает и там, и на premium.roxera.net — один сервис.

1. Кнопка «Вход RoxeraMail»

Ванильный JS, без зависимостей. Клик открывает popup хаба с выбором аккаунта (как Google Sign-In) — пароль вводится только на хабе, ваш сайт его никогда не видит.

<script src="https://oauth.roxera.net/widget.js"
        data-api="https://oauth.roxera.net"></script>
<div data-roxera-login></div>
<script>
  // u = { email, tier: 'free'|'premium', until, entitlements, linked }
  RoxeraAuth.onLogin(function (u) {
    if (u.tier === 'premium') unlockPro(u.entitlements);
    else lockPro();
  });
</script>

События window: roxera:login, roxera:logout. Методы: RoxeraAuth.signin(), signout(), user(), isPremium(), status(email), onLogin(fn). Состояние: localStorage 'roxera.auth.v1'.

Свой сервис с прямым Firebase-входом (как почта)? Подскажите хабу email юзера — и chooser покажет его как у Google:

// После вашего signIn:
RoxeraAuth.remember(user.email);

1.1. Вход в проект вашего сайта (custom token, «как Google»)

Если ваш бэкенд доверяет проекту (см. SSO_GRANTS), хаб вернёт customToken — сайт входит в свой Firebase-проект, как будто пользователь авторизовался у него:

const u = await RoxeraAuth.signin({ project: 'r0xera' });
// u = { email, tier, entitlements, customToken, ... }
import { getAuth, signInWithCustomToken } from 'firebase/auth';
await signInWithCustomToken(getAuth(), u.customToken); // реальная сессия вашего проекта
// Строгая проверка для своего бэкенда — обмен ID-токена на custom token:
curl -X POST https://oauth.roxera.net/v1/auth/custom-token \
  -H 'content-type: application/json' \
  -d '{"idToken":"ROXERA_ID_TOKEN","project":"r0xera","origin":"https://link.s-g.lol"}'

Пара origin → project выдаётся только из allowlist. Юзер ищется по email: если он уже входил через Google/пароль — сессия сольётся в тот же аккаунт (link-эффект).

2. Проверка isPremium

GET /v1/premium/status?email= — публичный, для UI

curl 'https://premium.roxera.net/v1/premium/status?email=user@mail.roxera.net'
# {"email":"...","tier":"premium","until":"2026-10-29T...","entitlements":{...}}
Поле entitlementsFreePremium 💛
boxes — лимит сущностей110
dailyQuota — лимит в день1030
verified — галочкаfalsetrue
premiumDomains[]["xye-coc.online","xyeglot.online"]
tempExtendfalsetrue

POST /v1/premium/verify — строгий, для своего бэкенда

Клиент присылает Firebase ID token, сервер сам валидирует его в Google. Email из query брать нельзя — его можно подделать, токен — нет.

curl -X POST https://oauth.roxera.net/v1/premium/verify \
  -H 'content-type: application/json' -d '{"idToken":"FIREBASE_ID_TOKEN"}'
# {"ok":true,"uid":"...","email":"...","tier":"premium","until":"...","entitlements":{...},"linked":{...}|null}
// Python (свой бэкенд):
import urllib.request, json
body = json.dumps({"idToken": id_token_from_client}).encode()
tier = json.load(urllib.request.urlopen("https://oauth.roxera.net/v1/premium/verify", body))["tier"]
give_pro = (tier == "premium")

3. Что ещё есть (для своих сервисов)

МетодПутьНазначение
GET/v1/healthЖивость
GET/v1/premium/plansТариф и лимиты из конфига (не хардкодьте цены)
GET/v1/premium/configПубличный конфиг (bot username, флаги)

Ошибки: bad_email (400), unauthorized (401), forbidden (403), rate_limited (429). Всё остальное — привязка, оплата, отвязка — пользователь делает в почте и в боте, не у вас.