How the Indonesia PPh Final UMKM (MSME final income tax) calculator is verified

Inputs

InputFieldType
Business turnover for the year (IDR)annualTurnovernumber
Business expenses for the year (IDR)annualExpensesnumber
How you are taxedregimestring
Free profession (doctor, lawyer, consultant, artist, influencer…)freeProfessionboolean
Turnover went above Rp4.8 billion in an earlier tax yearoverCeilingBeforeboolean
Marital status on 1 JanuarymaritalStatusstring
Dependants you fully support (at most 3 count)dependantsnumber

The formula

Each derived value below is computed by the Valem engine from the expression shown, in this order. Nothing else runs — there is no hidden code path.

Derived valueFieldFormula (JSONata)
Final AppliesfinalAppliesregime = 'final' and $not(freeProfession) and $not(overCeilingBefore)
Over Ceiling This YearoverCeilingThisYear$n(annualTurnover) > $const.finalTurnoverCeiling
Final tax (0.5% above Rp500 million)finalTaxfinalApplies ? $round($max([0, $n(annualTurnover) - $const.finalExemptTurnover]) * $const.finalRate, 2) : 0
Profitprofit$round($max([0, $n(annualTurnover) - $n(annualExpenses)]), 2)
PTKP (non-taxable income)ptkp$const.ptkpSelf + (maritalStatus = 'married' ? $const.ptkpMarried : 0) + ($d := $floor($n(dependants)); ($d > $const.maxDependants ? $const.maxDependants : $d)) * $const.ptkpPerDependant
Taxable income at normal ratestaxableIncomefinalApplies ? 0 : ($v := profit - ptkp; $v > 0 ? $floor($v / 1000) * 1000 : 0)
Tax at 5–35%progressiveTax$round($bandedCharge(taxableIncome, [{"upTo": $const.pitBand1, "rate": $const.pitRate1}, {"upTo": $const.pitBand2, "rate": $const.pitRate2}, {"upTo": $const.pitBand3, "rate": $const.pitRate3}, {"upTo": $const.pitBand4, "rate": $const.pitRate4}, {"upTo": null, "rate": $const.pitRate5}]), 2)
Income tax / yearincomeTax$round(finalTax + progressiveTax, 2)
Net income / yearnetIncome$round($max([0, $n(annualTurnover) - $n(annualExpenses) - incomeTax]), 2)

Rates and thresholds

The expressions above read these as $const. They are the figures the annual refresh replaces, so they live as data rather than being written into the formulas — which is what makes a year-on-year change a one-line diff instead of an edit to arithmetic.

ConstantValue
finalRate0.005
finalExemptTurnover500000000
finalTurnoverCeiling4800000000
ptkpSelf54000000
ptkpMarried4500000
ptkpPerDependant4500000
maxDependants3
pitBand160000000
pitBand2250000000
pitBand3500000000
pitBand45000000000
pitRate10.05
pitRate20.15
pitRate30.25
pitRate40.3
pitRate50.35

Test vectors (7)

Every case is executed against this model on each build, by the Java engine and independently by a JavaScript one. A mismatch of a single cent fails the build, so the page cannot ship advertising a figure the model no longer produces.

Case 1 — S1 Rp1.2 billion turnover on the 0.5% final tax: 0.5% of the Rp700 million above Rp500 million = Rp3,500,000

given annualTurnover1200000000
given annualExpenses900000000
given regimefinal
given freeProfessionno
given maritalStatussingle
given dependants0
given overCeilingBeforeno
expect finalAppliestrue
expect finalTax3500000
expect incomeTax3500000
expect netIncome296500000

Case 2 — S2 the same business on the normal rates: Rp300 million profit less Rp54 million PTKP — Rp30,900,000

given annualTurnover1200000000
given annualExpenses900000000
given regimenormal
given freeProfessionno
given maritalStatussingle
given dependants0
given overCeilingBeforeno
expect finalAppliesfalse
expect taxableIncome246000000
expect progressiveTax30900000
expect incomeTax30900000
expect netIncome269100000

Case 3 — S3 turnover of exactly Rp500 million: no final tax

given annualTurnover500000000
given annualExpenses300000000
given regimefinal
given freeProfessionno
given maritalStatussingle
given dependants0
given overCeilingBeforeno
expect finalTax0
expect incomeTax0

Case 4 — S4 a free profession cannot use the final tax and is taxed at the normal rates

given annualTurnover600000000
given annualExpenses200000000
given regimefinal
given freeProfessionyes
given maritalStatusmarried
given dependants2
given overCeilingBeforeno
expect finalAppliesfalse
expect ptkp67500000
expect taxableIncome332500000
expect progressiveTax52125000

Case 5 — S5 Rp5 billion this year, at most Rp4.8 billion last year: still 0.5% to the year's end (art. 61(1)) — Rp22,500,000

given annualTurnover5000000000
given annualExpenses4500000000
given regimefinal
given freeProfessionno
given maritalStatussingle
given dependants0
given overCeilingBeforeno
expect finalAppliestrue
expect overCeilingThisYeartrue
expect finalTax22500000
expect incomeTax22500000
expect netIncome477500000

Case 6 — S6 exactly Rp4.8 billion is still in the scheme (“tidak melebihi”): Rp21,500,000

given annualTurnover4800000000
given annualExpenses4000000000
given regimefinal
given freeProfessionno
given maritalStatussingle
given dependants0
given overCeilingBeforeno
expect finalAppliestrue
expect finalTax21500000

Case 7 — S7 turnover went above Rp4.8 billion in an earlier year: art. 17 rates on profit — Rp80,500,000

given annualTurnover5000000000
given annualExpenses4500000000
given regimefinal
given freeProfessionno
given overCeilingBeforeyes
given maritalStatussingle
given dependants0
expect finalAppliesfalse
expect finalTax0
expect taxableIncome446000000
expect progressiveTax80500000
expect netIncome419500000

Sources

Check it yourself — and reuse it

The whole model as JSON — inputs, outputs, formulas, vectors and sources in one re-runnable document.

This model is published under CC BY 4.0: use it, adapt it, build on it, with attribution. The licence covers the model — the expression of each rule and the arrangement of the catalog. The rates and thresholds are law, and nobody licenses those; the sources above are where they come from.

← Back to the Indonesia PPh Final UMKM (MSME final income tax) calculator

Built with Valem — a live, verifiable calculator you can fork and embed.