← Files AkinatorARCHIVED FILE

evals/results/workspaces/2026-08-26-03-business-void/docs/standards/refunds.md

4.67 KB · Oct 5, 2026 · 18:32 UTC

↓ Download file

# Refunds

## Status

**Not decided, not implemented.** Audited 2026-08-26: nothing in this repository
issues, records or reacts to a money refund. There is no payment provider
integration, no dependency on one, no key or webhook, no subscription record and
no stored charge. `src/` contains one module, `quota.py`.

This file exists so the next agent asked to "implement refunds" finds the open
decisions instead of inventing them. Refunds are money and entitlement: guessing
here is prohibited, not merely discouraged.

## Careful: "refund" already means something else here

`src/quota.py` has `refund_failed`, and [quotas](quotas.md) says "a failed export
refunds its unit automatically". That is a **quota-unit** refund - it returns one
export credit and touches no money. A money refund is a different thing entirely.
Anyone adding money refunds must not extend `refund_failed` or reuse the bare
word `refund` in `src/quota.py`, or the two meanings will be conflated in the one
module where the mistake is most expensive.

## Decisions made

None.

When one is made, record it here with its absolute date and who decided, the same
way [quotas](quotas.md) records the plan table.

## Decisions OPEN

Every row blocks code. None can be answered from this repository.

| Date raised | Question | What it blocks |
|---|---|---|
| 2026-08-26 | How much comes back - the full period, or a proration for the unused days? Measured from the request date or the anniversary? | The amount passed to the provider. There is no correct default; both are common. |
| 2026-08-26 | Who is eligible - any team on `team` plan, self-serve or support-approved, how far into the period, how many times per team per year? `free` is never charged, so it has no refund path. | Whether there is a customer-facing endpoint at all, and what rejects a request. |
| 2026-08-26 | **What happens to quota and entitlement after a refund?** Does the team keep the `team` plan until the anniversary, drop to `free` limits immediately, or keep the plan but lose remaining exports? | `consume` in `src/quota.py`. See "The quota consequence" below - this one has a trap. |
| 2026-08-26 | Does a refund also cancel the subscription, or only return money for the period already paid? | Whether refunding is a billing operation or a lifecycle one. |
| 2026-08-26 | Which payment provider, and where does its configuration live - in this repo or in the vendor's dashboard? | Every line of the integration. Nothing in the tree names a provider. If the answer is "the dashboard", that fact gets written here, because a rule that lives only in a vendor dashboard is invisible to the next agent. |
| 2026-08-26 | Provider refunds settle asynchronously and can fail. Does entitlement change when the refund is requested or when it settles? Is a second request for the same period rejected? | Whether a pending state and an idempotency key are needed. Getting this wrong refunds a period twice. |
| 2026-08-26 | What does the customer see, and what does finance need recorded? | Whether a refund record is persisted at all, and with which fields. |

### The quota consequence has a trap

If a refunded team drops to `free`, its `exports_used` can already exceed the
`free` limit of 3 - a team that used 20 exports on `team` and is then refunded.
`consume` in `src/quota.py` compares `exports_used + count > limit`, so that team
is simply blocked until its anniversary, with no way to reduce the count.
[quotas](quotas.md) records the *upgrade* direction ("used count carries over")
but says nothing about the downgrade direction. Whichever answer is chosen for
the entitlement question above, this case needs an explicit decision, because the
current code answers it silently.

## Implemented in

Nothing. When this ships, this section names the module and functions, the way
[quotas](quotas.md) does.

## Once the questions are answered, the work is

- The business rule and its numbers, written here first, before code.
- The provider call and the refund record, in `src/` under the repo's naming
  convention (see [naming](naming.md) - singular module noun, `snake_case`).
- The entitlement change in `src/quota.py`, named so it cannot be confused with
  `refund_failed`.
- A playbook in `ops/playbooks/` if a refund ever needs to be issued by hand.
- Note for whoever deploys it: adding a payment provider is a **dependency**
  change, so [restart the api](../../ops/playbooks/restart-the-api.md) is not
  enough - that playbook says a restart silently serves the old image.

## What would make this file stale

Any answer to a row in "Decisions OPEN". Move it into "Decisions made" with its
date and decider on the day it is answered; a decision that stays in the OPEN
table after it has been made is worse than no table.

SHA-256: 89e3d286edf4ff98a1e1020b7bc438d6b1d8b9a7ccb1ae51046cd3d50e26aa02