Functional Weave
Code in Rust

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 £80
  • payroll_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 £1
  • payroll_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.

pub fn payroll_giving(tax_code: &str, frequency: &str, period: i64, pay: &Money, donation: &Money, previous_pay_to_date: &Money, previous_tax_to_date: &Money, pay_date: &str) -> PayrollGiving
tax_codestringas issued by HMRC, e.g. 1257L or S1257L
frequencyPayFrequency
periodinttax week or month of the payment, from payroll.tax-period
payMoneytaxable pay for the period before the donation, after any net pay pension deduction
donationMoneythe Payroll Giving deduction this period
previous_pay_to_dateMoneytaxable pay already paid this tax year, after earlier Payroll Giving deductions
previous_tax_to_dateMoneytax already deducted this tax year
pay_datedatedecides the tax year's bands
returnsPayrollGiving

The type it declares, generated into your project

/// The period's PAYE with and without the donation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PayrollGiving {
    /// pay less the donation: what PAYE is worked out on
    pub taxable_pay: Money,
    /// PAYE this period had there been no donation
    pub tax_without_giving: Money,
    /// PAYE this period with the donation deducted first
    pub tax_with_giving: Money,
    /// the relief: taxWithoutGiving less taxWithGiving
    pub tax_saved: Money,
    /// donation less taxSaved: what the gift costs the employee
    pub net_cost: Money,
}

Your code names it in one line, in the file that uses it

fune!(charity.payroll-giving@^1);  // then call payroll_giving(…)
impl/rust.rs · 69 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.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::money_amount::{money, money_from_value, money_to_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune
use super::payroll_income_tax::income_tax;  ← from payroll.income-tax ^1.0.0 · built alongside by fune

/// 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.
///
/// # Panics
/// Panics on a non-GBP or negative donation, a donation above pay, and
/// anything payroll.income-tax refuses.
pub fn payroll_giving(
    tax_code: &str,
    frequency: &str,
    period: i64,
    pay: &Money,
    donation: &Money,
    previous_pay_to_date: &Money,
    previous_tax_to_date: &Money,
    pay_date: &str,
) -> PayrollGiving {
    if donation.currency != "GBP" {
        panic!("payroll amounts must be in GBP, received {} for donation", donation.currency);
    }
    if donation.minor < 0 {
        panic!("donation must not be negative, received {}", donation.minor);
    }
    if donation.minor > pay.minor {
        panic!("donation of {} is more than pay of {}", donation.minor, pay.minor);
    }
    let taxable_pay = money(pay.minor - donation.minor, "GBP");
    let without = income_tax(tax_code, frequency, period, pay, previous_pay_to_date, previous_tax_to_date, pay_date).tax.minor;
    let with_giving =
        income_tax(tax_code, frequency, period, &taxable_pay, previous_pay_to_date, previous_tax_to_date, pay_date).tax.minor;
    PayrollGiving {
        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"),
    }
}

pub fn payroll_giving_to_value(g: &PayrollGiving) -> Value {
    Value::obj(vec![
        ("taxablePay", money_to_value(&g.taxable_pay)),
        ("taxWithoutGiving", money_to_value(&g.tax_without_giving)),
        ("taxWithGiving", money_to_value(&g.tax_with_giving)),
        ("taxSaved", money_to_value(&g.tax_saved)),
        ("netCost", money_to_value(&g.net_cost)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    if let Value::Float(_) = args[2] {
        panic!("period {} is not a tax period for {} pay", args[2].as_f64(), args[1].as_str());
    }
    payroll_giving_to_value(&payroll_giving(
        args[0].as_str(),
        args[1].as_str(),
        args[2].as_i64(),
        &money_from_value(&args[3]),
        &money_from_value(&args[4]),
        &money_from_value(&args[5]),
        &money_from_value(&args[6]),
        args[7].as_str(),
    ))
}

Install

fune build

With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 3 dependencies, pins them in fune.lock, downloads only the Rust 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. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:

fune add charity.payroll-giving
Download for Rust charity.payroll-giving-1.0.0-rust.fune · 14,084 bytes sha256 708a72a75826c4088b4dc942b55421c463f7fb57686a904429ac4de53dc55f07

The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./charity.payroll-giving-1.0.0-rust.fune, or fetch it from a terminal with fune pull charity.payroll-giving@1.0.0:rust.

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.

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

PathBytes
README.md1,812
impl/python.py1,740
impl/rust.rs2,674
impl/typescript.ts1,792
vectors.json5,382