LDG-005
Minimum withdrawals and fees, driven by settings
Compute the fee once, show the net before they confirm, and keep every threshold in the settings table so repricing is not a code change.
A withdrawal minimum exists because network and processor costs do not scale down. Below a certain amount you lose money paying someone out, and the minimum is where you decide that line sits.
Fees have to be visible before confirmation. A user who requests 1000 and receives 950 without warning will assume they were cheated, and on a small site that story spreads faster than any explanation.
Keep the numbers in settings, per currency if you pay several. Coin prices move; a minimum hardcoded last year is either turning away good withdrawals or paying out at a loss.
function withdrawal_quote(string $currency, int $requestedUnits): array
{
$cur = strtoupper($currency);
$min = setting_int('min_withdraw_' . $cur, 0);
$flatFee = setting_int('fee_flat_' . $cur, 0); // in units
$pctFee = (float) setting('fee_percent_' . $cur, '0'); // e.g. 2.5
$errors = [];
if ($min > 0 && $requestedUnits < $min) {
$errors[] = 'The minimum for ' . $cur . ' is ' . $min . ' units.';
}
$percentPart = (int) ceil($requestedUnits * $pctFee / 100);
$fee = $flatFee + $percentPart;
$net = $requestedUnits - $fee;
if ($net <= 0) {
$errors[] = 'The fee would consume the whole withdrawal.';
}
return [
'requested' => $requestedUnits,
'fee' => $fee,
'net' => max(0, $net),
'min' => $min,
'errors' => $errors,
];
}
Using it
Render the quote live next to the amount field, and again on the confirmation step. The number they see should be the number that arrives.
Store requested, fee and net as three separate columns on the withdrawal row. Deriving the fee later from a setting that has since changed gives you the wrong history.
What bites people
Ceil the fee and floor the payout. Rounding both in the user's favour is a slow leak with no ledger entry.
Charge the fee on the requested amount, not on the net, or your arithmetic is circular and the numbers will not tie out.
Per-currency settings, not one global minimum. The same USD value is a wildly different unit count across coins.
This one touches real money. Point it at a throwaway wallet and watch a full cycle before you trust it with a live balance.