Saltar al contenido principal

FEL Provider Port

Source-backed guide for the FEL/SAT provider pattern in apps/backend/src/fel/.

Use this page when adding or troubleshooting electronic invoicing providers. For product workflows, see the retail and restaurant FEL workflow pages under dev/fel/.


Intent

The FEL module keeps tax-document use cases independent from provider HTTP details. Application services build and validate invoice, credit note, debit note, and cancellation documents; infrastructure adapters handle provider-specific request formats.

This separation matters because Guatemala FEL providers do not expose identical APIs:

  • direct certifiers such as digifact and infile certify XML payloads
  • rpafelapi is an intermediary adapter and receives a JSON DTE payload through the same provider port parameter
  • cancellation can use the standard document certification path or the RPA voidCertificate endpoint, depending on runtime routing

Do not describe provider URL formats, token refresh behavior, or HTTP retries as domain behavior. Those are adapter concerns.


Hexagonal map

LayerCodepathsResponsibility
Domaindomain/fel.interface.ts, domain/electronic-certification-provider.interface.ts, repository portsFEL document shapes, provider contract, validation result types, persistence contracts
Applicationapplication/fel.service.ts, fel-credit-note.service.ts, fel-debit-note.service.ts, fel-cancellation.service.ts, provider.service.ts, event handlersBuild documents, choose provider path, enforce use-case rules, emit domain events
Infrastructureinfrastructure/provider-digifact.service.ts, provider-infile.service.ts, provider-rpafelapi.service.ts, Kysely repositoriesProvider HTTP calls, token handling, XML/JSON transport details, persistence adapters
Interfacesinterfaces/fel.controller.ts, DTOs, pagination queriesHTTP routes, Swagger metadata, request parsing

Provider contract

All provider adapters implement ElectronicCertificationProvider:

export interface ElectronicCertificationProvider {
certifyDocument(
params: IProviderCertifyDocumentParameters,
): Promise<CertifierResponse | undefined>;

getSharedInfo(params: IProviderGetSharedInfoParameters): Promise<unknown>;
}

ProviderService maps provider names to adapters:

Provider keyAdapter
digifactProviderDigifactService
infileProviderInfileService
rpafelapiProviderRpaFelApiService

Unknown provider names fail fast with Unknown provider: (<name>).


Certification routing

FelService.certifyDocument chooses the certification path before it calls the provider port.

Direct certifier path

When USE_RPA_FEL_API !== "true":

  1. Load the business by document.businessData.id.
  2. Extract the certifier config from business.felCertifierConfig.
  3. Resolve provider API URL from GuatemalanCertifiersUrls.
  4. Decrypt the configured certifier token.
  5. Reject expired JWT-style tokens because direct certifier refresh is not implemented in this path.
  6. Convert the document to XML with XmlConversionService.
  7. Resolve document.felProviderData.providerName through ProviderService.
  8. Call provider.certifyDocument({ taxId, xmlContent, apiUrl, token }).

RPAfelApi path

When USE_RPA_FEL_API === "true":

  1. Build the DTE JSON payload from the document using XmlToDteJsonService.
  2. Inject certifier credentials into the DTE payload.
  3. Resolve rpafelapi through ProviderService.
  4. Pass JSON.stringify(dteJson) in the xmlContent field of the provider port.

rpafelapi is an intermediary, not the fiscal certifier itself. The business certifier still determines fiscal provider-specific fields that are placed in the DTE payload.


Document workflows

Sale invoice certification

Sales events call FelService.processSaleEvent when a sale is submitted or reviewed. On success, the service emits fel.documentCertified; the sales module persists FEL fields on the sale. On failure, the sale flow continues and FEL status is marked failed.

Credit notes (NCRE)

FelCreditNoteService:

  1. validates that the original sale invoice is certified
  2. checks the provided original invoice UUID against sale.felAuthorization
  3. prevents the credit amount from exceeding the original sale total
  4. builds and certifies the credit note
  5. emits fel.creditNote.certified

Inventory listens to the certified credit-note event for reversal behavior.

Debit notes (NDEB)

FelDebitNoteService follows the same certified-invoice and UUID checks as credit notes. Tax parity validation is still marked as a TODO in FelValidationService; do not claim that new-tax prevention is enforced until that TODO is implemented.

Cancellations (ANULACION)

Retail cancellation requests use FelCancellationService:

  1. load the sale and original invoice UUID from sale.felAuthorization
  2. validate cancellation eligibility
  3. persist a pending cancellation
  4. certify the cancellation

Validation currently enforces:

  • original invoice must be certified
  • cancellation date must be in the same calendar month/year as the sale date
  • cancellation is blocked when credit notes already exist for the sale

When USE_RPA_FEL_API === "true", certification uses ProviderRpaFelApiService.voidCertificate and sends the invoice authorization as felSerialNumber-felNumber, not the UUID.


Public HTTP surface

Base controller: FelController at /fel.

AreaRoutes
Core utilitiesPOST /fel/convert-to-xml, POST /fel/certify-document, POST /fel/get-shared-info
Sale retryPOST /fel/sales/:id/retry
Credit notesGET/POST /fel/credit-notes, GET /fel/credit-notes/:id, GET /fel/credit-notes/sale/:saleId, POST /fel/credit-notes/:id/certify, PDF/print endpoints
Debit notesSame pattern under /fel/debit-notes
CancellationsSame pattern under /fel/cancellations

Only POST /fel/sales/:id/retry has an explicit @Permission(Sale, Update) decorator in this controller. Other routes still run through the backend's global authentication guard unless separately marked public.


Common pitfalls

  • business.felCertifierConfig must contain the provider config expected by the selected path.
  • Direct certifier JWTs are not refreshed by FelService; expired JWTs fail fast.
  • getSharedInfo resolves the business certifier provider, not rpafelapi.
  • RPA cancellation uses the authorization number (Serie-Numero) for NumeroDocumentoAAnular.
  • The provider port accepts xmlContent, but rpafelapi currently receives JSON serialized into that field.
  • FEL provider network failures are surfaced as gateway-style errors; inspect provider response bodies only when an HTTP response exists.