Skip to main content

Ошибки токенов

Token already used (401)

Причина: Один и тот же preimage был отправлен дважды — атака повторного воспроизведения или случайная повторная попытка. Решение: Каждый платёж по инвойсу создаёт одноразовый токен. Сгенерируйте новый инвойс и оплатите его снова. На стороне клиента убедитесь, что L402Client кэширует токены по URL конечной точки (по умолчанию это так).

Token expired (401 / valid: false)

Причина: Инвойсы истекают через 1 час. Поле exp токена задаётся в миллисекундах. Решение: Запросите новый инвойс. Если срок действия истекает слишком быстро, проверьте точность системных часов сервера (Date.now() на сервере и time.time() на клиенте должны отличаться не более чем на несколько секунд).

Invalid preimage format (вместо 200 возвращается 402)

Причина: Preimage содержит не ровно 64 шестнадцатеричных символа (32 байта). Решение: Убедитесь, что ваш кошелёк возвращает сырой preimage в виде hex-строки из 64 символов. Некоторые кошельки возвращают его в формате base64 — в этом случае сначала декодируйте его.

Webhook signature mismatch (401)

Причина: Заголовок l402-signature не совпадает с секретом, или тело запроса было изменено при передаче. Решение:
  1. Убедитесь, что L402_WEBHOOK_SECRET совпадает на стороне отправителя и получателя.
  2. Используйте express.raw({ type: 'application/json' }) (а не express.json()) для чтения сырого тела запроса перед верификацией — JSON-парсинг переформатирует строку и аннулирует подпись.
  3. Проверьте, что ваш обратный прокси (nginx, Cloudflare) не изменяет тело запроса.

Ошибки провайдера

ManagedProvider: invoice creation failed / HTTP 503

Причина: l402kit.com временно недоступен или API Blink не работает. Решение: Повторите запрос с экспоненциальной задержкой. Проверьте статус на status.blink.sv. Для производственных нагрузок, требующих нулевого времени простоя, используйте собственный провайдер (BlinkProvider, AlbyProvider и т. д.) с вашими учётными данными.
Причина: Ваш кошелёк Blink имеет недостаточную исходящую ликвидность для разделённого платежа. Решение: Пополните кошелёк shinydapps@blink.sv. Платформе требуется небольшой баланс для маршрутизации разделённых платежей. Это влияет только на разделение через ManagedProvider — создание инвойсов не затрагивается.

LNURL fetch failed при разделении платежа

Причина: Lightning Address разработчика не разрешается. Конечная точка /.well-known/lnurlp/ вернула статус, отличный от 200. Решение: Убедитесь, что Lightning Address действителен, а LNURL-pay конечная точка домена доступна. Проверьте с помощью:
curl https://<domain>/.well-known/lnurlp/<username>

AlbyProvider: HTTP 401

Причина: Токен доступа Alby истёк или был отозван. Решение: Перегенерируйте токен в Alby Hub → Настройки → Токены доступа. Убедитесь, что область видимости включает invoices:create.

BTCPayProvider: HTTP 403

Причина: У API-ключа отсутствует необходимое разрешение. Решение: В BTCPay Server → Аккаунт → API-ключи убедитесь, что ключ имеет область видимости btcpay.store.cancreatelightninginvoice для нужного магазина.

Защита от повторных атак

Несколько экземпляров / повторные атаки не блокируются

Причина: По умолчанию MemoryReplayAdapter работает только в рамках текущего процесса. При перезапуске или при наличии нескольких экземпляров он сбрасывается. Решение: Используйте RedisReplayAdapter для развёртываний с несколькими экземплярами:
import Redis from "ioredis";
import { RedisReplayAdapter } from "l402-kit";

const redis = new Redis(process.env.REDIS_URL!);
app.get("/api", l402({
  priceSats: 10,
  lightning,
  replayAdapter: new RedisReplayAdapter(redis),
}), handler);
Уникальное ограничение payment_hash в Supabase выступает в роли надёжного второго уровня защиты вне зависимости от адаптера в памяти, поэтому повторные атаки всегда блокируются даже без Redis.

Ограничения скорости

Too many requests. Max 20 invoices/minute per IP. (429)

Причина: Более 20 запросов на создание инвойса в минуту с одного IP-адреса, достигших конечной точки ManagedProvider. Решение: Реализуйте кэширование на стороне клиента — не создавайте новый инвойс при каждой загрузке страницы. L402Client автоматически кэширует токены по URL конечной точки. Для потоков между серверами, генерирующих много инвойсов, используйте собственный провайдер.

Специфика фреймворков

Express: middleware не срабатывает

Причина: Обработчик маршрута Express зарегистрирован до middleware l402(), или порядок middleware неверен. Решение:
// ✅ Правильно — l402 перед обработчиком
app.get("/api", l402({ priceSats: 10, lightning }), myHandler);

// ❌ Неправильно — l402 зарегистрирован после обработчика
app.get("/api", myHandler, l402(...));

FastAPI: 422 Unprocessable Entity при ответе 402

Причина: FastAPI валидирует тела ответов в соответствии с объявленной моделью ответа. Тело ответа 402 не совпадает с ней. Решение: Исключите статус 402 из валидации ответа или не объявляйте модель ответа для декорированных конечных точек:
@app.get("/premium")  # no response_model= here
@l402_required(price_sats=10, lightning=provider)
async def premium():
    return {"data": "paid content"}

Go: panic: l402: no Lightning provider set

Причина: Options.Lightning равен nil. Решение: Всегда задавайте провайдер:
// ✅
l402kit.Middleware(l402kit.Options{
    PriceSats: 10,
    Lightning: l402kit.NewManagedProvider("you@blink.sv"),
}, handler)

// ❌ вызывает panic
l402kit.Middleware(l402kit.Options{PriceSats: 10}, handler)

Всё ещё не решили проблему?

Откройте issue на github.com/ShinyDapps/l402-kit/issues, указав:
  • Язык и версию SDK
  • Минимальный воспроизводящий пример
  • Сообщение об ошибке и трассировку стека