Skip to main content
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:
# 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:
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:
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:
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:
# 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

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:
/*
|--------------------------------------------------------------------------
| 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():
// 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():
/**
 * @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
# =============================================================================
# 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:
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:
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:
php artisan tinker
>>> config('asaas.origin_headers')
  1. Testar geração de headers:
php artisan tinker
>>> $service = app(\App\Services\OriginChannelHeaders::class);
>>> $service->shouldSendHeaders()
>>> $service->forCurrentTenant()
  1. Monitorar logs:
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():
/**
 * 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:
# 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:
'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:
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)
# =============================================================================
# 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:
ASAAS_ORIGIN_HEADERS_ENABLED=true
ASAAS_ORIGIN_VALUE=nortgraf-platform
ASAAS_CHANNEL_ACCESS_TOKEN=ch_seu_token
ASAAS_DEFAULT_EXTERNAL_REFERENCE=system
  1. Criar pagamento PIX sandbox:
// Em algum controller/test
$payment = $asaasService->createPixPayment([
    'customer' => $customer->asaas_customer_id,
    'value' => 10.0,
    'description' => 'Teste headers canal'
]);
  1. Verificar logs:
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