Functional Weave
Code in TypeScript

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

  • 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 25p
  • giftAid(£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 gross
  • giftAid(£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
donationMoneythe net amount the donor gave, in GBP
onDatedatethe date of the gift; decides the tax year's basic rate
returnsGiftAidthe 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";
impl/typescript.ts · 49 lines · open · raw

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
Download for TypeScript charity.gift-aid-1.0.2-typescript.fune · 12,942 bytes sha256 f02c31c3cf42b18c5f7a0a0d684b4956d82256e1b82c6e091183ac2461fd1425

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./charity.gift-aid-1.0.2-typescript.fune, or fetch it from a terminal with fune pull charity.gift-aid@1.0.2:typescript.

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.

CaseArgumentsExpected
£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
CaseArgumentsExpected
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

PathBytes
NOTICE229
README.md2,993
data/basic-rate.json377
impl/python.py2,268
impl/rust.rs3,133
impl/typescript.ts2,249
vectors.json3,719