> ## Documentation Index
> Fetch the complete documentation index at: https://docs.easygoal.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Modo Externo

> Mantenha seu checkout. Use o SSO da Easy Goal como camada opcional de autenticação.

## Quando usar

* Você já tem um produto ativo na Kiwify ou AbacatePay com base de clientes
* Quer oferecer **login via Easy Goal** como opção (sem migrar o checkout)
* Precisa que apenas quem pagou tenha acesso ao login SSO

## Pré-requisitos

<Steps>
  <Step title="Criar API App">
    Solicite ao time Easy Goal a criação do API App do seu produto e o repasse da `api_key` gerada.
    Essa chave é o canal privado do seu produto — guarde com segurança.
  </Step>

  <Step title="Registrar produto">
    Registre seu produto junto à Easy Goal no modo **Externo**, vinculando o API App criado.
  </Step>

  <Step title="Configurar webhook">
    No painel do seu gateway (Kiwify ou AbacatePay), adicione um webhook apontando para:

    ```
    POST https://app.easygoal.com.br/api/webhooks/kiwify/<api_key>
    ```

    Cada compra confirmada criará automaticamente uma assinatura na Easy Goal.
  </Step>

  <Step title="Instalar template no seu app">
    Use o pacote `@easygoal/auth` do monorepo ou implemente manualmente conforme abaixo.
  </Step>
</Steps>

***

## Variáveis de ambiente

Adicione ao `.env` do seu app — **sem** service role key:

```bash theme={null}
NEXT_PUBLIC_SSO_URL=https://sso.easygoal.com.br
NEXT_PUBLIC_EASY_API_KEY=eg_live_xxxxxxxxxxxx
NEXT_PUBLIC_EG_APP_URL=https://app.easygoal.com.br
NEXT_PUBLIC_KIWIFY_CHECKOUT_URL=https://pay.kiwify.com.br/seu-produto
```

***

## Implementação (Next.js)

### 1. Helper SSO

```ts theme={null}
// src/lib/easygoal-sso.ts
const SSO_URL = process.env.NEXT_PUBLIC_SSO_URL ?? 'https://sso.easygoal.com.br'

export function buildSsoLoginUrl(appBaseUrl: string, next = '/dashboard'): string {
  const callbackUrl = `${appBaseUrl}/api/auth/easygoal/callback?next=${encodeURIComponent(next)}`
  const url = new URL(`${SSO_URL}/auth/login`)
  url.searchParams.set('redirect_to', callbackUrl)
  const apiKey = process.env.NEXT_PUBLIC_EASY_API_KEY
  if (apiKey) url.searchParams.set('api_key', apiKey)
  return url.toString()
}
```

### 2. Callback route

```ts theme={null}
// src/app/api/auth/easygoal/callback/route.ts
import { NextRequest, NextResponse } from 'next/server'

function decode(token: string) {
  try {
    return JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString())
  } catch { return null }
}

export async function GET(request: NextRequest) {
  const { searchParams } = request.nextUrl
  const egSession = searchParams.get('eg_session')
  const next = searchParams.get('next') ?? '/'
  const apiKey = process.env.NEXT_PUBLIC_EASY_API_KEY
  const appUrl = process.env.NEXT_PUBLIC_EG_APP_URL ?? 'https://app.easygoal.com.br'

  if (!egSession || !apiKey) {
    return NextResponse.redirect(new URL('/login?sso_error=missing_token', request.url))
  }

  const claims = decode(egSession)
  if (!claims?.sub) {
    return NextResponse.redirect(new URL('/login?sso_error=invalid_token', request.url))
  }

  const params = new URLSearchParams({ user_id: claims.sub })
  if (claims.email) params.set('email', claims.email)

  const res = await fetch(`${appUrl}/api/saas/check-access?${params}`, {
    headers: { Authorization: `Bearer ${apiKey}` },
    cache: 'no-store',
  })

  const { active } = await res.json()

  if (!active) {
    return NextResponse.redirect(new URL('/login?sso_error=no_account', request.url))
  }

  const response = NextResponse.redirect(new URL(next.startsWith('/') ? next : '/', request.url))
  response.cookies.set('eg_session', egSession, {
    httpOnly: true, secure: true, sameSite: 'lax', path: '/', maxAge: 60 * 60 * 24 * 7,
  })
  return response
}
```

### 3. Botão de login

```tsx theme={null}
'use client'
import { buildSsoLoginUrl } from '@/lib/easygoal-sso'

export function EasyGoalLoginButton() {
  return (
    <button onClick={() => { window.location.href = buildSsoLoginUrl(window.location.origin) }}>
      Entrar com Easy Goal
    </button>
  )
}
```

### 4. Middleware de proteção

```ts theme={null}
// middleware.ts
import { NextResponse, type NextRequest } from 'next/server'

function decodeSession(token: string) {
  try {
    return JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString())
  } catch { return null }
}

export function middleware(request: NextRequest) {
  const isPublic = ['/login', '/acesso-pendente', '/api/auth/easygoal'].some(p =>
    request.nextUrl.pathname.startsWith(p)
  )
  if (isPublic) return NextResponse.next()

  const token = request.cookies.get('eg_session')?.value
  if (!token) return NextResponse.redirect(new URL('/login', request.url))

  const payload = decodeSession(token)
  if (!payload || payload.exp < Math.floor(Date.now() / 1000)) {
    const res = NextResponse.redirect(new URL('/login', request.url))
    res.cookies.delete('eg_session')
    return res
  }

  return NextResponse.next()
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}
```

***

## Fluxo completo

```
Usuário clica "Entrar com Easy Goal"
  └─► SSO autentica (email/senha ou Google/GitHub)
        └─► redirect para /api/auth/easygoal/callback?eg_session=...
              └─► GET /api/saas/check-access (EG verifica assinatura)
                    ├─ active: true  → seta cookie → dashboard
                    └─ active: false → /login?sso_error=no_account
```

<Warning>
  O webhook de pagamento deve notificar a Easy Goal **antes** do usuário tentar o login.
  Após a compra, oriente o cliente a aguardar a confirmação (geralmente instantânea) antes de acessar.
</Warning>

***

## AbacatePay como gateway externo

O mesmo fluxo funciona com AbacatePay. Use o endpoint:

```
POST https://app.easygoal.com.br/api/webhooks/abacatepay/<api_key>
```

Esse endpoint já está em produção e processa a notificação automaticamente.
