Appearance
Fee calculation
A SQL-driven port of FeesCalculatorService. For each fee_type in (IN, OUT), find the single most specific active rule.
Matching: specificity ranking
sql
select *,
(case when business_id @> array[:business_id] then 0 else 1 end
+ case when risk_level @> array[:risk_level] then 0 else 1 end
+ case when card_brand @> array[:card_brand] then 0 else 1 end) as _rank
from fee_rules
where rule_type = :rule_type
and status = 'active'
and currency_code = :currency_code
and (acquirer is null or acquirer @> array[:acquirer])
and (business_id is null or business_id @> array[:business_id])
and (payment_method_id is null or payment_method_id @> array[:pmid])
-- ... country_code, risk_level, card_brand with the same null-or-match pattern
order by _rank asc
limit 1;Tonder's Mongo
$or/$existsnull handling becomes Postgres(col is null or col @> array[:val]). Specificity ranking is the_rankexpression. Missing either IN or OUT rule → errorE0013. There is a matching simulator in the Admin Panel.
Formulas (port verbatim)
The tonder side label is renamed to platform in the response, since Vecnet is the platform. Use the decimal library for all of this.
| Category | Formula |
|---|---|
| PAYMENT / APMS | fee = max(amount×rate/100 + fixed_fee, minimum_fee); iva = fee×iva_rate/100; rr = amount×hold_reserve_percentage/100; net = amount − fee − iva − rr. International vs domestic selects inter/intra rates; APMS is always domestic. |
| REFUND | fee = refund_fee; net = amount + fee + iva (merchant absorbs fees on top). |
| DISPUTE | fee = chargeback_fee; if Won → net = fee + iva, else net = amount + fee + iva. |
| VOID | all zero; net = amount. |
| WITHDRAWAL | fee = max(amount×rate/100 + fixed_fee, minimum_fee); net = amount (fees posted as separate entries). |
| TOPUP | hardcoded default rule (0 rates/fees, fixed_days_delay), no rule lookup. |
Fees feed the posting function, which records them as DEBIT/CREDIT entries across the platform revenue/expense and VAT accounts.