Você é um engenheiro sênior. Integre a API de pagamentos da LofyPay (gateway brasileiro de PIX) neste projeto, do jeito certo e seguro. Antes de escrever código, identifique a stack do projeto (framework, linguagem, onde roda o servidor) e me diga em 3 linhas o plano. Se faltar alguma informação, pergunte antes de inventar.

# Contexto da API
- Base URL: https://app.lofypay.com/api/v1
- Documentação completa: https://docs.lofypay.com
- REST + JSON. Valores sempre em REAIS (number, ex.: 100.50), nunca em centavos.
- Autenticação: header "Authorization: Bearer <SECRET_KEY>". A chave começa com sk_live_ (produção) ou sk_test_ (sandbox). A mesma URL serve os dois ambientes; o prefixo da chave decide.
- Rate limit: 30 req/min por IP e 120 req/min por chave. Em 429, respeite o header Retry-After.

# Regras de segurança (obrigatórias)
1. A SECRET KEY só existe no SERVIDOR, em variável de ambiente (LOFYPAY_SECRET_KEY). Nunca no front-end, no app mobile, no repositório ou em logs.
2. Toda chamada à LofyPay sai do backend (rota de API / server action / função serverless). O navegador fala só com o meu backend.
3. Nunca libere pedido só porque chegou um POST no webhook: valide a assinatura (canal assinado) OU confirme no endpoint de status antes de entregar.
4. Seja idempotente: o mesmo pagamento confirmado duas vezes não pode entregar o produto duas vezes.

# 1) Criar cobrança PIX
POST https://app.lofypay.com/api/v1/gateway
Headers: Authorization: Bearer <SECRET_KEY>, Content-Type: application/json, Idempotency-Key: <id único do pedido> (opcional, evita cobrança duplicada em retentativa)
Body:
{
  "amount": 100.50,
  "method": "pix",
  "external_reference": "PEDIDO-123",          // id do pedido no meu sistema (volta no webhook)
  "notification_url": "https://MEU-SITE/api/webhooks/lofypay-pedido", // opcional
  "expiration": 1800,                          // opcional, segundos (60 a 86400)
  "client": { "name": "Nome do comprador", "document": "CPF/CNPJ só dígitos", "email": "email@exemplo.com" },
  "trackingParameters": { "utm_source": "...", "utm_campaign": "...", "utm_medium": "...", "utm_content": "...", "utm_term": "...", "src": null, "sck": null } // opcional (Utmify)
}
Resposta de sucesso:
{ "status": "success", "idTransaction": "52fc5262-...", "paymentCode": "000201...", "paymentCodeBase64": "iVBOR..." (opcional) }
ATENÇÃO: HTTP 200 NÃO garante sucesso. Trate como sucesso só se status for "success" (ou "OK" no sandbox) E vier idTransaction. Se vier { "status": "error", "message": "..." }, mostre a mensagem e não abra o checkout.
- paymentCode é o "copia e cola". Gere o QR Code a partir dele (biblioteca de QR no front). paymentCodeBase64 nem sempre vem.
- Salve no banco: idTransaction, external_reference, valor, status "pendente".
- Valor mínimo por PIX pode existir por processadora (erro "deposit_below_minimum"); mostre a mensagem da API.

# 2) Tela de pagamento
- Mostre o QR Code, o copia e cola com botão "Copiar", o valor e um aviso de expiração.
- Enquanto estiver aberta, consulte o MEU backend a cada 5 s (que consulta a LofyPay ou lê o banco atualizado pelo webhook). Pare quando pagar ou expirar.

# 3) Consultar status (fonte da verdade)
POST https://app.lofypay.com/api/v1/status
Headers: Authorization: Bearer <SECRET_KEY>, Content-Type: application/json
Body: { "idtransaction": "<idTransaction>" }
Resposta: { "status": "PAID_OUT" }
Status possíveis: WAITING_FOR_APPROVAL (aguardando), PAID_OUT (PAGO), EXPIRED, REFUNDED, FAILED. Só PAID_OUT é pago.

# 4) Webhooks (dois canais, use pelo menos um)
Canal A — notification_url (por cobrança, SEM assinatura):
POST no meu endpoint com { "status": "PAID", "idTransaction": "...", "amount": 100.50, "external_reference": "PEDIDO-123", "paid_at": "..." }
Como não é assinado: ao receber, chame /api/v1/status com o idTransaction e só marque como pago se voltar PAID_OUT.

Canal B — endpoints cadastrados no painel (Gateway/API > Webhooks), ASSINADOS (recomendado):
Envelope: { "event": "payment.approved", "sent_at": "...", "data": { "idTransaction": "...", "external_reference": "...", "amount": 100.50, "payment_method": "pix", ... } }
Eventos: payment.pending, payment.approved, payment.refunded, payment.cancelled, saque.requested, saque.paid, saque.failed, med.opened, med.updated, med.resolved.
Headers: X-LofyPay-Event, X-LofyPay-Timestamp, X-LofyPay-Id (id da entrega, use para deduplicar), X-LofyPay-Signature = "t=<unix>,v1=<hex>".
Validação: HMAC-SHA256 com o secret DO ENDPOINT (variável LOFYPAY_WEBHOOK_SECRET, NÃO é a sk_live_) sobre a string "<t>.<corpo cru>". Compare em tempo constante e rejeite se |agora - t| > 300 s. Use o corpo CRU (sem JSON.parse antes) para o HMAC bater.
Exemplo Node:
  const parts = Object.fromEntries(sig.split(",").map(p => p.split("=")));
  const esperado = crypto.createHmac("sha256", process.env.LOFYPAY_WEBHOOK_SECRET).update(parts.t + "." + rawBody).digest("hex");
  válido = Math.abs(Date.now()/1000 - Number(parts.t)) <= 300 && crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(parts.v1));
Regras: responda 200 em até 8 s e processe depois (a LofyPay reenvia em 1 min, 5 min, 30 min, 2 h e 12 h se não receber 2xx). Deduplique por X-LofyPay-Id e pelo idTransaction já pago.

# 5) Sandbox (teste antes de ir para produção)
Use uma chave sk_test_. Para simular o pagamento:
POST https://app.lofypay.com/api/v1/sandbox/pay  Body: { "api-key": "sk_test_...", "idTransaction": "<id>", "status": "paid" }
(status aceita paid, failed, expired, pending). Isso dispara o webhook de teste (header X-Lofypay-Environment: test).

# 6) Saque PIX (só se eu pedir)
POST https://app.lofypay.com/api/v1/cashout com Idempotency-Key obrigatório na prática:
{ "amount": 250.00, "name": "Nome do favorecido", "cpf": "CPF/CNPJ", "keypix": "chave", "tipo_chave": "CPF|CNPJ|EMAIL|TELEFONE|EVP" }
A resposta "PROCESSING" NÃO é pago: o fim vem pelo evento saque.paid ou saque.failed. Erros trazem { code, message, retryable }.

# 7) Erros
- 400 campo inválido · 401 chave inválida (missing_key, public_key_used, invalid_format, key_not_found, key_revoked) · 403 IP/permissão · 409 conflito/saque em andamento · 422 recusado (ex.: saldo) · 429 limite (Retry-After) · 502 processadora não gerou (nada foi cobrado, pode repetir).
- Guarde o header x-request-id das respostas nos logs para o suporte.

# O que entregar
1. Variáveis de ambiente: LOFYPAY_SECRET_KEY e LOFYPAY_WEBHOOK_SECRET (e .env.example sem valores reais).
2. Um módulo/serviço de backend "lofypay" com: criarCobrancaPix(pedido), consultarStatus(idTransaction) e verificarWebhook(rawBody, headers).
3. Rota do backend para criar o PIX do pedido e devolver ao front só o necessário (copia e cola, QR, valor, expiração). Nunca devolva a chave.
4. Rota de webhook que valida a assinatura (ou confirma no /status), marca o pedido como pago de forma idempotente e responde 200 rápido.
5. Tela de pagamento com QR Code, copiar, contagem de expiração e confirmação automática.
6. Tratamento de erro legível para o usuário e logs com x-request-id.
7. Um passo a passo curto de como testar no sandbox (sk_test_ + /api/v1/sandbox/pay) e de como ir para produção (trocar para sk_live_ e cadastrar o webhook no painel).

Checklist final antes de terminar: a chave não aparece em nenhum arquivo do front nem no repositório; o webhook rejeita assinatura inválida; pagar duas vezes o mesmo aviso não duplica entrega; o fluxo inteiro funciona no sandbox.