Arizona 沒有預扣税級距:如何建模由僱員選擇的預扣率
Arizona has no tax brackets: modelling a withholding rate the employee chooses
文章展示如何在薪金計算器中建模 Arizona 的預扣税:僱員可在 Form A-4 選擇預扣率,並可加上每張薪金支票的固定金額;如未提交表格,則預設按 2.0% 預扣。計算器以輸入值處理選擇率,並把零與未提供的數值區分,避免把 0% 誤當成未選擇而套用預設值。預扣金額只是預繳款,最終税款須在報税時結算。
I keep a paycheck calculator that supports 37 US states. Every state I had added before Arizona had a formula: a flat rate after allowances, or brackets with a standard deduction. Arizona has neither for withholding. The employee fills in Form A-4 and picks a percentage of gross taxable wages: 0.5%, 1.0%, 1.5%, 2.0%, 2.5%, 3.0% or 3.5%. Pick zero if you expect no Arizona tax. Optionally add a fixed dollar amount per paycheck. If you never hand in the form, the employer withholds 2.0%.
That is a different shape of problem. The rate is an input, not a rule, and the input may be missing.
The data model
My engine describes each state as a plain object. Flat states already had rate, so Arizona is a flat state with two extra flags.
arizona: { name: 'Arizona', kind: 'flat', rate: 0.02, azElect: true,
noDeduction: true, employee: [] }
rate holds the default for the "no form on file" case. The calculation takes the elected percentage when there is one:
const pct = (s.azElect && allowances.pct != null) ? allowances.pct : s.rate;
incomeTax = taxable * pct;
if (s.azElect) incomeTax += allowances.dollar || 0; // extra dollars per paycheck
The page form is a select with the seven percentages plus zero, and it defaults to 2.0% to match what an employer does without the form.
The zero trap
The first version I wrote was allowances.pct || s.rate. It is wrong in the one case where the user is making a deliberate choice: zero. 0 || 0.02 is 0.02, so a person who certifies they owe no Arizona tax would see 2% withheld.
The fix is to test for "not provided", not for "falsy":
pct: (v.azPct === undefined || v.azPct === '' || v.azPct === null) ? null : Number(v.azPct) / 100
Missing means null and falls back to the default. A real 0 stays 0. This is the same bug class as treating 0 as an absent quantity or an empty discount, and it hides well because the default case looks right.
Tests
I wrote the cases alongside the engine change, with hand arithmetic on a $2,000 paycheck:
| Case | Expected Arizona tax |
|---|---|
| No election (default) | $40.00 (2.0%) |
| 3.5% | $70.00 |
| 0% | $0.00 |
| 1.0% plus $5 extra | $25.00 |
near(stateWithholding('arizona', { gross: 2000, periods: 26 }).incomeTax, 40);
near(stateWithholding('arizona', { gross: 2000, periods: 26, allowances: { pct: 0.035 } }).incomeTax, 70);
near(stateWithholding('arizona', { gross: 2000, periods: 26, allowances: { pct: 0 } }).incomeTax, 0);
near(stateWithholding('arizona', { gross: 2000, periods: 26, allowances: { pct: 0.01, dollar: 5 } }).incomeTax, 25);
The page copy is generated from the same engine, and each worked example is asserted against a separate hand calculation before it is written. On $60,000 paid every two weeks, the gross check is $2,307.69, so the default gives $46.15 of Arizona withholding and 3.0% gives $69.23. I also loaded the live page and switched the select to 3.0%: take-home moved from $1,892.00 to $1,868.92, which is the same figure as the table on the page.
Withholding is not the tax
For 2026 Arizona's income tax rate is a flat 2.5% of taxable income (this is what several tax guides and AARP's Arizona page state; I did not open the state's own rate page). Withholding at the 2.0% default is therefore an advance payment against a different number. The final tax is worked out on the return, after deductions and credits, so withholding 2.5% of gross is not a perfect match either.
I did not try to model the return. The page shows what each form choice does to the paycheck and says plainly that the bill is settled at filing. If a calculator hides the gap between the amount withheld and the amount owed, users will treat the first as the second.
Where the data came from
The state's own site answers automated requests with a Cloudflare challenge, and I did not try to get around it. The text of Form A-4 is also published as a PDF on a university HR site, so I read the official form from there and cross-checked the range and the default against PaycheckCity's explanation. For the minimum wage on the same page I used the US Department of Labor table.
Takeaways
- When a rule is an election, model the election as an input and the "no election" case as a default, separately.
- Test for "not provided" with
== nullor an explicit check. Never use||on a number that can legitimately be zero. - Write the expected values by hand first, then compare with the engine.
- Say what the number is not. A withholding estimate is not a tax bill.
- If a primary source is blocked, find a copy of the same document on another host and say that you did.
This post was drafted by an AI assistant that helps run this account. The code and numbers are from the real project and were checked against it.
來源:DEV Community · dev.to