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

# Headers Asaas - Configuração

Este sistema automaticamente adiciona headers customizados a todas as requisições para a API do Asaas, permitindo identificar o canal/parceria de origem das transações.

## Variáveis de Ambiente Necessárias

Adicione as seguintes variáveis no seu arquivo `.env`:

```bash theme={null}
# Ativar/Desativar headers de canal
ASAAS_ORIGIN_HEADERS_ENABLED=true

# Headers fixos do canal
ASAAS_ORIGIN_HEADER=https://seudominio.com.br
ASAAS_CHANNEL_ACCESS_TOKEN=seu_token_do_canal_asaas

# Referência externa padrão (opcional)
ASAAS_DEFAULT_EXTERNAL_REFERENCE=default_channel_ref
```

## Headers Enviados Automaticamente

Quando habilitado (`ASAAS_ORIGIN_HEADERS_ENABLED=true`), o sistema envia os seguintes headers em todas as chamadas para a API do Asaas:

* **Origin**: Identificador fixo do canal (valor de `ASAAS_ORIGIN_HEADER`)
* **Origin-Channel-Access-Token**: Token de acesso do canal (valor de `ASAAS_CHANNEL_ACCESS_TOKEN`)
* **Origin-Channel-External-Reference**: Referência dinâmica do tenant/loja atual

### Como funciona a referência externa (`Origin-Channel-External-Reference`)

O sistema obtém automaticamente a referência do tenant/loja através da seguinte ordem de prioridade:

1. **Sessão web**: Se `session('loja_id')` existir, usa formato `loja_{id}`
2. **Domínio/Host**: Se for multi-tenant por domínio, extrai do host (ex: `tenant_minhaloja` para `minhaloja.seudominio.com`)
3. **Fallback**: Usa valor de `ASAAS_DEFAULT_EXTERNAL_REFERENCE` se configurado

## Arquivos Modificados

### Novos Arquivos

* `app/Services/OriginChannelHeaders.php` - Serviço responsável por gerar os headers dinâmicos
* `docs/asaas_origin_headers.md` - Esta documentação

### Arquivos Modificados

* `config/asaas.php` - Adicionadas configurações dos headers de origem
* `app/Providers/AppServiceProvider.php` - Registrado o serviço no container
* `vendor/leopaulo88/asaas-sdk-laravel/src/Support/AsaasClient.php` - Modificado `getHeaders()` para incluir headers dinâmicos

## Exemplo de Headers Enviados

Com as configurações:

```bash theme={null}
ASAAS_ORIGIN_HEADERS_ENABLED=true
ASAAS_ORIGIN_HEADER=https://nortgraf.com.br
ASAAS_CHANNEL_ACCESS_TOKEN=ch_abc123
```

E uma sessão com `loja_id = 42`, todas as requisições para o Asaas incluirão:

```http theme={null}
Origin: https://nortgraf.com.br
Origin-Channel-Access-Token: ch_abc123
Origin-Channel-External-Reference: loja_42
```

## Desabilitação

Para desabilitar os headers customizados, simplesmente configure:

```bash theme={null}
ASAAS_ORIGIN_HEADERS_ENABLED=false
```

## Logs e Debug

O sistema loga avisos em caso de erros na geração de headers, mas falha silenciosamente para não quebrar o fluxo de pagamentos. Verifique logs com:

```bash theme={null}
# Pattern: [OriginChannelHeaders] ou [AsaasClient]
tail -f storage/logs/laravel.log | grep -E "OriginChannelHeaders|AsaasClient"
```

## Importante

⚠️ **Modificação do Vendor**: O arquivo `vendor/leopaulo88/asaas-sdk-laravel/src/Support/AsaasClient.php` foi modificado.

**Recomendações**:

* Considerar fazer um fork do pacote e usar nova versão
* Ou usar patches do Composer (`cweagans/composer-patches`)
* Documentando esta modificação para atualizações futuras do pacote

***

## Implementação Detalhada

Esta seção documenta todos os passos realizados para implementar o sistema de headers de canal.

### 1. Criação do Serviço Principal

**Arquivo:** `app/Services/OriginChannelHeaders.php`

Serviço responsável por gerar os headers dinâmicos baseado na configuração e contexto atual:

```php theme={null}
<?php

namespace App\Services;

use Illuminate\Support\Facades\Config;

class OriginChannelHeaders
{
    /**
     * Gera headers dinâmicos para integração com canal Asaas
     */
    public function forCurrentTenant(): array
    {
        $headers = [];

        // Origin (fixo do canal)
        $origin = Config::get('asaas.origin_headers.origin');
        if ($origin) {
            $headers['Origin'] = $origin;
        }

        // Origin-Channel-Access-Token (fixo do canal)
        $channelToken = Config::get('asaas.origin_headers.channel_access_token');
        if ($channelToken) {
            $headers['Origin-Channel-Access-Token'] = $channelToken;
        }

        // Origin-Channel-External-Reference (tenant/loja atual)
        $externalRef = $this->getCurrentTenantReference();
        if ($externalRef) {
            $headers['Origin-Channel-External-Reference'] = $externalRef;
        }

        return $headers;
    }

    /**
     * Verifica se deve enviar headers de canal
     */
    public function shouldSendHeaders(): bool
    {
        return Config::get('asaas.origin_headers.enabled', false);
    }

    /**
     * Obtém referência externa do tenant atual
     */
    protected function getCurrentTenantReference(): ?string
    {
        try {
            // Prioridade 1: Sessão web (loja_id)
            $lojaId = session('loja_id');
            if ($lojaId) {
                return "loja_{$lojaId}";
            }

            // Prioridade 2: Host/domínio (multi-tenant)
            if (app()->bound('request')) {
                $request = app('request');
                $host = $request->getHost();
                
                if ($host && !in_array($host, ['localhost', '127.0.0.1', 'nortgraf.com.br', 'www.nortgraf.com.br'])) {
                    $parts = explode('.', $host);
                    if (count($parts) >= 2) {
                        return "tenant_{$parts[0]}";
                    }
                    return "host_{$host}";
                }
            }

            // Prioridade 3: Fallback configurado
            return Config::get('asaas.origin_headers.default_external_reference');

        } catch (\Exception $e) {
            \Log::warning('[OriginChannelHeaders] Erro ao obter referência do tenant', [
                'error' => $e->getMessage()
            ]);
            return null;
        }
    }
}
```

### 2. Configuração no arquivo `config/asaas.php`

Adicionada nova seção de configuração:

```php theme={null}
/*
|--------------------------------------------------------------------------
| Origin Headers (Canal/Parceria)
|--------------------------------------------------------------------------
|
| Headers adicionais enviados nas requisições para identificar o canal
| de origem das transações. Usado em parcerias com o Asaas.
|
*/
'origin_headers' => [
    'enabled' => (bool) env('ASAAS_ORIGIN_HEADERS_ENABLED', false),
    'origin' => env('ASAAS_ORIGIN_HEADER'),
    'channel_access_token' => env('ASAAS_CHANNEL_ACCESS_TOKEN'),
    'default_external_reference' => env('ASAAS_DEFAULT_EXTERNAL_REFERENCE'),
],
```

### 3. Registro no Container Laravel

**Arquivo:** `app/Providers/AppServiceProvider.php`

Adicionado no método `register()`:

```php theme={null}
// Registra o serviço de headers de canal para integração com Asaas
$this->app->singleton(\App\Services\OriginChannelHeaders::class, function ($app) {
    return new \App\Services\OriginChannelHeaders();
});
```

### 4. Modificação do SDK Asaas

**Arquivo:** `vendor/leopaulo88/asaas-sdk-laravel/src/Support/AsaasClient.php`

Modificado o método `getHeaders()`:

```php theme={null}
/**
 * @return array<string, mixed>
 */
protected function getHeaders(): array
{
    $headers = [
        'access_token' => $this->config['api_key'],
        'Accept' => 'application/json',
        'Content-Type' => 'application/json',
        'User-Agent' => 'Asaas-SDK-Laravel/1.0',
    ];

    // Headers dinâmicos de canal (para integrações/parcerias)
    try {
        if (app()->bound(\App\Services\OriginChannelHeaders::class)) {
            $channelHeadersService = app(\App\Services\OriginChannelHeaders::class);
            if ($channelHeadersService->shouldSendHeaders()) {
                $dynamicHeaders = $channelHeadersService->forCurrentTenant();
                $headers = array_merge($headers, $dynamicHeaders);
            }
        }
    } catch (\Throwable $e) {
        // Falha silenciosa para não quebrar requisições se o serviço falhar
        if (function_exists('logger')) {
            logger()->warning('[AsaasClient] Erro ao obter headers de canal', [
                'error' => $e->getMessage()
            ]);
        }
    }

    return $headers;
}
```

### 5. Arquivo de Exemplo de Configuração

**Arquivo:** `.env.asaas.example`

```bash theme={null}
# =============================================================================
# ASAAS - HEADERS DE CANAL/PARCERIA
# =============================================================================

# Ativar headers customizados do canal nas requisições para Asaas
ASAAS_ORIGIN_HEADERS_ENABLED=true

# Origem/domínio do canal (header Origin)
ASAAS_ORIGIN_HEADER=https://seudominio.com.br

# Token de acesso do canal fornecido pelo Asaas
ASAAS_CHANNEL_ACCESS_TOKEN=ch_seu_token_aqui

# Referência externa padrão (fallback quando não conseguir detectar tenant)
ASAAS_DEFAULT_EXTERNAL_REFERENCE=default_ref
```

### 6. Atualização da Documentação Principal

**Arquivo:** `docs/asaas_module.md`

Adicionada seção sobre headers de canal na documentação principal do módulo Asaas.

### 7. Fluxo de Funcionamento

1. **Inicialização**: Sistema verifica se `ASAAS_ORIGIN_HEADERS_ENABLED=true`
2. **Detecção do Tenant**: Obtém referência do tenant atual via:
   * `session('loja_id')` → formato `loja_{id}`
   * Host/domínio → formato `tenant_{subdomain}` ou `host_{domain}`
   * Fallback → valor de `ASAAS_DEFAULT_EXTERNAL_REFERENCE`
3. **Geração de Headers**: Monta array com 3 headers customizados
4. **Injeção Automática**: Todos os métodos do SDK (`payments()->create()`, `pixQrCode()`, etc.) incluem os headers
5. **Logging**: Erros são logados mas não quebram o fluxo de pagamento

### 8. Exemplo de Requisição Resultante

Com configuração:

```bash theme={null}
ASAAS_ORIGIN_HEADERS_ENABLED=true
ASAAS_ORIGIN_HEADER=https://nortgraf.com.br
ASAAS_CHANNEL_ACCESS_TOKEN=ch_nortgraf_123
```

E sessão com `loja_id = 42`, uma requisição típica para criar pagamento incluiria:

```http theme={null}
POST /payments HTTP/1.1
Host: www.asaas.com
access_token: $aact_YWJjZGVmZ2hpams...
Accept: application/json
Content-Type: application/json
User-Agent: Asaas-SDK-Laravel/1.0
Origin: https://nortgraf.com.br
Origin-Channel-Access-Token: ch_nortgraf_123
Origin-Channel-External-Reference: loja_42

{
  "customer": "cus_abc123",
  "billingType": "PIX",
  "value": 99.90,
  "description": "Pedido #1234"
}
```

### 9. Verificação e Testes

Para testar se está funcionando:

1. **Verificar configuração**:

```bash theme={null}
php artisan tinker
>>> config('asaas.origin_headers')
```

2. **Testar geração de headers**:

```bash theme={null}
php artisan tinker
>>> $service = app(\App\Services\OriginChannelHeaders::class);
>>> $service->shouldSendHeaders()
>>> $service->forCurrentTenant()
```

3. **Monitorar logs**:

```bash theme={null}
tail -f storage/logs/laravel.log | grep -E "OriginChannelHeaders|AsaasClient"
```

### 10. Considerações de Segurança

* **Token sensível**: `ASAAS_CHANNEL_ACCESS_TOKEN` deve ser tratado como credencial secreta
* **Fallback seguro**: Sistema falha silenciosamente para não quebrar pagamentos
* **Logs limitados**: Não loga dados sensíveis, apenas erros de configuração
* **Validação**: Headers só são enviados se todas as validações passarem

### 11. Manutenção e Atualizações

**`Importante para futuras atualizações do pacote leopaulo88/asaas-sdk-laravel:`**

1. **Backup da modificação**: Salve o diff do `AsaasClient.php`
2. **Monitor de atualizações**: Use `composer show leopaulo88/asaas-sdk-laravel`
3. **Reaplicação**: Após atualizar o pacote, reaplicar a modificação no `getHeaders()`

**Opções recomendadas**:

* Fork do repositório: `git clone` + modificação + `composer.json` apontando para seu fork
* Patches do Composer: Use `cweagans/composer-patches` para automatizar a aplicação

***

## Melhorias Implementadas (Janeiro 2025)

Esta seção documenta as melhorias de segurança e robustez para multi-tenant implementadas após análise detalhada do sistema.

### 1. Priorização Correta de Detecção de Tenant

**Problema Identificado**: A detecção de tenant via `session('loja_id')` pode falhar em contextos CLI/queue, e o fallback para subdomínio podia extrair dados incorretos de domínios customizados de clientes.

**Solução Implementada**: Nova ordem de prioridade robusta no método `getCurrentTenantReference()`:

```php theme={null}
/**
 * Obtém referência externa do tenant atual com priorização robusta
 */
protected function getCurrentTenantReference(): ?string
{
    try {
        // PRIORIDADE #1: Current Loja (funciona em web, CLI, jobs, queue)
        if (app()->bound('current_loja')) {
            $currentLoja = app('current_loja');
            if ($currentLoja) {
                $identifier = $currentLoja->id ?? $currentLoja->uuid ?? null;
                if ($identifier) {
                    return "loja_{$identifier}";
                }
            }
        }

        // PRIORIDADE #2: Sessão web (apenas para web requests)
        if (function_exists('session') && app()->bound('session.store')) {
            $lojaId = session('loja_id');
            if ($lojaId) {
                return "loja_{$lojaId}";
            }
        }

        // PRIORIDADE #3: Subdomínio APENAS se for *.nortgraf.com.br (evita domínios customizados)
        if (app()->bound('request')) {
            $host = app('request')->getHost();
            if ($host && str_ends_with($host, '.nortgraf.com.br')) {
                $subdomain = explode('.', $host)[0];
                if ($subdomain && $subdomain !== 'www') {
                    return "tenant_{$subdomain}";
                }
            }
        }

        // PRIORIDADE #4: Fallback configurado
        return Config::get('asaas.origin_headers.default_external_reference');

    } catch (\Exception $e) {
        \Log::warning('[OriginChannelHeaders] Erro ao obter referência do tenant', [
            'error' => $e->getMessage(),
            'trace' => app()->environment('local') ? $e->getTraceAsString() : null
        ]);
        return Config::get('asaas.origin_headers.default_external_reference');
    }
}
```

**Benefícios**:

* ✅ **current\_loja** tem prioridade máxima (funciona em todos os contextos)
* ✅ Subdomínio só é extraído de domínios `*.nortgraf.com.br` (evita problemas com domínios customizados)
* ✅ Fallback garantido para CLI, jobs sem contexto
* ✅ Logging melhorado com trace apenas em ambiente local

### 2. Atualização de Variáveis de Ambiente com Nomenclatura Melhorada

**Problema**: Nomes de variáveis ambíguos que podiam causar confusão.

**Solução**: Nomenclatura mais clara e consistente:

```bash theme={null}
# ANTES (confuso)
ASAAS_ORIGIN_HEADER=https://seudominio.com.br

# DEPOIS (claro e específico)
ASAAS_ORIGIN_VALUE=nortgraf-platform

# ANTES (genérico)
ASAAS_DEFAULT_EXTERNAL_REFERENCE=default_channel_ref

# DEPOIS (específico para propósito)
ASAAS_DEFAULT_EXTERNAL_REFERENCE=system
```

**Atualização na Configuração**:

```php theme={null}
'origin_headers' => [
    'enabled' => (bool) env('ASAAS_ORIGIN_HEADERS_ENABLED', false),
    'origin' => env('ASAAS_ORIGIN_VALUE'), // ⬅️ Renamed
    'channel_access_token' => env('ASAAS_CHANNEL_ACCESS_TOKEN'),
    'default_external_reference' => env('ASAAS_DEFAULT_EXTERNAL_REFERENCE', 'system'), // ⬅️ Default value
],
```

### 3. Debug Logging Temporário para Validação

**Objetivo**: Confirmar que headers estão sendo aplicados corretamente em produção.

**Implementação**: Debug logging temporário no `AsaasClient.php`:

```php theme={null}
protected function getHeaders(): array
{
    $headers = [
        'access_token' => $this->config['api_key'],
        'Accept' => 'application/json',
        'Content-Type' => 'application/json',
        'User-Agent' => 'Asaas-SDK-Laravel/1.0',
    ];

    // Headers dinâmicos de canal
    try {
        if (app()->bound(\App\Services\OriginChannelHeaders::class)) {
            $channelHeadersService = app(\App\Services\OriginChannelHeaders::class);
            if ($channelHeadersService->shouldSendHeaders()) {
                $dynamicHeaders = $channelHeadersService->forCurrentTenant();
                $headers = array_merge($headers, $dynamicHeaders);
                
                // 🔍 DEBUG TEMPORÁRIO - Confirmar aplicação de headers
                \Log::debug('[AsaasClient] Headers de canal aplicados', [
                    'enabled' => true,
                    'headers_count' => count($dynamicHeaders),
                    'origin' => $dynamicHeaders['Origin'] ?? null,
                    'has_channel_token' => isset($dynamicHeaders['Origin-Channel-Access-Token']),
                    'external_reference' => $dynamicHeaders['Origin-Channel-External-Reference'] ?? null,
                ]);
            } else {
                \Log::debug('[AsaasClient] Headers de canal desabilitados', ['enabled' => false]);
            }
        }
    } catch (\Throwable $e) {
        \Log::warning('[AsaasClient] Erro ao obter headers de canal', [
            'error' => $e->getMessage()
        ]);
    }

    return $headers;
}
```

**⚠️ Importante**: Este logging é **temporário** e deve ser removido após validação em produção.

### 4. Exemplo de Configuração Atualizada

**Arquivo**: `.env.asaas.example` (atualizado)

```bash theme={null}
# =============================================================================
# ASAAS - HEADERS DE CANAL/PARCERIA
# =============================================================================

# Ativar/Desativar headers de canal
ASAAS_ORIGIN_HEADERS_ENABLED=true

# Identificador do canal (header Origin) - use string simples e estável
# Evite URLs completas para não depender de domínio/ambiente
ASAAS_ORIGIN_VALUE=nortgraf-platform

# Token de acesso do canal fornecido pelo Asaas
ASAAS_CHANNEL_ACCESS_TOKEN=seu_token_do_canal_asaas

# Referência externa padrão (fallback para CLI, jobs, quando não há contexto de loja)
ASAAS_DEFAULT_EXTERNAL_REFERENCE=system
```

### 5. Validação em Produção

**Plano de Teste Direto**:

1. **Configurar ambiente**:

```bash theme={null}
ASAAS_ORIGIN_HEADERS_ENABLED=true
ASAAS_ORIGIN_VALUE=nortgraf-platform
ASAAS_CHANNEL_ACCESS_TOKEN=ch_seu_token
ASAAS_DEFAULT_EXTERNAL_REFERENCE=system
```

2. **Criar pagamento PIX sandbox**:

```php theme={null}
// Em algum controller/test
$payment = $asaasService->createPixPayment([
    'customer' => $customer->asaas_customer_id,
    'value' => 10.0,
    'description' => 'Teste headers canal'
]);
```

3. **Verificar logs**:

```bash theme={null}
tail -f storage/logs/laravel.log | grep "\[AsaasClient\]"
```

**Expected Output**:

```
[2025-01-21 15:30:42] local.DEBUG: [AsaasClient] Headers de canal aplicados {
    "enabled": true,
    "headers_count": 3,
    "origin": "nortgraf-platform",
    "has_channel_token": true,
    "external_reference": "loja_42"
}
```

### 6. Benefícios das Melhorias

**Robustez Multi-tenant**:

* ✅ Funciona consistentemente em web, CLI, queue, jobs
* ✅ Nunca "chuta" subdomínio de domínios customizados do cliente
* ✅ Sempre tem fallback funcional

**Segurança**:

* ✅ Logging limitado, não expõe tokens
* ✅ Graceful degradation se serviço falhar
* ✅ Validações de contexto antes de extract

**Manutenibilidade**:

* ✅ Nomes de variáveis mais claros
* ✅ Debug temporário para validação
* ✅ Documentação completa atualizada

**Compatibilidade**:

* ✅ Backwards-compatible com configurações existentes
* ✅ Zero breaking changes no comportamento
* ✅ Funciona com todos os métodos existentes do Asaas

### 7. Próximos Passos

1. **✅ Implementação completa** - Todas as melhorias aplicadas
2. **🟡 Validação produção** - Criar PIX sandbox e confirmar logs
3. **🟡 Remover debug logs** - Após confirmação que funciona
4. **🟡 Documentar fork/patch** - Para manutenção do vendor modification
