charity.payroll-giving
UK Payroll Giving for one pay period: the donation comes off pay before PAYE, and the tax it saves the employee.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 12 tests, run in TypeScript, Python and Rust.
Not professional advice. This capability calculates tax figures from published rules. It is a software component for developers, not tax advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed in its README, and have a payroll specialist review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.
What it does
Payroll Giving (Give As You Earn) for one pay period. The employer deducts the donation from pay **before income tax** (ITEPA 2003 s713) but **after National Insurance**, and passes it to a Payroll Giving agency. The relief is simply the PAYE the lower pay saves, so this runs payroll.income-tax twice, with and without the donation, and reports the difference.
That is deliberately not `donation x marginal rate`. PAYE works on taxable pay rounded down to whole pounds, so the saving each period comes in whole multiples of the rate on £1: a £5 donation at basic rate saves exactly £1, a 50p donation saves nothing that month, and a donation that straddles a band boundary gets each rate on its own part. The PAYE is the real deduction the payroll will make, cumulative codes, Scottish and Welsh bands, K codes and all.
For example
payroll_giving(1257L, monthly, 1, £3,000.00, £100.00, £0.00, £0.00, 2026-04-25)→ taxable pay £2,900.00, tax without giving £390.20, tax with giving £370.20, tax saved £20.00, net cost £80.00 basic rate: £100 given from £3,000 saves £20 of PAYE, costing £80payroll_giving(1257L, monthly, 1, £3,000.00, £5.00, £0.00, £0.00, 2026-04-25)→ taxable pay £2,995.00, tax without giving £390.20, tax with giving £389.20, tax saved £1.00, net cost £4.00 £5 given saves exactly £1payroll_giving(1257L, monthly, 1, £3,000.00, £0.50, £0.00, £0.00, 2026-04-25)→ taxable pay £2,999.50, tax without giving £390.20, tax with giving £390.20, tax saved £0.00, net cost £0.50 50p given saves nothing: PAYE rounds taxable pay down to whole pounds (naive 20% says 10p)
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
def payroll_giving(tax_code: str, frequency: PayFrequency, period: int, pay: Money, donation: Money, previous_pay_to_date: Money, previous_tax_to_date: Money, pay_date: str) -> PayrollGiving
| tax_code | string | as issued by HMRC, e.g. 1257L or S1257L |
| frequency | PayFrequency | |
| period | int | tax week or month of the payment, from payroll.tax-period |
| pay | Money | taxable pay for the period before the donation, after any net pay pension deduction |
| donation | Money | the Payroll Giving deduction this period |
| previous_pay_to_date | Money | taxable pay already paid this tax year, after earlier Payroll Giving deductions |
| previous_tax_to_date | Money | tax already deducted this tax year |
| pay_date | date | decides the tax year's bands |
| returns | PayrollGiving |
The type it declares, generated into your project
@dataclass(frozen=True)
class PayrollGiving:
"""The period's PAYE with and without the donation."""
#: pay less the donation: what PAYE is worked out on
taxable_pay: Money
#: PAYE this period had there been no donation
tax_without_giving: Money
#: PAYE this period with the donation deducted first
tax_with_giving: Money
#: the relief: taxWithoutGiving less taxWithGiving
tax_saved: Money
#: donation less taxSaved: what the gift costs the employee
net_cost: Money
Your code names it in one line, in the file that uses it
from fune.charity.payroll_giving import payroll_giving # charity.payroll-giving@^1
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from .charity_payroll_giving_types import PayrollGiving
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
from .payroll_income_tax import income_tax ← from payroll.income-tax ^1.0.0 · built alongside by fune
from .payroll_tax_period_types import PayFrequency
def payroll_giving(
tax_code: str,
frequency: PayFrequency,
period: int,
pay: Money,
donation: Money,
previous_pay_to_date: Money,
previous_tax_to_date: Money,
pay_date: str,
) -> PayrollGiving:
"""Payroll Giving (ITEPA 2003 s713): the donation is deducted from pay
before PAYE is worked out, so the relief is whatever PAYE the lower pay
saves, by the same tax table routines the payroll uses. PAYE rounds
taxable pay down to whole pounds, so a 50p donation can save nothing.
"""
if donation.currency != "GBP":
raise ValueError("payroll amounts must be in GBP, received %s for donation" % (donation.currency,))
if donation.minor < 0:
raise ValueError("donation must not be negative, received %d" % (donation.minor,))
if donation.minor > pay.minor:
raise ValueError("donation of %d is more than pay of %d" % (donation.minor, pay.minor))
taxable_pay = money(pay.minor - donation.minor, "GBP")
without = income_tax(tax_code, frequency, period, pay, previous_pay_to_date, previous_tax_to_date, pay_date).tax.minor
with_giving = income_tax(
tax_code, frequency, period, taxable_pay, previous_pay_to_date, previous_tax_to_date, pay_date
).tax.minor
return PayrollGiving(
taxable_pay=taxable_pay,
tax_without_giving=money(without, "GBP"),
tax_with_giving=money(with_giving, "GBP"),
tax_saved=money(without - with_giving, "GBP"),
net_cost=money(donation.minor - (without - with_giving), "GBP"),
)Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 3 dependencies, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:
fune add charity.payroll-giving
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./charity.payroll-giving-1.0.0-python.fune, or fetch it from a terminal with fune pull charity.payroll-giving@1.0.0:python.
The whole function, every language, is one file too: charity.payroll-giving-1.0.0.fune, 17,773 bytes, sha256 6b0c4d385fdbed84474164b57fe3c3f23e9d39a2757335f3e7ea1641e055b2ce. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
# fune: before charity.payroll-giving
after — your function gets the result and the arguments, and returns the final result.
# fune: after charity.payroll-giving
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
# fune: replace money.amount in charity.payroll-giving
# fune: replace payroll.income-tax in charity.payroll-giving
# fune: replace payroll.tax-period in charity.payroll-giving
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show charity.payroll-giving --steps.
# fune: step charity.payroll-giving after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| basic rate: £100 given from £3,000 saves £20 of PAYE, costing £80 | 1257L, monthly, 1, £3,000.00, £100.00, £0.00, £0.00, 2026-04-25 | → | taxable pay £2,900.00, tax without giving £390.20, tax with giving £370.20, tax saved £20.00, net cost £80.00 |
| £5 given saves exactly £1 | 1257L, monthly, 1, £3,000.00, £5.00, £0.00, £0.00, 2026-04-25 | → | taxable pay £2,995.00, tax without giving £390.20, tax with giving £389.20, tax saved £1.00, net cost £4.00 |
| 50p given saves nothing: PAYE rounds taxable pay down to whole pounds (naive 20% says 10p) | 1257L, monthly, 1, £3,000.00, £0.50, £0.00, £0.00, 2026-04-25 | → | taxable pay £2,999.50, tax without giving £390.20, tax with giving £390.20, tax saved £0.00, net cost £0.50 |
| higher rate: £200 given from £5,000 saves £80 | 1257L, monthly, 1, £5,000.00, £200.00, £0.00, £0.00, 2026-04-25 | → | taxable pay £4,800.00, tax without giving £952.06, tax with giving £872.06, tax saved £80.00, net cost £120.00 |
| Scottish intermediate rate: £100 saves £21 | S1257L, monthly, 1, £3,000.00, £100.00, £0.00, £0.00, 2026-04-25 | → | taxable pay £2,900.00, tax without giving £392.27, tax with giving £371.27, tax saved £21.00, net cost £79.00 |
| cumulative month 6: £100 saves £20 against tax to date | 1257L, monthly, 6, £3,000.00, £100.00, £15,000.00, £1,951.60, 2026-09-25 | → | taxable pay £2,900.00, tax without giving £390.40, tax with giving £370.40, tax saved £20.00, net cost £80.00 |
| no donation: nothing saved | 1257L, monthly, 1, £3,000.00, £0.00, £0.00, £0.00, 2026-04-25 | → | taxable pay £3,000.00, tax without giving £390.20, tax with giving £390.20, tax saved £0.00, net cost £0.00 |
| pay under the allowance: the donation saves no tax at all | 1257L, monthly, 1, £1,000.00, £1,000.00, £0.00, £0.00, 2026-04-25 | → | taxable pay £0.00, tax without giving £0.00, tax with giving £0.00, tax saved £0.00, net cost £1,000.00 |
| a donation above pay is an error | 1257L, monthly, 1, £100.00, £100.01, £0.00, £0.00, 2026-04-25 | → | error: donation of 10001 is more than pay of 10000 |
| a negative donation is an error | 1257L, monthly, 1, £100.00, -£0.01, £0.00, £0.00, 2026-04-25 | → | error: donation must not be negative |
Show the other 2 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| a euro donation is an error | 1257L, monthly, 1, £100.00, €1.00, £0.00, £0.00, 2026-04-25 | → | error: payroll amounts must be in GBP, received EUR for donation |
| a bad period is refused by payroll.income-tax | 1257L, monthly, 13, £3,000.00, £1.00, £0.00, £0.00, 2026-04-25 | → | error: period 13 is not a tax period for monthly pay |
More from the author
- `pay` is the period's taxable pay before the donation (after any net pay pension deduction); `previousPayToDate` should already be net of earlier Payroll Giving deductions, as it is on the payroll record. - National Insurance is not affected by the donation and is not computed here (payroll.national-insurance on the full pay). - A charity cannot claim Gift Aid on Payroll Giving donations (s416(4) Condition C excludes them), and agency fees are the caller's to handle. - The tax years are those payroll.income-tax has bands for (2023-24 on).
## Sources
- Income Tax (Earnings and Pensions) Act 2003 s713, https://www.legislation.gov.uk/ukpga/2003/1/section/713 - GOV.UK, "Payroll Giving" ("taken from your employees' pay before tax but after National Insurance"; "You can't claim Gift Aid on Payroll Giving donations"), https://www.gov.uk/payroll-giving - Income Tax Act 2007 s416(4), https://www.legislation.gov.uk/ukpga/2007/3/section/416
Files
| Path | Bytes |
|---|---|
| README.md | 1,812 |
| impl/python.py | 1,740 |
| impl/rust.rs | 2,674 |
| impl/typescript.ts | 1,792 |
| vectors.json | 5,382 |