RPAfelApi Architecture Analysis
📊 Overview
RPAfelApi is a production-ready .NET/C# API for FEL certification that demonstrates several excellent patterns we should consider adopting in FlowPOS.
🎯 Key Features & Patterns
1. XSD Code Generation ✅
What they do:
xsd GT_Documento-0.1.2.xsd xmldsig-core-schema.xsd /c /out:../Models /namespace:GT_Documento
Analysis:
- Uses
xsd.exe(Microsoft tool) to generate C# classes from XSD - Same approach as FELgt repository
- Ensures 100% schema compliance
For FlowPOS:
- ✅ Confirms XSD code generation is the right approach
- ✅ We should do the same for TypeScript
- ✅ Validates our FELgt contribution idea
2. Certificate Caching 🎯 EXCELLENT PATTERN
Their Flow:
Request → Check if rpaUUID exists in storage →
If FOUND: Return cached certificate (200)
If NOT FOUND: Sign → Save → Publish → Return (200)
Why This is Brilliant:
-
Performance:
- Avoids re-signing same document
- Faster response times
- Reduces certifier API calls
-
Cost:
- Certifiers may charge per request
- Caching saves money
-
Idempotency:
- Same request = same response
- Prevents duplicate certifications
- Better error recovery
For FlowPOS - Implementation:
// Add to FelService
async certifyDocument(params: ICertifyDocumentParameters): Promise<undefined> {
const { document } = params;
const internalReference = document.addendumData.internalReference; // sale.id
// Check cache first
const cached = await this.felCertificationCacheRepository.findByInternalReference(
internalReference
);
if (cached && cached.status === 'certified') {
return cached.certifierResponse; // Return cached
}
// Not cached, proceed with certification
const result = await this.provider.certifyDocument({...});
// Save to cache
await this.felCertificationCacheRepository.create({
internalReference,
certifierResponse: result,
status: 'certified',
// ... other fields
});
return result;
}
Database Table:
CREATE TABLE fel_certification_cache (
id UUID PRIMARY KEY,
internal_reference VARCHAR NOT NULL UNIQUE, -- sale.id
business_id UUID NOT NULL,
document_type VARCHAR, -- FACT, NCRE, NDEB, ANULACION
certifier_response JSONB NOT NULL,
status VARCHAR, -- 'certified', 'failed', 'pending'
created_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ, -- Optional: cache expiration
INDEX idx_internal_reference (internal_reference)
);
3. "Already Signed" Error Handling 🎯 IMPORTANT
Their Flow:
FirmarFactura → 406 (already signed) →
FirmarFacturaGet (fetch from GFACE) →
Save to DB → Publish → 200
Why This Matters:
- Scenario: Document was already certified (maybe by another system, retry, etc.)
- Problem: Certifier returns 406 "Already signed"
- Solution: Fetch the existing certificate instead of failing
For FlowPOS:
async certifyDocument(params: ICertifyDocumentParameters): Promise<undefined> {
try {
return await this.provider.certifyDocument({...});
} catch (error) {
// Check if it's "already signed" error
if (error.response?.status === 406 ||
error.response?.data?.Codigo === 406) {
// Try to fetch existing certificate
const existing = await this.fetchExistingCertificate(
params.document.addendumData.internalReference
);
if (existing) {
// Cache it and return
await this.cacheCertificate(existing);
return existing;
}
}
throw error;
}
}
4. Event Publishing (Pub/Sub) 🎯 GOOD PATTERN
Their Flow:
Save to DB → Publish Topic → 200
Why This is Good:
-
Decoupling:
- Certification doesn't block other processes
- Multiple subscribers can react
- Async processing
-
Scalability:
- Can handle high volume
- Queue-based processing
- Better error recovery
For FlowPOS:
You already have event-driven architecture! Just need to ensure:
- ✅ Events are published after certification
- ✅ Multiple handlers can subscribe
- ✅ Consider adding Pub/Sub for distributed systems
Current Implementation:
// You already do this!
this.eventEmitter.emit(
OnDocumentCertifiedEvent.eventName,
new OnDocumentCertifiedEvent(saleEventRecord, certifyDocumentResult),
);
Enhancement - Add Pub/Sub:
// After certification
await this.pubSubService.publish('fel.document.certified', {
saleId: saleEventRecord.id,
felUuid: result.Autorizacion,
certifier: felCertifier,
timestamp: new Date(),
});
5. Internal Reference (rpaUUID)
Their Pattern:
- Use
rpaUUIDas unique identifier - Maps to your
internalReference(sale.id) - Used for caching and lookups
For FlowPOS:
- ✅ You already use
internalReference: saleEventRecord.id - ✅ Perfect for caching key
- ✅ Good for idempotency
🔄 Comparison: RPAfelApi vs FlowPOS
| Feature | RPAfelApi | FlowPOS | Recommendation |
|---|---|---|---|
| XSD Generation | ✅ C# classes | ❌ Manual | Add TypeScript generation |
| Certificate Caching | ✅ Yes | ❌ No | Add this! |
| Already Signed Handling | ✅ Yes | ❌ No | Add this! |
| Event Publishing | ✅ Pub/Sub | ✅ Events | Keep events, consider Pub/Sub |
| Error Handling | ✅ Comprehensive | ✅ Good | Enhance with 406 handling |
| Idempotency | ✅ Via cache | ⚠️ Partial | Add caching for full idempotency |
🚀 Recommended Enhancements for FlowPOS
Priority 1: Certificate Caching ⭐⭐⭐
Why:
- Prevents duplicate certifications
- Faster responses
- Cost savings
- Better error recovery
Implementation:
- Create
fel_certification_cachetable - Check cache before certifying
- Save to cache after certification
- Use
internalReference(sale.id) as key
Priority 2: "Already Signed" Handling ⭐⭐⭐
Why:
- Handles edge cases gracefully
- Prevents failures on retries
- Better user experience
Implementation:
- Catch 406 errors
- Fetch existing certificate from certifier
- Cache and return
Priority 3: Enhanced Error Logging ⭐⭐
Why:
- Better debugging
- Track certification failures
- Monitor certifier issues
Implementation:
- Save errors to database
- Include full request/response
- Track retry attempts
📋 Implementation Plan
Phase 1: Certificate Caching
Migration:
CREATE TABLE fel_certification_cache (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
business_id UUID NOT NULL REFERENCES business(id),
internal_reference VARCHAR NOT NULL, -- sale.id
document_type VARCHAR, -- FACT, NCRE, NDEB, ANULACION
original_invoice_uuid VARCHAR, -- For NCRE/NDEB/ANULACION
certifier VARCHAR NOT NULL,
certifier_response JSONB NOT NULL,
status VARCHAR NOT NULL, -- 'certified', 'failed', 'pending'
created_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ,
UNIQUE(internal_reference, document_type)
);
CREATE INDEX idx_fel_cache_internal_ref ON fel_certification_cache(internal_reference);
CREATE INDEX idx_fel_cache_business ON fel_certification_cache(business_id);
Service Update:
// FelService.certifyDocument()
async certifyDocument(params: ICertifyDocumentParameters): Promise<undefined> {
const cacheKey = this.getCacheKey(params.document);
// Check cache
const cached = await this.felCacheRepository.findByKey(cacheKey);
if (cached?.status === 'certified') {
this.logger.log(`Returning cached certificate for ${cacheKey}`);
return cached.certifierResponse;
}
// Proceed with certification...
const result = await this.provider.certifyDocument({...});
// Save to cache
await this.felCacheRepository.create({
...cacheKey,
certifierResponse: result,
status: 'certified',
});
return result;
}
Phase 2: Already Signed Handling
Update Provider Services:
async certifyDocument(params: IProviderCertifyDocumentParameters): Promise<undefined> {
try {
return await this.httpService.post(...);
} catch (error) {
// Check for "already signed"
if (this.isAlreadySignedError(error)) {
// Fetch existing certificate
return await this.fetchExistingCertificate(params);
}
throw error;
}
}
private isAlreadySignedError(error: any): boolean {
return error.response?.status === 406 ||
error.response?.data?.Codigo === 406 ||
error.response?.data?.Mensaje?.includes('ya certificado');
}
private async fetchExistingCertificate(params: IProviderCertifyDocumentParameters): Promise<undefined> {
// Call certifier's "get certificate" endpoint
// Implementation depends on certifier API
}
💡 Key Insights
1. Caching is Critical
- RPAfelApi shows production-grade caching
- Prevents duplicate certifications
- Essential for high-volume systems
2. Error Handling Matters
- "Already signed" is a real scenario
- Need graceful handling
- Fetch existing vs fail
3. Idempotency via Internal Reference
- Use
internalReference(sale.id) as cache key - Same sale = same certificate
- Prevents duplicates
4. Async Processing
- Pub/Sub for scalability
- Your event system is good
- Consider adding message queue
🎯 Action Items
Immediate (High Value)
-
Add Certificate Caching:
- Create
fel_certification_cachetable - Update
FelServiceto check cache - Save results to cache
- Create
-
Handle "Already Signed":
- Catch 406 errors
- Fetch existing certificate
- Return gracefully
Short Term
-
Enhanced Error Logging:
- Save errors to database
- Track retry attempts
- Monitor certifier health
-
Cache Expiration:
- Optional: Add TTL to cache
- Refresh stale certificates
- Cleanup old entries
Long Term
- Pub/Sub Integration:
- Add message queue (Redis, Pub/Sub)
- Async certification processing
- Better scalability
📚 References
- RPAfelApi: (Internal repository - C# .NET implementation)
- XSD Generation: Same approach as FELgt
- Certificate Caching: Production best practice
- Error Handling: Real-world scenario handling
Last Updated: 2025-12-18
Status: Analysis Complete - Ready for Implementation