charity.gift-aid
Gift Aid a UK charity can claim on a donation: basic rate tax grossed up, 25p per £1 at 20%.
1.0.2 · published 2026-10-03 by charlie · Anterra
Pinned by 15 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 charity tax specialist review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.
What it does
The Gift Aid a UK charity (or CASC) can reclaim from HMRC on one donation, or on a total of donations made in the same tax year.
A Gift Aid donation is treated as given after the donor has paid basic rate tax on it (Income Tax Act 2007 s414(2)(a)), so the charity reclaims the tax on the grossed up amount: `donation x rate / (100% - rate)`. At 20% that is 25p for every £1, and a £100 donation is worth £125 to the charity.
For example
gift_aid(£1.00, 2026-09-23)→ donation £1.00, tax reclaimable £0.25, gross £1.25, basic rate basis points 20% £1 at 20% is 25pgift_aid(£100.00, 2025-01-10)→ donation £100.00, tax reclaimable £25.00, gross £125.00, basic rate basis points 20% £100 is £25 of Gift Aid, £125 grossgift_aid(£10.01, 2026-04-06)→ donation £10.01, tax reclaimable £2.50, gross £12.51, basic rate basis points 20% £10.01 gives 250.25p, rounded down to 250p, not up
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 gift_aid(donation: Money, on_date: str) -> GiftAid
| donation | Money | the net amount the donor gave, in GBP |
| on_date | date | the date of the gift; decides the tax year's basic rate |
| returns | GiftAid | the reclaimable tax, rounded down to the penny, and the gross |
The type it declares, generated into your project
@dataclass(frozen=True)
class GiftAid:
"""A donation, the tax on it the charity reclaims, and the two together."""
donation: Money
#: donation x rate / (100% - rate), rounded down to the penny
tax_reclaimable: Money
#: donation plus taxReclaimable: the grossed up amount
gross: Money
#: the UK basic rate for the tax year, 2000 = 20%
basic_rate_basis_points: int
Your code names it in one line, in the file that uses it
from fune.charity.gift_aid import gift_aid # charity.gift-aid@^1
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import re
from .charity_gift_aid_data import BASIC_RATE, BASIC_RATE_HISTORY ← this capability’s own data, compiled from data/basic-rate.json into the same file by fune build
from .charity_gift_aid_types import GiftAid
from .math_round_div import round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
ISO_DATE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}")
def _basic_rate(on_date: str) -> int:
for row in BASIC_RATE:
if row.valid_from <= on_date and (row.valid_to is None or on_date <= row.valid_to):
return row.basis_points
# A history=current build carries only the rate in force; answering an old
# gift with today's rate would be a quiet wrong claim, so say why instead.
if BASIC_RATE_HISTORY != "full" and len(BASIC_RATE) > 0:
earliest = min(row.valid_from for row in BASIC_RATE)
if on_date < earliest:
raise ValueError(
"no Gift Aid basic rate on %s: this build was installed with history=%s, so it only carries rules from %s. "
"Reinstall with history=full for earlier tax years." % (on_date, BASIC_RATE_HISTORY, earliest)
)
raise ValueError("no Gift Aid basic rate on %s" % (on_date,))
def gift_aid(donation: Money, on_date: str) -> GiftAid:
"""The tax a charity reclaims on a Gift Aid donation. The donor is treated
as having given the gift after deducting basic rate tax (ITA 2007 s414), so
the charity reclaims donation x rate / (100% - rate): 25p per £1 at 20%.
It is always the UK basic rate, for Scottish and Welsh taxpayers too.
"""
if donation.currency != "GBP":
raise ValueError("Gift Aid is claimed in GBP, received %s" % (donation.currency,))
if donation.minor < 0:
raise ValueError("donation must not be negative, received %d" % (donation.minor,))
if not isinstance(on_date, str) or not ISO_DATE.fullmatch(on_date):
raise ValueError('onDate must be an ISO date (YYYY-MM-DD), received "%s"' % (on_date,))
rate = _basic_rate(on_date)
# Rounded down: a claim may never exceed the tax treated as deducted.
tax = round_div(donation.minor * rate, 10000 - rate, "down")
return GiftAid(
donation=money(donation.minor, "GBP"),
tax_reclaimable=money(tax, "GBP"),
gross=money(donation.minor + tax, "GBP"),
basic_rate_basis_points=rate,
)Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 2 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.gift-aid
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./charity.gift-aid-1.0.2-python.fune, or fetch it from a terminal with fune pull charity.gift-aid@1.0.2:python.
The whole function, every language, is one file too: charity.gift-aid-1.0.2.fune, 18,560 bytes, sha256 09899aa21a9b970e077beb12a42deb66e9f6710f0beffa59f313fac439e4c65e. 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.gift-aid
after — your function gets the result and the arguments, and returns the final result.
# fune: after charity.gift-aid
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 math.round-div in charity.gift-aid
# fune: replace money.amount in charity.gift-aid
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.gift-aid --steps.
# fune: step charity.gift-aid 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 | |
|---|---|---|---|
| £1 at 20% is 25p | £1.00, 2026-09-23 | → | donation £1.00, tax reclaimable £0.25, gross £1.25, basic rate basis points 20% |
| £100 is £25 of Gift Aid, £125 gross | £100.00, 2025-01-10 | → | donation £100.00, tax reclaimable £25.00, gross £125.00, basic rate basis points 20% |
| £10.01 gives 250.25p, rounded down to 250p, not up | £10.01, 2026-04-06 | → | donation £10.01, tax reclaimable £2.50, gross £12.51, basic rate basis points 20% |
| 3p gives 0.75p, rounded down to 0 | £0.03, 2026-04-06 | → | donation £0.03, tax reclaimable £0.00, gross £0.03, basic rate basis points 20% |
| a zero donation claims nothing | £0.00, 2026-04-06 | → | donation £0.00, tax reclaimable £0.00, gross £0.00, basic rate basis points 20% |
| 2007-08 at 22%: £10 is 22/78, 282.05p rounded down to 282p (not 25%) | £10.00, 2008-04-05 | → | donation £10.00, tax reclaimable £2.82, gross £12.82, basic rate basis points 22% |
| 6 April 2008 is the first day of the 20% basic rate | £10.00, 2008-04-06 | → | donation £10.00, tax reclaimable £2.50, gross £12.50, basic rate basis points 20% |
| £78 at 22% is exactly £22 | £78.00, 2007-04-06 | → | donation £78.00, tax reclaimable £22.00, gross £100.00, basic rate basis points 22% |
| a large donation: £1,000,000 is £250,000 | £1,000,000.00, 2026-04-06 | → | donation £1,000,000.00, tax reclaimable £250,000.00, gross £1,250,000.00, basic rate basis points 20% |
| a date before the rates on file is an error | £10.00, 2007-04-05 | → | error: no Gift Aid basic rate on 2007-04-05 |
Show the other 5 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| a euro donation is an error | €10.00, 2026-04-06 | → | error: Gift Aid is claimed in GBP, received EUR |
| a negative donation is an error | -£1.00, 2026-04-06 | → | error: donation must not be negative |
| a malformed date is an error | £1.00, 6/4/2026 | → | error: onDate must be an ISO date |
| a trailing newline after onDate is not an ISO date | £1.00, 2026-09-16 | → | error: onDate must be an ISO date |
| Arabic-Indic digits in onDate are not an ISO date | £1.00, ٢٠٢٦-09-16 | → | error: onDate must be an ISO date |
More from the author
## Decisions
- **Always the UK basic rate.** Since 6 April 2018 s414 and s415 refer to "the basic rate", not the Scottish or Welsh basic rate, so a Scottish taxpayer's gift is grossed up at the same 20% as anyone else's. Any difference between a devolved basic rate and the UK one is settled with the donor through s414A, never through the charity's claim. - **Rounded down to the penny.** The tax treated as deducted is exact; a claim may not exceed it, so a fraction of a penny is dropped. £10.01 gives 250.25p, claimed as 250p. Claim on a total rather than summing per-gift results if you want the pennies back (charity.reclaim-batch does). - **The date is the date of the gift**, which decides the tax year and so the basic rate: 22% in 2007-08 (22/78, about 28.2p per £1), 20% from 6 April 2008. - It does not check eligibility (declaration, benefits): see charity.gift-aid-eligibility. - The transitional relief of 3p per £1 paid to charities for 2008-09 to 2010-11 (Finance Act 2008 s53) was a separate government payment, not Gift Aid, and is not included.
The rates start at 6 April 2007, when ITA 2007 took over the rules; an earlier date is an error rather than a guess. A build installed with `history=current` refuses dates before the rate it carries and says why.
## Sources
- Income Tax Act 2007 ss414-415, https://www.legislation.gov.uk/ukpga/2007/3/part/8/chapter/2 (s414(2)(a) "income tax at the basic rate"; s415 grossed up "by reference to the basic rate for the tax year in which the gift is made"). - Finance Act 2007 s1 (basic rate 22% for 2007-08), https://www.legislation.gov.uk/ukpga/2007/11/section/1 - Finance Act 2008 s1 (basic rate 20% for 2008-09), https://www.legislation.gov.uk/ukpga/2008/9/section/1 - GOV.UK, "Income Tax rates and Personal Allowances" (basic rate 20% for 2026-27), https://www.gov.uk/income-tax-rates - GOV.UK, "Claim top-up payments for the Gift Aid Small Donations Scheme" ("If the basic rate of Income Tax is 20% ... a donor gives £30, you can claim ... £7.50"), https://www.gov.uk/guidance/claiming-a-top-up-payment-on-small-charitable-donations
1.0.1 fixes Python accepting a trailing newline or non-ASCII digits in onDate; adds tests.
## Notices
Contains public sector information licensed under the Open Government Licence v3.0 (https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).
Legislation: Crown copyright and database right.
1.0.2 adds its attribution notices (NOTICE). The code and the tests are unchanged.
Files
| Path | Bytes |
|---|---|
| NOTICE | 229 |
| README.md | 2,993 |
| data/basic-rate.json | 377 |
| impl/python.py | 2,268 |
| impl/rust.rs | 3,133 |
| impl/typescript.ts | 2,249 |
| vectors.json | 3,719 |