goldspec
# Chapter 1.3 — the card is charged exactly once

**Last updated** 2026-10-26 v2

> **As a customer I want my card charged once for my order, however the processor's answers arrive,
> so that a slow network is never a second payment out of my account.**

The drill-down of step `ch1-3` in [chapter 1](chapter-01-ordering.md). A charge whose answer never
came back is the hard case: resending it risks charging twice, and dropping it risks an order that
was paid for and never placed.

```mermaid
flowchart TD
    START([<b>START</b> · a committed order<br/>with its reference]):::terminal

    SEND[<b>ch1-3-1</b> · send the charge,<br/>keyed by the order's reference]:::process
    HEARD{<b>ch1-3-2</b><br/>did an answer<br/>come back in time?}:::question
    ASK[<b>ch1-3-3</b> · ask the processor what<br/>became of that reference]:::process
    OK{<b>ch1-3-4</b><br/>was the charge<br/>approved?}:::question
    DECLINED[/<b>DECLINED</b> · said to the customer,<br/>and nothing was taken/]:::bad
    DONE([<b>END</b> · charged once, under<br/>the order's reference]):::good

    START --> SEND --> HEARD
    HEARD -->|yes| OK
    HEARD -->|no| ASK --> OK
    OK -->|no| DECLINED
    OK -->|yes| DONE

    P1>"<b>ch1-3-P1</b> how long to wait<br/>for an answer"]:::parameter
    HEARD -.tuned by.-> P1

    subgraph LEGEND["legend — shape carries the role"]
        direction LR
        LT([START / END — named in caps, so it reads in greyscale]):::terminal
        LA[action]:::process
        LS[[sub-flow — drills down]]:::subroutine
        LD{question}:::question
        LG([reached]):::good
        LB[/REFUSED — a different shape, so it reads in greyscale/]:::bad
        LP>"parameter — a value to discover"]:::parameter
    end

    classDef terminal fill:#eef0ee,stroke:#52514e,color:#0b0b0b;
    classDef process fill:#e9eef5,stroke:#2a78d6,color:#0b0b0b;
    classDef question fill:#fdf3d8,stroke:#b8860b,color:#0b0b0b;
    classDef subroutine fill:#e9eef5,stroke:#2a78d6,color:#0b0b0b;
    classDef good fill:#e2f2e2,stroke:#0ca30c,color:#0b0b0b;
    classDef bad fill:#f7e4e4,stroke:#b02a2a,color:#0b0b0b;
    classDef parameter fill:#f4f2ee,stroke:#8a6212,color:#0b0b0b,stroke-dasharray:2 2;
```

## Nodes

### ch1-3-1 · send the charge

> **As a customer I want the charge sent under my order's reference, so that whatever happens to it
> afterwards can be traced back to the one order it belongs to.**

#### Information

| Information | IO | Description | Required | Provenance |
|---|---|---|---|---|
| ch1-3-1-inf1 | In | `order` | yes | parent |
| ch1-3-1-inf2 | In | `order.reference` | yes | parent |
| ch1-3-1-inf3 | Out | `charge request` | yes | derived |

#### User Requirements
  * ch1-3-1-ur1 - the amount charged is the order's total, as the customer accepted it

#### System Requirements
  * ch1-3-1-sr1 - the request carries the order's reference as its key, so the processor treats a
    repeat as the same charge

#### Non-Functional Requirements

#### Unknowns


---

### ch1-3-2 · did an answer come back in time?

> **As a customer I want the shop to notice when my charge has gone unanswered, so that silence is
> never read as either a payment or a refusal.**

#### Information

| Information | IO | Description | Required | Provenance |
|---|---|---|---|---|
| ch1-3-2-inf1 | In | `charge request` | yes | ch1-3-1 |
| ch1-3-2-inf2 | Out | `processor answer` | optional | observed |

#### User Requirements
  * ch1-3-2-ur1 - no answer is treated as *unknown*, never as approved or declined

#### System Requirements

#### Non-Functional Requirements
  * ch1-3-2-nfr1 - a lost response and a lost request look the same from here, so the only safe
    next step is to ask rather than to send again

#### Unknowns

**Parameter `ch1-3-P1` — how long to wait for an answer.** The model is available today: the
distribution of the processor's response times bounds it from below, because waiting less than a
slow answer takes asks about charges that are still on their way; the time a customer will watch a
spinner bounds it from above. Working value ten seconds; confirmed by those two bounds.

---

### ch1-3-3 · ask the processor what became of the charge

> **As a customer I want the shop to find out what happened to my charge before doing anything
> else, so that I am not charged a second time for an answer that was merely late.**

#### Information

| Information | IO | Description | Required | Provenance |
|---|---|---|---|---|
| ch1-3-3-inf1 | In | `order.reference` | yes | parent |
| ch1-3-3-inf2 | Out | `processor answer` | yes | observed |

#### User Requirements

#### System Requirements
  * ch1-3-3-sr1 - the lookup is by the order's reference, and it never submits a charge

#### Non-Functional Requirements

#### Unknowns
  * ch1-3-3-u1 - what to do when the processor has no record of the reference either is
    undecided: send once more under the same key, or refuse the order and say so

---

### ch1-3-4 · was the charge approved?

> **As a customer I want to be told plainly when my card is declined, so that I know nothing was
> taken and can pay another way.**

#### Information

| Information | IO | Description | Required | Provenance |
|---|---|---|---|---|
| ch1-3-4-inf1 | In | `processor answer` | yes | ch1-3-2 |
| ch1-3-4-inf2 | Out | `charge` | optional | derived |
| ch1-3-4-inf3 | Out | `decline` | optional | derived |

#### User Requirements
  * ch1-3-4-ur1 - a decline is said to the customer, with the processor's reason where it gave one

#### System Requirements

#### Non-Functional Requirements

#### Unknowns

## Glossary

| Term | Meaning |
|---|---|
| Charge request | What the shop sends the processor. It is not a charge until the processor approves it |
| Processor answer | What the processor says about one reference: approved, declined, or no record of it |
| Decline | The processor's refusal of a charge. Nothing was taken, and the order goes no further |