The ReconciliationService has been fully implemented to detect and resolve transaction mismatches between payment providers and blockchain networks. The system runs every 10 minutes and automatically resolves discrepancies when consensus is reached.
-
ReconciliationService (
src/modules/reconciliation/services/reconciliation.service.ts)- Main orchestrator for reconciliation logic
- Detects PROVIDER_MISMATCH, BLOCKCHAIN_MISMATCH, and BOTH_MISMATCH scenarios
- Auto-resolves when both sources agree
- Escalates ambiguous cases for manual review
-
BlockchainService (
src/modules/blockchain/blockchain.service.ts)- Interface-based implementation for testability
- Provides RPC calls to blockchain nodes
- Tracks transaction confirmations and finalization status
- Configurable via environment variables
-
ConfirmationTracker (
src/modules/blockchain/confirmation.tracker.ts)- Monitors blockchain transactions for finalization
- Detects and handles chain reorganizations (reorgs)
- Automatically updates transaction status on finalization
-
BlockchainModule (
src/modules/blockchain/blockchain.module.ts)- Encapsulates blockchain services and makes them injectable
- PROVIDER_MISMATCH: Provider reports success/failure but blockchain hasn't confirmed yet
- BLOCKCHAIN_MISMATCH: Blockchain confirms while provider API is unavailable/silent
- BOTH_MISMATCH: Provider and blockchain disagree (conflicting data)
When both provider and blockchain agree on a terminal state (SUCCESS or FAILED), the transaction is automatically resolved without manual intervention.
When no consensus exists or sources conflict, issues are marked as ESCALATED for manual review to prevent false resolutions.
- API timeouts/failures don't cause false positives
- Blockchain RPC errors are gracefully handled
- Missing transaction metadata doesn't crash the system
CREATE TABLE reconciliation_issues (
id UUID PRIMARY KEY,
transactionId UUID NOT NULL REFERENCES transactions(id),
mismatchType VARCHAR(50) NOT NULL,
internalStatus VARCHAR(50) NOT NULL,
providerStatus VARCHAR(50),
blockchainStatus VARCHAR(50),
status VARCHAR(50) DEFAULT 'OPEN',
resolution TEXT,
rawSnapshot JSONB,
createdAt TIMESTAMP DEFAULT NOW(),
updatedAt TIMESTAMP,
INDEX idx_recon_status(status),
INDEX idx_recon_transaction(transactionId)
);-- Requires these fields:
- externalId: VARCHAR(100) - Payment provider transaction ID
- metadata: JSONB - Should contain { txHash: '0x...' }# Blockchain Configuration
BLOCKCHAIN_RPC_URL=http://localhost:8545
BLOCKCHAIN_REQUIRED_CONFIRMATIONS=12
# Payment Provider Configuration
PROVIDER_API_URL=https://api.payment-provider.com
PROVIDER_API_KEY=your_api_key_here
PROVIDER_API_TIMEOUT=30000PROVIDER_API_TIMEOUT: Default 30000msBLOCKCHAIN_REQUIRED_CONFIRMATIONS: Default 12 blocks
GET /admin/reconciliation?page=1&limit=20&status=ESCALATEDQuery Parameters:
page: Page number (default: 1)limit: Items per page (default: 20)status: Filter by status (OPEN, AUTO_RESOLVED, ESCALATED)
Response:
{
"success": true,
"data": [
{
"id": "uuid",
"transactionId": "uuid",
"mismatchType": "BOTH_MISMATCH",
"internalStatus": "PENDING",
"providerStatus": "SUCCESS",
"blockchainStatus": "FAILED",
"status": "ESCALATED",
"resolution": "No consensus — manual review required",
"createdAt": "2023-01-01T00:00:00Z"
}
],
"meta": {
"page": 1,
"limit": 20,
"total": 5,
"totalPages": 1
}
}The reconciliation job runs every 10 minutes and:
- Finds all PENDING transactions older than 5 minutes
- Fetches status from both provider and blockchain
- Detects mismatches and initiates resolution
- Logs statistics: flagged, auto-resolved, escalated count
Cron Expression: EVERY_10_MINUTES (0 */10 * * * *)
The system normalizes provider statuses to standard transaction states:
| Provider Status | Internal Status |
|---|---|
| PENDING | PENDING |
| PROCESSING | PENDING |
| COMPLETED | SUCCESS |
| SUCCEEDED | SUCCESS |
| FAILED | FAILED |
| FAILURE | FAILED |
| CANCELLED | CANCELLED |
- Queries
eth_getTransactionReceiptto check if mined - Verifies confirmation count against required threshold
- Returns status based on receipt.status field
0x1(1) = SUCCESS0x0(0) = FAILED- null = pending or not found
private deriveResolution(
providerStatus: string | null,
blockchainStatus: string | null
): 'SUCCESS' | 'FAILED' | null {
const terminalStates = ['SUCCESS', 'FAILED'];
const candidates = [providerStatus, blockchainStatus].filter(Boolean);
if (candidates.length === 0) return null;
// All sources agree and it's a terminal state
const allAgree = candidates.every((s) => s === candidates[0]);
if (allAgree && terminalStates.includes(candidates[0]!)) {
return candidates[0] as 'SUCCESS' | 'FAILED';
}
return null; // Escalate for manual review
}-
ReconciliationService (63 test cases)
- Mismatch detection (PROVIDER, BLOCKCHAIN, BOTH)
- Auto-resolution scenarios
- Escalation logic
- Provider status normalization
- Error handling
- Pagination and filtering
-
BlockchainService (24 test cases)
- Transaction receipt retrieval
- Block number queries
- Transaction status determination
- Confirmation count verification
- RPC error handling
-
ConfirmationTracker (15 test cases)
- Transaction tracking lifecycle
- Finalization on confirmed blocks
- Orphaned transaction detection
- Chain reorg handling
- Module shutdown cleanup
- End-to-end reconciliation flows
- Multi-transaction scenarios
- Pagination and filtering
- Status persistence across runs
// When a transaction is submitted to blockchain:
const txHash = '0x...';
const tracker = app.get(ConfirmationTracker);
await tracker.trackTransaction(txHash);const reconciliation = app.get(ReconciliationService);
await reconciliation.runReconciliation();const issues = await reconciliation.getIssues({
status: 'ESCALATED',
limit: 50,
});- Cron job scans only PENDING transactions older than 5 minutes
- Database indices on status, transactionId, and createdAt
- Batch processing doesn't block the event loop
- Blockchain: Use a dedicated RPC node, not public endpoints
- Provider API: Implement caching for recent transactions
- Database: Regular cleanup of resolved issues (older than 30 days)
- Logging: Use appropriate log levels in production
- Verify environment variables are set correctly
- Check blockchain RPC endpoint accessibility
- Ensure provider API key is valid
- Confirm transaction metadata includes txHash
- Check that both provider and blockchain agree on status
- Verify transaction statuses are being normalized correctly
- Ensure no conflicting data in raw sources
- Transactions marked as FAILED if block becomes invalid
- Confirmation tracker automatically detects and handles reorgs
- Check blockchain logs for reorg activity
- API Keys: Store PROVIDER_API_KEY in secrets manager, never commit to repo
- RPC Endpoints: Use trusted nodes only, validate all responses
- Database: Ensure reconciliation tables have proper access controls
- Logging: Don't log sensitive transaction data or API responses
- Parallel Provider Support: Handle multiple payment providers simultaneously
- Multi-Chain Support: Extend blockchain service for Polygon, Arbitrum, etc.
- Webhook Integration: Notify stakeholders of escalated issues
- Machine Learning: Predict likely resolution based on historical patterns
- Custom Reconciliation Rules: Allow domain-specific logic per transaction type
For issues or questions:
- Check the test files for usage examples
- Review error logs in application logger
- Validate environment configuration
- Contact the development team for custom scenarios