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.1 (not the latest) · 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
giftAid(£1.00, 2026-09-23)→ donation £1.00, tax reclaimable £0.25, gross £1.25, basic rate basis points 20% £1 at 20% is 25pgiftAid(£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 grossgiftAid(£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.
export function giftAid(donation: Money, onDate: string): GiftAid
| donation | Money | the net amount the donor gave, in GBP |
| onDate | 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
/** A donation, the tax on it the charity reclaims, and the two together. */
export interface GiftAid {
readonly donation: Money;
/** donation x rate / (100% - rate), rounded down to the penny */
readonly taxReclaimable: Money;
/** donation plus taxReclaimable: the grossed up amount */
readonly gross: Money;
/** the UK basic rate for the tax year, 2000 = 20% */
readonly basicRateBasisPoints: number;
}
Your code names it in one line, in the file that uses it
import { giftAid } from "#fune/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 { roundDiv } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { type GiftAid } from "./charity_gift_aid_types.ts";
import { BASIC_RATE, BASIC_RATE_HISTORY } from "./charity_gift_aid_data.ts"; ← this capability’s own data, compiled from data/basic-rate.json into the same file by fune build
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
function basicRate(onDate: string): number {
const row = BASIC_RATE.find((r) => r.validFrom <= onDate && (r.validTo === null || onDate <= r.validTo));
if (row !== undefined) return row.basisPoints;
// 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" && BASIC_RATE.length > 0) {
const earliest = BASIC_RATE.map((r) => r.validFrom).reduce((a, b) => (b < a ? b : a));
if (onDate < earliest) {
throw new RangeError(
`no Gift Aid basic rate on ${onDate}: this build was installed with history=${BASIC_RATE_HISTORY}, so it only carries rules from ${earliest}. Reinstall with history=full for earlier tax years.`
);
}
}
throw new RangeError(`no Gift Aid basic rate on ${onDate}`);
}
/**
* 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.
*/
export function giftAid(donation: Money, onDate: string): GiftAid {
if (donation.currency !== "GBP") {
throw new RangeError(`Gift Aid is claimed in GBP, received ${donation.currency}`);
}
if (donation.minor < 0) {
throw new RangeError(`donation must not be negative, received ${donation.minor}`);
}
if (!ISO_DATE.test(onDate)) {
throw new RangeError(`onDate must be an ISO date (YYYY-MM-DD), received "${onDate}"`);
}
const rate = basicRate(onDate);
// Rounded down: a claim may never exceed the tax treated as deducted.
const tax = roundDiv(donation.minor * rate, 10000 - rate, "down");
return {
donation: money(donation.minor, "GBP"),
taxReclaimable: money(tax, "GBP"),
gross: money(donation.minor + tax, "GBP"),
basicRateBasisPoints: rate,
};
}Install
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the TypeScript 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 TypeScript implementation. Install it without the registry with fune add ./charity.gift-aid-1.0.1-typescript.fune, or fetch it from a terminal with fune pull charity.gift-aid@1.0.1:typescript.
The whole function, every language, is one file too: charity.gift-aid-1.0.1.fune, 17,990 bytes, sha256 e4bafd2aea4db6e66eb48ebb4b6995c03499a576a132368d8f7a866d00b566c9. 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.
Files
| Path | Bytes |
|---|---|
| README.md | 2,685 |
| data/basic-rate.json | 377 |
| impl/python.py | 2,268 |
| impl/rust.rs | 3,133 |
| impl/typescript.ts | 2,249 |
| vectors.json | 3,719 |