Pacotes de extensão
Algumas integrações são produtos por si só — feeds bancários com credenciais de provedor, fatura eletrônica em APIs governamentais. A REDA Financials estrutura essas integrações como pacotes de extensão: o núcleo define um contrato abstrato global, a extensão o implementa, e um registro de configuração conecta os dois. Hoje são dois contratos base, mais três interfaces de capacidade opcionais que um provedor implementa apenas se precisar delas.
Como o padrão funciona
Ambos os contratos seguem a mesma arquitetura, e ambos os cabeçalhos de classe descrevem isso com as mesmas palavras: "The core package resolves the provider at runtime via: Type.forName(setup.Provider_Class__c).newInstance()". Nenhum dos dois contratos codifica um fornecedor de forma fixa. Há quatro partes móveis, cada uma com um responsável claro:
| Parte | Responsável | Função |
|---|---|---|
Contrato abstrato — refin.BankFeedProvider / refin.EInvoiceProvider | Pacote principal | Classes abstratas globais (ambas marcadas com @since 2026-05-28) que definem as assinaturas de método e os formatos de resultado. Global = parte da superfície estável do pacote gerenciado. |
| Implementação | Pacote de extensão (ou sua organização) | Uma classe concreta que estende o contrato; contém toda a lógica de API específica do provedor. Segundo ambas as documentações de classe: "Authentication and API credentials are managed entirely by the extension package." |
Registro de configuração — refin__Bank_Feed_Setup__c / refin__E_Invoice_Setup__c | Seu administrador | Registra a classe implementadora totalmente qualificada em Provider_Class__c, além das configurações de ambiente, ativação e escopo. |
Objeto de resultado — BankFeedResult / EInvoiceResult | Pacote principal | A classe interna global que todo método retorna, para que os resultados sejam registrados de forma uniforme, seja qual for o provedor. |
refin.BankFeedProvider — extratos bancários automatizados
- O contrato: três métodos que uma implementação fornece —
connect(o fluxo de vinculação do provedor),sync(buscar transações desde o cursor e entregar linhas de extrato),disconnect(revogar o acesso). Os resultados retornam em um formato padrão (sucesso, transações importadas, cursor atualizado, saldo, erro). - A conexão: um registro de Configuração de feed bancário por conta conectada — nome do provedor, ambiente (Sandbox/Production), o nome da classe implementadora, cursor de sincronização e status (Active / Paused / Error / Disconnected), última sincronização. As configurações ficam na página de registro da conta bancária.
- As credenciais permanecem com a extensão — o núcleo nunca vê os tokens do provedor; a extensão é responsável por toda a sua autenticação de ponta a ponta.
- O resultado: os extratos chegam como extratos bancários comuns (origem de importação marcada de acordo) e seguem para a mesma correspondência e conciliação que um CSV teria — automação a montante, fluxo de trabalho familiar a jusante.
O contrato de feed bancário, método a método
Cada método recebe o registro de configuração como seu contexto. A documentação da classe explica o modelo: "Each method receives the Bank_Feed_Setup__c record as the primary context — the provider reads the linked bank account and its own credentials to execute sync logic."
global abstract BankFeedResult sync(refin__Bank_Feed_Setup__c setup);
global abstract BankFeedResult connect(
refin__Bank_Feed_Setup__c setup,
Map<String, String> params
);
global abstract void disconnect(refin__Bank_Feed_Setup__c setup);
| Método | O que o contrato exige dele (segundo a documentação da classe) |
|---|---|
sync | "The provider fetches new transactions from the bank API (using the sync cursor from the setup record), converts them to Bank Statement Lines via the core package's internal import path, and returns the updated cursor for persistence." A documentação acrescenta que é "Called by the core scheduled batch job" — veja a ressalva de orquestração acima; o lado do agendamento depende da versão do pacote principal. |
connect | Inicia o fluxo de vinculação para uma nova conta bancária. "Provider-specific: may involve OAuth redirect, token exchange, or API key validation. The params map carries provider-specific connection parameters (e.g. public_token for Plaid, link_id for Belvo)." |
disconnect | "Revokes access tokens, removes webhooks, and cleans up provider-side resources. The core package updates the setup record's Sync_Status to Disconnected after this method completes." |
BankFeedResult é uma classe interna global — todo membro é @AuraEnabled global, e o construtor define success como false e transactionsImported como 0 por padrão, de modo que uma implementação precisa conquistar um sucesso:
| Membro | Tipo | Significado |
|---|---|---|
success | Boolean | Se a operação foi concluída. Padrão false. |
transactionsImported | Integer | Quantas transações a sincronização entregou. Padrão 0. |
updatedCursor | String | A nova posição incremental, retornada "for persistence" para que a próxima sincronização retome de onde esta terminou. |
currentBalance | Decimal | O saldo da conta informado pelo provedor no momento da sincronização. |
errorMessage | String | Detalhe honesto da falha quando success é falso. |
refin__Bank_Feed_Setup__c — referência de campos
| Campo | O que ele contém |
|---|---|
Bank_Account__c | A conta bancária REDA que esse feed atende — uma configuração por conta conectada. |
Provider_Class__c (rótulo "Provider Class") | O coração da conexão. Descrição literal: "Fully qualified Apex class name that extends refin.BankFeedProvider… Extension packages register their implementation here (e.g. plaid.PlaidBankFeedProvider)." |
Provider__c | Apenas um nome de exibição — segundo sua própria descrição, "The actual integration logic is determined by Provider_Class__c, not this field." |
Environment__c | Qual ambiente do provedor a conexão tem como alvo (Sandbox / Production). |
Is_Active__c | Se o feed participa da sincronização. |
Sync_Cursor__c | A posição incremental do provedor — sync a lê e retorna o valor atualizado em BankFeedResult.updatedCursor. |
Sync_Status__c | Lista de seleção: Active / Paused / Error / Disconnected. A ajuda do valor Error é explícita: "Error: last sync failed — check Audit Log for details." |
Last_Sync__c | Quando o feed sincronizou pela última vez. |
Provider_Account_Id__c | O identificador, no lado do provedor, da conta vinculada. |
Um esqueleto de implementação
Aqui está o formato de um provedor — a equipe da Meridian esboçando um contra a API direta do banco americano deles. Apenas as peças do contrato são reais; os comentários marcam onde entra a lógica do seu provedor:
global class MeridianBankFeedProvider extends refin.BankFeedProvider {
global override refin.BankFeedProvider.BankFeedResult connect(
refin__Bank_Feed_Setup__c setup, Map<String, String> params) {
refin.BankFeedProvider.BankFeedResult result =
new refin.BankFeedProvider.BankFeedResult();
// Exchange the connection parameters (e.g. public_token) with the
// provider. Store credentials in YOUR package — never in core objects.
result.success = true;
return result;
}
global override refin.BankFeedProvider.BankFeedResult sync(
refin__Bank_Feed_Setup__c setup) {
refin.BankFeedProvider.BankFeedResult result =
new refin.BankFeedProvider.BankFeedResult();
// 1. Fetch transactions since setup.refin__Sync_Cursor__c
// 2. Deliver them as bank statement lines
// 3. Return the new cursor so it can be persisted
result.transactionsImported = 42;
result.updatedCursor = 'cursor-after-this-batch';
result.success = true;
return result;
}
global override void disconnect(refin__Bank_Feed_Setup__c setup) {
// Revoke tokens and webhooks on the provider side; the core
// sets Sync_Status to Disconnected after this completes.
}
}
refin.EInvoiceProvider — fatura eletrônica obrigatória
- O contrato:
submit(montar o payload da jurisdição a partir do documento e chamar a autoridade),checkStatus(consultar autoridades assíncronas),cancel(regras específicas por jurisdição se aplicam — a própria nota do contrato cita a janela de 24 horas do Brasil e a aceitação do destinatário no México). - A conexão: uma Configuração de fatura eletrônica por entidade/país — provedor, ambiente, classe implementadora, quais tipos de documento participam, e uma opção Auto Submit On Post; toda tentativa de envio é registrada como um Histórico de fatura eletrônica no documento (status, UUID e número fiscal, payloads, resposta, contagem de tentativas) — a trilha de auditoria de conformidade que os reguladores exigem.
- Nota sobre disponibilidade: os históricos e listas relacionadas são visíveis em todo o aplicativo (você já viu Históricos de fatura eletrônica nas páginas de fatura); o envio ao vivo requer a extensão de provedor da sua jurisdição — confirme o que está disponível para o seu país com a REDA.
O contrato de fatura eletrônica, método a método
| Método | Assinatura | Contrato |
|---|---|---|
| submit | global abstract EInvoiceResult submit(refin__E_Invoice_Trail__c trail, SObject sourceDocument) | O documento de origem é um refin__Sales_Invoice__c ou refin__Purchase_Invoice__c; o provedor monta o XML/JSON da jurisdição e chama a API governamental; o núcleo atualiza o Histórico a partir do resultado |
| checkStatus | global abstract EInvoiceResult checkStatus(refin__E_Invoice_Trail__c trail) | Consulta para autoridades assíncronas que respondem depois |
| cancel | global abstract EInvoiceResult cancel(refin__E_Invoice_Trail__c trail) | "Jurisdiction-specific rules apply (e.g. Brazil NF-e cancellation within 24h, Mexico CFDI cancellation with receiver acceptance)." |
Os três retornam um EInvoiceResult — membros: success, status, fiscalUUID, fiscalNumber, responseCode, responseMessage, xmlPayload, responsePayload, errorMessage.
Os objetos de fatura eletrônica — referência de campos
Campo de refin__E_Invoice_Setup__c | Função |
|---|---|
Entity__c · Country_Code__c · Environment__c · Is_Active__c | Qual empresa, qual jurisdição, teste vs. produção, ativado/desativado |
Provider__c | Apenas nome de exibição |
Provider_Class__c | A classe Apex totalmente qualificada que estende refin.EInvoiceProvider (exemplo do próprio campo: sovos.SovosEInvoiceProvider) |
Document_Types__c | Seleção múltipla: Fatura de venda / Nota de crédito / Fatura de compra / Nota de crédito de fornecedor — o "core package checks this field when determining whether to create an E-Invoice Trail record" |
Auto_Submit_On_Post__c | Literal: "When checked, posting a Sales Invoice or Purchase Invoice automatically creates an E-Invoice Trail record in Draft status, triggering the asynchronous submission process. When unchecked, e-invoice submission must be initiated manually via a quick action or API call." |
O registro refin__E_Invoice_Trail__c é a linha de auditoria de cada envio: referências à configuração e à fatura de origem, Document_Type__c, Status__c, Fiscal_UUID__c, Fiscal_Number__c, XML_Payload__c, os campos de resposta (Response_Code__c/Message__c/Payload__c/Date__c), Submitted_Date__c, Retry_Count__c e Provider_Document_Id__c — a cadeia de evidências que os reguladores exigem, uma linha por tentativa. Status__c tem oito valores: Draft, Ready to Send, Queued, Submitted, Accepted, Rejected, Cancelled, Error.
Interfaces de capacidade opcionais — como um contrato congelado ainda cresce
O refin.EInvoiceProvider foi publicado como classe abstrata global em uma versão Managed-Released, o que significa que nenhum método pode mais ser adicionado a ele. Em vez de congelar o framework junto com ele, a REDA adiciona o comportamento novo como interfaces globais separadas, algo que o Salesforce permite. Um provedor estende EInvoiceProvider e implementa as interfaces de capacidade que suporta; o núcleo detecta o suporte com instanceof e simplesmente pula a capacidade para os provedores que não a implementam.
| Interface | Métodos | Implemente quando |
|---|---|---|
refin.EInvoiceQueueProvider | drain(setup, maxRecords) retornando List<EInvoiceEvent>; acknowledge(setup, events) | Sua autoridade fiscal responde por uma fila de notificações em vez de uma resposta síncrona. Um único drain carrega as duas direções — atualizações de status de saída e documentos de fornecedor de entrada — diferenciadas por EInvoiceEvent.processType. Deliberadamente não existe um método separado para "buscar entrada". |
refin.EInvoiceDocumentProvider | fetchDocuments(trail) retornando List<EInvoiceDocument> | Você recupera os artefatos legais (XML assinado, resposta da autoridade, uma representação imprimível como o DANFE brasileiro) sob demanda em vez de entregá-los junto com o evento. Dispense-a se seus eventos já trazem documents. |
refin.EInvoiceProvisioner | provisionOrganization(setup, args); provisionCompany(setup, args), ambos retornando EInvoiceProvisionResult | Você suporta o provisionamento self-service das estruturas de conta do lado da autoridade a partir do próprio aplicativo. É uma capacidade de fase posterior: hoje essas estruturas são criadas manualmente pelo provedor de conformidade. Ela é declarada agora justamente porque uma API global já publicada não pode ser estendida depois. |
acknowledge apenas quando cada evento já está armazenado de forma durável; confirmar antes perderia a notificação para sempre. Um provedor não deve habilitar nenhum modo de confirmação automática que se antecipe a essa chamada, e drain deve devolver somente notificações não confirmadas, para que drains sucessivos avancem.
Os três tipos de dados que elas trocam
refin.EInvoiceEvent: um marco na vida de um documento perante a autoridade. O núcleo o correlaciona com uma trilha porerpDocumentId, um valor que o seu provedor carimba no envelope de saída e que a autoridade devolve. Ostatusé normalizado para Accepted, Rejected, Cancelled ou InProgress: o núcleo baseia as novas tentativas e os estados terminais nesse campo, nunca noresponseCodebruto. Também carreganotificationId,processType(Outbound / Inbound),responseMessage,fiscalUUID,fiscalNumber, uma lista opcionaldocuments,payloadeeventDate.refin.EInvoiceDocument: um artefato, comtitle,fileExtension('xml' ou 'pdf'),base64BodyedocumentKind('Legal', 'LegalResponse' ou 'Representation'). O núcleo o decodifica e o anexa à trilha e à fatura de origem como arquivos do Salesforce, para que os usuários vejam os artefatos em conformidade no registro com que já trabalham.refin.EInvoiceProvisionResult:success(que por padrão éfalse),externalId(o identificador do lado do provedor) emessage.
documentKind, processType e status são todos String e não enums, deliberadamente, para que o conjunto permitido possa crescer sem quebrar uma assinatura global congelada. Não faça um switch exaustivo sobre eles: trate os valores que você conhece e deixe os demais passarem.
Erros comuns a evitar
| Erro | Por que prejudica | Faça isto em vez disso |
|---|---|---|
Erros de digitação em Provider_Class__c | A resolução em tempo de execução é feita pelo nome — uma string errada faz o provedor simplesmente nunca rodar | Copie o nome totalmente qualificado (namespace.NomeDaClasse) da documentação da extensão; teste a conexão imediatamente |
Confiar em Provider__c para trocar de provedor | É apenas um rótulo de exibição; "The actual integration logic is determined by Provider_Class__c, not this field." | Altere Provider_Class__c; atualize o rótulo para os humanos |
| Armazenar credenciais de autoridades/bancos nos registros de configuração | O contrato é explícito: "Authentication and API credentials are managed entirely by the extension package" | As credenciais vivem nas credenciais nomeadas próprias da extensão, governadas pela segurança da sua organização |
| Construir o código da sua organização contra os internos de um pacote de provedor | Pacotes de provedor evoluem em seu próprio cronograma — apenas os contratos globais refin. são a costura estável | Integre por meio dos registros de configuração e dos históricos; trate o provedor como uma caixa-preta |
| Presumir detalhes de orquestração sem testar sua versão | O comportamento de agendamento e envio automático depende da versão | Verifique o agendamento de sincronização e o comportamento de envio automático em um sandbox nas suas versões instaladas antes de prometê-los ao negócio |
Para equipes que constroem uma extensão
- Implemente a classe abstrata no namespace do seu pacote; registre o nome de classe totalmente qualificado no registro de configuração.
- Seja responsável pelas suas credenciais (credenciais nomeadas no seu pacote), pelo seu mapeamento de API, pela sua semântica de erros — retorne resultados honestos; o núcleo os exibe em Sync Status e nos históricos.
- Programe apenas contra o contrato — a superfície global documentada é a estável (as regras).
O que vem a seguir
- Importação de extratos bancários — para onde os feeds fluem.
- Integrações do AppExchange — o panorama mais amplo do ecossistema.
- Desenvolvimento em Apex na REDA — as regras da superfície global.
Perguntas frequentes
O contrato foi projetado para o ecossistema de agregadores (Plaid, Belvo e afins são os arquétipos que ele nomeia). Pergunte à REDA quais extensões de provedor estão disponíveis atualmente para sua região — a disponibilidade evolui mais rápido do que a documentação.
O contrato resolve a classe implementadora pelo nome, então uma implementação local na organização é tecnicamente endereçável — sensata para uma API bancária sob medida. Empacote-a corretamente se ela vier a sobreviver a uma única organização.
Varia país a país (Brasil, México, Itália e Arábia Saudita lideram; muitos seguem). Seu consultor tributário responde pelo "se devemos"; a configuração + a extensão do provedor respondem pelo "como".
BankFeedResult informa success, transactionsImported, updatedCursor (onde a próxima sincronização retoma), currentBalance e errorMessage — os valores padrão são honestos (success=false, zero importado), para que um provedor descuidado não relate vitória por acidente. EInvoiceResult traz a resposta da autoridade: identificadores fiscais, payloads de resposta e o texto do erro quando ela recusou.
É a saúde da conexão em uma visão rápida — Active / Paused / Error / Disconnected. A própria orientação do campo para o estado ruim: "Error: last sync failed — check Audit Log for details." Disconnected é definido pelo núcleo depois que o disconnect do seu provedor é concluído.
Isso foi útil?