Part 5 of 7 · Refund handler series ~5 min read

How a refund gets approved

A card lands in Sam’s Slack at 9:14am. A customer wants $89 back on a faulty blender, the policy covers it, and there’s a drafted reply with an Approve button. What happens when Sam taps it? And just as important — what happens when Sam doesn’t agree with the draft, or decides the answer should be no? This post walks through the three things a person can do on a card — approve, edit, decline — and how the reply, the money, and the audit trail all stay in sync. The one rule underneath all of it: the money only moves after a human tap.

Key takeaways

  • Three actions per card: approve (send the draft as-is), edit (change it first), decline (say no, with a reason).
  • The reply goes out via SES; nothing is sent before a person taps.
  • The refund itself is never issued by the system — approve records the decision for a person to pay out.
  • Every action writes an audit row: who, when, the decision, the cited policy line, the amount.
  • The Approve button is a Slack interactive message backed by a Function URL.

Three actions on the card

Three actions on the approval card A diagram showing one input on the left flowing through a small interactive Slack card, then branching into three action paths. Far left: an "Approval card in Slack" box showing a typical card — customer, item, amount, cited policy line, and the drafted reply — with three button placeholders below: Approve, Edit, Decline. The approver taps one button. The middle column shows the three branches. Branch one, Approve: a Function URL Lambda sends the drafted reply to the customer via SES, marks the request resolved, and records the decision so a person can pay out the refund through the usual channel; the system itself never moves money. Branch two, Edit: opens the draft in a box; the approver changes the wording or the amount, then sends; the edited reply goes out via SES and the changes are recorded against the original draft. Branch three, Decline: the approver says no — either overriding an in-policy draft or confirming an out-of-policy one; a short reason is required, a kind decline goes out, and the request is closed as declined. The right side shows the convergence: every action writes a row to the rf-audit DynamoDB table with timestamp, request id, decision, by-user, the policy line cited, and the amount. A note at the bottom: the money only moves after a human tap — approve records the decision, a person pays it out. Approval card item, amount, line, draft [Approve] [Edit] [Decline] Action 1 Approve • Reply sent via SES as-is • Request marked resolved • Decision recorded; a person pays it out Action 2 Edit • Box opens with the draft • Change wording or amount • Edited reply sent; changes recorded Action 3 Decline • Approver says no with a short reason • Kind decline sent; closed as declined Audit trail DynamoDB rf-audit timestamp · request_id decision · by-user line · amount The money only moves after a human tap — approve records the decision, a person pays it out.
Fig 5. Three actions per card, three different effects. Approve sends the draft as-is and records the decision. Edit lets the approver change it first. Decline says no with a reason. Every action writes to the audit trail, and the system never moves money on its own.

Action 1: approve (the most common)

Sam reads the card, agrees, and taps Approve. The tap goes to a Function URL Lambda — approve-handler — which does three things in order. First, it sends the drafted reply to the customer via SES, exactly as written. Second, it marks the request resolved in DynamoDB and posts the decision where your finance step expects it — a row that says “refund $89 on order #4821, approved by Sam.” Third, it writes an action: approved row to rf-audit with the user, the timestamp, the cited policy line, and the amount.

One thing it does not do is move money. The system records that a refund was approved; a person (or your existing payments step, with its own controls) issues the actual refund. That separation is deliberate — the handler is allowed to draft, send replies, and record decisions, but issuing money is a step that always stays in human hands. A bug in the handler can, at worst, send a too-kind email; it can never empty the till.

Action 2: edit (the small fix)

Sometimes the draft is 90% right. The amount should be a partial, not a full. The customer mentioned a detail the reply should acknowledge. The tone needs a touch more warmth for a regular. Sam taps Edit, and a box opens with the draft already in it. Sam changes what needs changing — including the amount, if the refund should differ from what was asked — and sends.

The edited reply goes out via SES, and the change is recorded against the original draft in rf-audit: what the draft said, what Sam changed it to, and the new amount. Recording the edit matters — it’s how you later see whether the drafts are usually good (rarely edited) or whether the policy doc needs a tune-up (often edited the same way). A pattern of identical edits is a signal that a rule should be rewritten, not re-edited every time.

Action 3: decline (the “no”)

Sometimes the answer is no — either because the checker already marked it out-of-policy and Sam is confirming, or because Sam is overriding an in-policy draft for a reason the policy doesn’t capture (a customer who’s clearly gaming the window, say). Decline asks for a short reason, sends the kind decline reply, and closes the request as declined.

The required reason isn’t bureaucracy — it’s the audit trail’s memory. A declined refund is exactly the kind of decision a customer might push back on, and “declined by Sam on May 19, reason: outside 30-day window, item used” is what lets anyone later see it was a fair call. An override of an in-policy draft is logged a little more loudly — it shows up in the weekly digest — because a manager probably wants to know when the system said yes and a human said no.

Every action is logged, every refund is traceable

The rf-audit table records every approve, edit, and decline with the user, the timestamp, the cited policy line, the amount, and a snapshot of the draft before and after any edit. Months later, when someone asks “why did we refund this?” or “why didn’t we?”, the answer is one lookup: the request, the policy line that applied, the person who decided, and what they sent. There’s no “I think Sam handled that one” — the record is exact.

This kind of traceability is what makes the speed safe. The handler can make refunds feel instant for the easy cases without anyone losing sight of why each one went out. Fast and accountable aren’t a trade-off here; the audit log is what lets you have both.

Next post: the cost breakdown. The whole pipeline above runs in coffee-money territory at SMB volume; Part 6 explains exactly where the dollars go and why the heavy model barely shows up on the bill.

All posts