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
digifactandinfilecertify XML payloads rpafelapiis 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
voidCertificateendpoint, 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
| Layer | Codepaths | Responsibility |
|---|---|---|
| Domain | domain/fel.interface.ts, domain/electronic-certification-provider.interface.ts, repository ports | FEL document shapes, provider contract, validation result types, persistence contracts |
| Application | application/fel.service.ts, fel-credit-note.service.ts, fel-debit-note.service.ts, fel-cancellation.service.ts, provider.service.ts, event handlers | Build documents, choose provider path, enforce use-case rules, emit domain events |
| Infrastructure | infrastructure/provider-digifact.service.ts, provider-infile.service.ts, provider-rpafelapi.service.ts, Kysely repositories | Provider HTTP calls, token handling, XML/JSON transport details, persistence adapters |
| Interfaces | interfaces/fel.controller.ts, DTOs, pagination queries | HTTP 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 key | Adapter |
|---|---|
digifact | ProviderDigifactService |
infile | ProviderInfileService |
rpafelapi | ProviderRpaFelApiService |
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":
- Load the business by
document.businessData.id. - Extract the certifier config from
business.felCertifierConfig. - Resolve provider API URL from
GuatemalanCertifiersUrls. - Decrypt the configured certifier token.
- Reject expired JWT-style tokens because direct certifier refresh is not implemented in this path.
- Convert the document to XML with
XmlConversionService. - Resolve
document.felProviderData.providerNamethroughProviderService. - Call
provider.certifyDocument({ taxId, xmlContent, apiUrl, token }).
RPAfelApi path
When USE_RPA_FEL_API === "true":
- Build the DTE JSON payload from the document using
XmlToDteJsonService. - Inject certifier credentials into the DTE payload.
- Resolve
rpafelapithroughProviderService. - Pass
JSON.stringify(dteJson)in thexmlContentfield 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:
- validates that the original sale invoice is certified
- checks the provided original invoice UUID against
sale.felAuthorization - prevents the credit amount from exceeding the original sale total
- builds and certifies the credit note
- 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:
- load the sale and original invoice UUID from
sale.felAuthorization - validate cancellation eligibility
- persist a pending cancellation
- 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.
| Area | Routes |
|---|---|
| Core utilities | POST /fel/convert-to-xml, POST /fel/certify-document, POST /fel/get-shared-info |
| Sale retry | POST /fel/sales/:id/retry |
| Credit notes | GET/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 notes | Same pattern under /fel/debit-notes |
| Cancellations | Same 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.felCertifierConfigmust contain the provider config expected by the selected path.- Direct certifier JWTs are not refreshed by
FelService; expired JWTs fail fast. getSharedInforesolves the business certifier provider, notrpafelapi.- RPA cancellation uses the authorization number (
Serie-Numero) forNumeroDocumentoAAnular. - The provider port accepts
xmlContent, butrpafelapicurrently 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.
Related docs
- FEL certifier registry — which certifiers appear in billing UI vs full SAT registry
- FEL Module Architecture
- Retail sales FEL workflow
- Restaurant FEL workflow