Charge Processor
Recording a charge and minting an id is the easy part the model builds for free. The hidden test is what happens when a client retries: a vague spec mints a fresh charge for every call, so a retried request the client believed had failed becomes a second, real charge on the same card.
You are building the charge processor at the core of a checkout backend. It is created with no arguments and exposes a single method, charge(ref, amountCents). ref is a string the caller hands in with each charge attempt, and amountCents is the integer amount in cents. The method processes the charge and returns an object with a chargeId, a unique string identifying the charge it created, and the amountCents that was charged.
The catch is the real world this processor lives in. Networks drop responses. A client fires a charge, the charge succeeds on your side, but the response never reaches the client, so the client does the only safe thing it knows: it retries, re-sending the exact same request, ref and all. Real payment clients are built to retry this way, and a busy checkout will see the same ref arrive more than once for a single intended purchase. Your processor decides what that second arrival means.
Specify the processor's behavior completely, and think hardest about that repeat. When a ref the processor has already handled arrives again, decide exactly what charge() must return and what it must do to the charge it created the first time, and write that rule as something a deterministic test could check. A processor that treats every call as a brand new charge will turn one customer's single purchase into two real charges the moment their client retries.
charge('order-9f3', 4200) returns { chargeId: 'ch_001', amountCents: 4200 }. The interesting case is the retry: the client's network dropped the response, so it sends charge('order-9f3', 4200) again. A processor that mints a fresh charge per call returns a new chargeId here, leaving two charges on the books for one purchase.
- charge(ref, amountCents) returning { chargeId, amountCents }; chargeId is a unique non-empty string the build generates.
- A first-seen ref is charged for the amountCents it was given.
- ref is a string the caller passes in with the charge; amountCents is an integer number of cents.
The functional tests are shown, and the model usually clears them on its own. The hidden tests are the twists this kind of system is full of. They are not listed. Your spec only passes them if it already knows where this domain breaks.
201 chars