DEV Community

Cover image for I built a zero-dependency, single-file freelancer fee calculator (PayPal vs Wise vs Upwork vs Fiverr vs Stripe vs Payoneer)
showayzun
showayzun

Posted on

I built a zero-dependency, single-file freelancer fee calculator (PayPal vs Wise vs Upwork vs Fiverr vs Stripe vs Payoneer)

If you invoice international clients, the number on the invoice is not the number you get. A platform takes a cut, a payment processor takes another, withdrawing costs a fixed fee, converting to your local currency loses a few percent, and you should be setting money aside for tax.

I kept doing this math in spreadsheets, so I built a small calculator for it: Freelancer Payout Fee Calculator. You put in one invoice amount and it shows what each of six platforms or payment methods leaves you, side by side.

Disclosure up front: this is ours. The same small team also sells a Notion template for tracking invoices and cash flow. The calculator is free, needs no sign-up, and works without it.

This post covers the formula, where every default number comes from (and which ones I couldn't confirm), and how the page is built.

The formula

Every platform goes through the same pipeline. Only the parameters change:

platform fee  = gross × platform%
payment fee   = gross × payment% + payment fixed   (min applied where noted)
after fees    = gross − platform fee − payment fee
withdraw fee  = withdraw fixed
FX loss       = (after fees − withdraw fee) × FX%   (only if converting)
pre-tax       = after fees − withdraw fee − FX loss
tax reserve   = pre-tax × tax%
net           = pre-tax − tax reserve
% lost        = (gross − net) ÷ gross
Enter fullscreen mode Exit fullscreen mode

Here's the core function, copied straight from the page:

function computeOne(p, gross, opts){
  opts = opts || {};
  var convert = opts.convert !== false;
  var fxPct = (opts.fxOverride === null || opts.fxOverride === undefined || opts.fxOverride === "" || isNaN(opts.fxOverride)) ? p.fxPct : Number(opts.fxOverride);
  var taxPct = Number(opts.taxPct) || 0;
  var g = Math.max(0, Number(gross) || 0);
  var platform = g * p.platformPct / 100;
  var payment = g > 0 ? Math.max(g * p.payPct / 100 + p.payFixed, p.payMin || 0) : 0;
  var afterFees = g - platform - payment;
  var withdraw = g > 0 ? p.wdFixed : 0;
  var base = Math.max(0, afterFees - withdraw);
  var fx = convert ? base * fxPct / 100 : 0;
  var preTax = base - fx;
  var tax = preTax * taxPct / 100;
  var net = preTax - tax;
  // ...returns all parts plus lostPct / feesLostPct / local
}
Enter fullscreen mode Exit fullscreen mode

It's plain ES5 so it runs in old in-app mobile browsers as well as Node.

Worked example: $1,000 on Upwork

With the default assumptions (10% service fee, $0.99 local-bank withdrawal, 2% FX placeholder, no tax):

  • Platform fee: $1,000 × 10% = $100.00
  • Withdrawal: $0.99
  • FX loss: ($1,000 − $100 − $0.99) × 2% = $17.98
  • Net: $881.03, so 11.9% lost

Add a 20% tax reserve and a local rate of 7.1 and you get tax $176.21, net $704.82, about 5,004.25 in local currency. Those exact figures are asserted in the test file (more on that below).

The comparison at $1,000

Same $1,000 invoice, default assumptions, FX applied, no tax:

Platform / method Net % lost
Wise (receive USD) $994.00 0.60%
Payoneer (direct) $970.20 2.98%
Stripe (intl. card) $946.14 5.39%
PayPal invoice (intl.) $921.12 7.89%
Upwork $881.03 11.90%
Fiverr $783.02 21.70%

Two caveats before anyone screenshots this. First, it isn't a fair race. Upwork and Fiverr are marketplaces: you use them because the client is there, and part of what you pay for is finding work. The other four are just ways to get paid. Second, Wise's lead depends on a placeholder FX rate (see below). Treat the table as a starting point, then put in the numbers your own account shows.

Where each fee comes from

Every default sits in an editable "assumptions" panel on the page, with a source link and a "checked 2026-10" note.

Platform Default Status Source
Upwork 10% service fee, $0.99 withdrawal, 2% FX fee % and FX: verify Service fee, local bank withdrawal
Fiverr 20% seller fee, $1 withdrawal (bank via Payoneer), 2% FX FX: verify Withdrawal fees
PayPal invoice, intl. 3.49% + 1.50% intl = 4.99% + $0.49 fixed (€/£0.39), 3.00% conversion confirmed PayPal merchant fees
Stripe, intl. card (US account) 2.9% + 1.5% = 4.4% + $0.30, +1% if conversion is needed confirmed Stripe pricing
Wise, receive USD ACH free ($6.11 for wire/Swift), 0.6% FX FX: verify Wise receive pricing
Payoneer, direct 1% (min $1) to a non-local receiving account, 2% FX FX: verify Payoneer pricing

Why some values say "verify": I didn't want to make up a precise-looking number where the official source doesn't give one.

  • Upwork's fee varies from 0% to 15% per contract. You see it on each offer, so 10% is only a placeholder.
  • Upwork and Fiverr FX: neither publishes the exchange margin on withdrawals, so 2% is a labeled placeholder.
  • Wise publishes a conversion fee range of 0.22%–4.42% depending on the currency pair. 0.6% is a placeholder; check Wise's own calculator for your pair.
  • Payoneer lists 1.2%–4% for withdrawals with currency conversion. 2% sits inside that range, but your rate depends on your corridor.

What's left out

  • Upwork's $2.99 fee per Direct-to-US-Bank withdrawal for non-US tax addresses (from 2026-09-01)
  • Payoneer's $29.95/yr account fee if you receive under $6,000 in 12 months
  • Fiverr buyer fees and Upwork Connects
  • Correspondent/intermediary bank fees

These are listed on the page too. I'd rather say what's missing than imply the number is complete.

How it's built

The constraint was: one HTML file, no dependencies, no build step.

  • Single file, inline CSS and JS. No framework, no CDN, no external fonts. The whole thing is about 18 KB.
  • Works offline via file://. You can download it, email it, or open it on a phone with no network. The only external URLs are the source links.
  • Pure core, thin UI. The calculation lives in its own <script id="calc"> block with no DOM access, and the UI script just reads inputs and renders tables. That split made testing easy.
  • Node tests against the real file. The test doesn't import a separate module. It reads index.html, regex-extracts the calc script, runs it with new Function, and checks results against hand-computed formulas: $1,000 Upwork → $881.03, PayPal $500 with no conversion → $474.56, Stripe with 0.5% FX → $950.92, Payoneer's $1 minimum on $50, Fiverr $1,000 → $783.02, and a zero-amount edge case. 17 assertions, and I rerun them on every version (the GitHub Pages build and a Chinese translation share the same core).
  • SEO without a framework. Canonical URL, Open Graph and Twitter tags, and two JSON-LD blocks (WebApplication and FAQPage). The FAQ answers come only from the sourced fee data, with the same "verify" caveats. A one-line Node check JSON.parses each block so a stray quote doesn't silently break it.
  • OG image from headless Chrome. A tiny HTML card rendered with google-chrome --headless --screenshot --window-size=1200,630. No design tool needed.
  • Static hosting. GitHub Pages, plus a robots.txt and sitemap.xml. The same file also uploads to itch.io as an HTML project.

The biggest lesson: keep the math pure and test it against the shipped artifact rather than a copy. When I made the SEO build and the translated version, I copied the calc block byte for byte, and the same test file passed against each one.

Please correct me

Fee schedules change and depend a lot on country and account type. If you have a real payout statement that disagrees with a default, especially one of the verify ones, please leave a comment with the platform, your country, and what you were actually charged. I'll update the defaults (with a thanks in the comments).

If you track invoices in Notion, we also make a Freelance Client & Cashflow OS template. That's the only plug.

Try the calculator here: Freelancer Payout Fee Calculator. All numbers are estimates, not financial or tax advice.

Top comments (0)