1. Design fee packages with clear naming and segmentation
Fee packages are the foundation of your fee logic. A well-organized package structure makes it easier to maintain, debug, and audit your fee configuration over time.
- Use descriptive names that reflect the business context (e.g., “pix-transfer-standard”, “wire-premium-segment”).
- Segment by product and customer group using
segmentId. This allows you to apply different fee rules to different customer tiers without creating conflicting packages. - Keep packages focused. A package that tries to cover too many scenarios becomes hard to test and maintain. Prefer multiple focused packages over one that does everything.
- Document your package structure internally. As your package count grows, a clear reference of which package applies where prevents misconfiguration.
2. Set fee priorities carefully
When a package contains multiple fees, the
priority field determines the order of execution. Getting this wrong can produce incorrect calculations.
- Priority 1 must always use
referenceAmount: originalAmount. The engine enforces this. - Fees with
isDeductibleFrom: truemust also usereferenceAmount: originalAmount. Deductible fees then always apply to the full transaction value. - Priority must be unique within a package. The engine rejects duplicate priorities.
- Think about fee dependencies. If one fee adjusts the transaction value and another fee should be calculated on the adjusted value, use
referenceAmount: afterFeesAmountwith a higher priority number. If the second fee should reference the original value, useoriginalAmount.
3. Always estimate before applying fees in production
Fees Engine provides an estimate endpoint that lets you preview fee calculations without writing anything to the ledger. Use estimation to:
- Validate new packages before activating them. Confirm that the calculated values match your expected results across different transaction amounts.
- Test edge cases: zero-amount transactions, boundary values at
minimumAmountandmaximumAmountthresholds, and exempted accounts. - Preview fees for users. If your product shows fees before confirmation, use the estimate endpoint to provide accurate previews.
- Debug unexpected results. If a calculated fee doesn’t match expectations, estimate the same transaction with a specific
packageIdto isolate the issue.
The calculate endpoint automatically selects the best matching package. The estimate endpoint requires a specific
packageId, giving you full control over which package to test.4. Manage exemptions explicitly
Fees Engine supports two types of exemptions: by transaction amount range and by account.
- Amount ranges (
minimumAmount,maximumAmount): Define the transaction value window in which fees apply. Transactions outside this range are exempt. Use this for promotional thresholds or tiered pricing. - Waived accounts (
waivedAccounts): Specific accounts exempt from fees within a package. Use this for internal accounts, employee accounts, or partnership arrangements.
- Keep waived account lists short and reviewed. Large lists become hard to audit. Periodically review which accounts are exempted and why.
- Document the business reason for each exemption in your internal records.
- Test exemption boundaries. If your range is R 300 and R$ 301 behave as expected.
5. Enable and tune caching for performance
Fees Engine caches fee packages in memory to reduce database queries during high traffic. Configure caching through environment variables:
PACKAGE_CACHE_ENABLED(default:true): Enables or disables the package cache.PACKAGE_CACHE_TTL_SECONDS(default:180): Time-to-live in seconds before the engine refreshes cached packages from the database.
- Keep caching enabled in production. It significantly reduces latency for high-volume transaction processing.
- Adjust TTL based on your change frequency. If you update packages frequently, use a shorter TTL (e.g., 60–120 seconds). If packages are stable, the default 180 seconds is appropriate.
- Be aware of cache delay. After you update a package, the change may take up to the configured TTL to propagate. If you need immediate effect, restart the service or temporarily reduce the TTL.
6. Use correct numeric values
Express all financial values in Fees Engine as strings using the
numeric type. This prevents floating-point precision errors that are common with decimal arithmetic.
- Always send values as strings, even whole numbers (e.g.,
"100"not100). - Never use floating-point types for monetary calculations in your integration layer.
- Note that the engine automatically adjusts fee splits with repeating decimals (e.g., R$ 10 divided by 3 accounts) to keep ledger totals exact.
7. Use soft delete for auditability
When you delete a fee package, Fees Engine marks it with a
deletedAt timestamp rather than removing it from the database. This preserves the audit trail for historical transactions that referenced that package.
- Don’t rely on hard deletion for fee packages in production. Historical transactions may reference deleted packages for reconciliation.
- Periodically review deleted packages if your database grows significantly. Archiving strategies can help manage storage without losing audit capability.
8. Monitor the Fees Engine in production
Fees Engine supports OpenTelemetry for traces and metrics. Enable it to gain visibility into fee calculation performance and behavior.
- Monitor health endpoints. Fees Engine exposes
/healthfor readiness and liveness checks (default port: 4002). - Set up alerts for sustained high latency on fee calculations, which may indicate database contention or cache misconfiguration.
- Monitor MongoDB connection pool usage, disk space, and replication health. The default
MONGO_MAX_POOL_SIZEis 100. - Review pod resource usage against your traffic patterns and autoscaling behavior.
9. Keep versions compatible
Before upgrading Fees Engine:
- Check the version compatibility table to confirm compatibility with your Midaz Core version.
- Always upgrade Midaz Core first, then Fees Engine.
- Back up your MongoDB data and Helm values before any major upgrade.
- Test the upgrade in a staging environment before applying it to production.
10. Review the security recommendations
Fees Engine processes financial data and integrates with Midaz ledger operations. Make sure your deployment follows the platform-wide Security recommendations, which cover:
- Network segmentation and Zero Trust Architecture
- Secret management and rotation (including
LICENSE_KEYand database credentials) - TLS 1.2+ enforcement for all communications
- RBAC configuration via Access Manager
- Patch management and vulnerability scanning
11. Design billing packages with clear scope
Each billing package should represent a single, well-defined charge. Avoid packing unrelated pricing into one package.
- Separate packages per transaction route. A package for Pix billing and a package for boleto billing are clearer than one package that tries to handle both.
- Use descriptive labels that include the billing type and target: “Pix Send Monthly Billing — Standard Tier” is better than “Billing Package 1”.
- One
accountTargettype per maintenance package. You cannot combinesegmentId,portfolioId, andaliasesin the same package. If you need different targets, create separate packages — a single/billing/calculatecall evaluates all active packages.
12. Write commercial terms against route volume
Volume calculation counts transactions per transaction route across the whole ledger. The free quota, the tiers, and the discount tiers all apply to that route-level total.
- State thresholds as route volume. “The first 100 transactions on the
pix-sendroute are free” maps directly onto a package. Write the contract in the same terms the engine bills in. - Split routes to split counts. One package per transaction route keeps each flow on its own quota, tiers, and discounts.
13. Plan free quotas and discount tiers carefully
Fees Engine evaluates free quotas and discounts in a specific order:
- The engine subtracts the free quota from the total count to get the billable count.
- The engine prices the billable count — for tiered packages it charges every billable unit at the single matching tier’s rate (volume pricing, not graduated).
- The engine applies one discount tier to the gross amount, chosen against the total count (before the free quota was subtracted).
- Free quotas reset each billing period. Set the value based on your commercial agreement per period (monthly, weekly, or daily), not lifetime.
- Tiers are volume brackets, not slices. A billable count of 1,750 against a 501–2,000 tier prices all 1,750 units at that tier’s rate. Crossing a bracket boundary changes the rate for the whole volume, so tier boundaries can move the bill sharply.
- Cover every positive billable count. If the billable count is positive and no tier’s range contains it, the calculation fails for that package. Leave the top tier’s upper bound open. Start the first tier at 1 — a billable count of zero produces a zero amount and an empty payload without a tier lookup.
- Discount tiers are cumulative thresholds, not ranges, and only one applies: the highest
minQuantitythe total count reaches. If you define discounts at 200 and 400 transactions, a client with 500 transactions gets the 400+ discount — not both. - Watch the two different counts. Pricing uses the billable count (after free quota); the discount uses the total count (before it).
14. Size maintenance account targets appropriately
Maintenance packages support three target types with different scale profiles:
Choose the target type that matches your operational scale. If you find yourself listing hundreds of aliases, migrate to a segment or portfolio in Midaz instead.
15. Handle billing failures with re-execution
Billing calculation follows an all-or-nothing policy. If any package fails, the engine returns no results.
- Build retry logic into your orchestrator. The calculation is stateless — re-executing for the same period produces the same results.
- Check error responses for the specific package and resource that failed. Common causes: invalid
ledgerId, unreachable Midaz API, or disabled billing packages. - Separate volume and maintenance calculations if one type consistently succeeds while the other fails. Call
/billing/calculatewith"type": "volume"and"type": "maintenance"independently to isolate failures.

