The first line of a billing operations runbook checklist is a name: who is allowed to issue a refund in the payment provider’s dashboard, and under which role. After it come 5 procedures, each walked through once in test mode: a full refund, a partial refund, complimentary access, a dispute response, and a chargeback you lose.

The billing operations runbook checklist: who may move money, and five procedures

A billing operations runbook checklist for a small SaaS has 9 lines: who can sign in to the payment provider and with which role, who may refund and up to what amount, five procedures in one fixed format, where each action is recorded, and the date every procedure was last walked through in test mode.

The runbook is one piece of SaaS billing process best practices, the part you open when a billing question turns urgent: a customer wants money back, a partner was promised a free year, or an email says a payment was disputed.

  • Everyone who can sign in to the payment provider, each with a named role and a second factor.
  • Who may refund, up to what amount, and who approves anything above it.
  • The full refund procedure.
  • The partial refund procedure.
  • The complimentary access procedure.
  • The dispute response procedure, with the person who owns its deadline.
  • The lost-chargeback procedure.
  • Where every action is recorded: the team’s own log, because the provider’s activity log covers access changes, not refunds.
  • The date each procedure was last walked through in test mode, and who walked it.

Every procedure uses one fixed shape, which is my format: the trigger, who may run it, the steps in the provider, what the app’s own records must show afterwards, the message to the customer, and where the record goes. The table holds the short form for Stripe; the provider role column comes from Stripe’s team roles page, and the last column is the part provider documentation does not carry.

ProcedureTriggerWho may do itStripe role that can do itWhat the app must show after
Full refundA duplicate charge, or a refund request your terms allowThe named support person, within the amount limitRefund Analyst, Support Specialist, Analyst or Administrator (“refund payments”)The refund on the customer’s billing history, and paid access set by your written rule
Partial refundA goodwill credit or a part-period refundSame person and limit as a full refundThe same roles as a full refundThe partial amount, with the plan unchanged unless the rule says otherwise
Complimentary accessA promised free period, a partner deal, an outage creditThe founder or one named adminSupport Specialist (its jobs include “Manage subscriptions”) or Administrator, who can “see and manage almost everything”The comp, its end date and its reason, visible on the account
Dispute responseStripe’s dispute notificationThe named dispute ownerDispute Analyst (“view, submit evidence for, and accept disputes”) or Support SpecialistThe dispute as open, and access set by your written rule
Lost chargebackThe dispute closes as lostThe founderDispute Analyst or Support Specialist (to view it)The loss recorded, access set by the rule, and no silent re-billing

The general runbook format, with its owner, trigger and rehearsal lines, is the runbook template; these five are that format filled in for money. A billing team at a larger company adds daily usage ingestion, CRM sync queues and revenue recognition to its runbook, which is real work for a company with a billing team and too much for an app where two people sign in to Stripe.

In the Production Hardening Sprint this is deliverable 5.10, where we “document refunds, complimentary access, and dispute handling in the application’s payment setup.”

What goes wrong without it

The momentWhat happens with no runbookWhat it costs
A refund request on a Friday nightThe only person with provider access is offline, and nobody else knows the stepsA weekend of waiting, and a customer who may take the charge to their bank instead
A promised free yearSomeone edits the account’s plan in the database with no end date and no noteA comp that never ends, and nobody who can say who granted it or why
A dispute email nobody ownsIt sits in one person’s inbox until the response window closesThe disputed amount and the dispute fee stay debited, and your dispute rate with that card network rises
A former contractor who still has Dashboard accessNobody removed them when the work endedSomeone outside the team can still act with whatever role they held

My audits read code, not operations, so I have no count of apps missing a billing runbook. They do show how faults reach a founder: 17 of the 21 third-party apps I audited in June and July 2026 had no error tracking or alerting, so when a user hits an error, nothing records it. Those 21 are 11 public apps audited across all 12 pillars plus 10 held-out apps audited blind, a selected set rather than a random sample, so the figure is not a rate for AI-built apps in general.

In my reading, an app like that tends to hear about a billing fault from the customer, and that email is when someone goes looking for the runbook. When the billing problem is part of a wider outage, what to tell customers when your app is broken covers the message and the refund question for that case.

The team is unsure how to issue a refund

A team unsure how to issue a refund is missing 3 facts: who has a provider role that can refund, whether a refund also cancels the subscription, and what the app should show afterwards. The refund happens in the payment provider, and the app follows the provider’s event.

Unsure usually looks like one of four things. The founder is the only person with access and is asleep. Support can see the customer in the app but has no login to Stripe. Nobody knows whether refunding ends the subscription, which is the question behind a refund is not a cancellation. Or nobody knows whether to refund in Stripe or change the row in the database: my rule is always the provider, and the app updates when the provider’s event arrives. All of this is the seller’s side of a refund, not what a customer can demand from a company.

There is no process for handling chargebacks

A business with no process for handling chargebacks leaves every dispute to chance. A dispute arrives with a response deadline, a fee and the disputed amount debited. The process is one named owner, a refund-or-fight decision, and evidence submitted before the deadline.

When a cardholder disputes a payment, Stripe notifies you through the Dashboard, email, webhooks and the API, and debits the disputed amount plus a dispute fee from your Stripe account. Stripe’s US pricing page lists $15.00 for each dispute you receive. After a chargeback is created you usually have 7-21 days to respond, depending on the card network.

Some disputes start earlier, as an inquiry, and that is where an unowned inbox costs the most. Stripe warns that failing to respond to an inquiry can signal acceptance of the claim, “resulting in an escalation to a formal, and likely unwinnable, chargeback.”

Volume has its own cost. If disputes exceed a network’s thresholds, the network places you into a monitoring program, and you can incur monthly fines and additional fees until you reduce your dispute or fraud levels in a sustained way; Stripe’s page on dispute monitoring programs lists the Visa and Mastercard programs and their thresholds.

How to write each procedure

Each section below is one page of the runbook, in the fixed format from the table above, with Stripe as the worked example. The steps come from Stripe’s documentation, not from a refund I ran for this page, so check each click path against your own Dashboard when you write yours down.

Other providers change the page, not the format. On Paddle, refunds and credits are adjustments that sit alongside the original transaction, most refunds for live accounts need Paddle’s approval, and “Paddle automatically creates adjustments for chargeback events for you”, in the words of Paddle’s refund and credit documentation. Paddle’s docs describe it as a merchant of record that handles refunds and chargebacks, so a Paddle runbook is mostly about who requests a refund and who tells the customer. On Lemon Squeezy, orders can be refunded partially or in full from the action menu on the Orders page, per Lemon Squeezy’s refund steps.

Refunds, full and partial

A refund procedure is 7 steps: find the customer by provider id, open the payment, choose the amount, give the reason, decide separately about the subscription, confirm the app received the event, and tell the customer the amount and timing. The partial-refund rule is written once so answers match.

  1. 01 Find the customer by the provider customer id stored on the app's record, not by typing an email into the search
  2. 02 Open the payment
  3. 03 Choose a full or partial refund, and enter the amount for a partial one
  4. 04 Pick the reason Stripe asks for, and add the note it requires if the reason is Other
  5. 05 Decide separately whether the subscription ends now, at the end of the period, or not at all
  6. 06 Confirm the refund's status, and that the app received the event and shows the right state
  7. 07 Tell the customer the amount and when to expect it, then record the refund in the team's log

In the Dashboard the refund starts from the overflow menu next to the payment, under Refund payment; it defaults to a full refund, and a partial refund is a different amount typed in. The reasons the API accepts from you are duplicate, fraudulent and requested_by_customer, and a refund’s status can be pending, requires_action, succeeded, failed or canceled. For the customer message, Stripe’s refund documentation says the customer sees the refund as a credit approximately 5-10 business days later, depending on the bank, and that some refunds, those issued shortly after the charge, appear as a reversal instead, with no separate credit.

The events the app must act on are part of the subscription lifecycle in Stripe, and step 6 is where you confirm they arrived. For partial refunds, my working rule is to write the amount rule down once, either pro rata by unused days or a fixed goodwill sum, so two people give the same answer to the same request.

Before refunding at all, check that you can fund a refund: where the money comes from, what happens to the original fee and what a failed refund needs. Refund rights differ by country and by the terms your customers accepted, and nothing here is legal advice.

Complimentary access

Complimentary access is granted one of two ways: a coupon in the payment provider, or a dated override written by an admin operation. My rule: every comp has an end date, a reason and a name, and the list is reviewed monthly.

In Stripe, a coupon can apply once, for a set number of months (duration=repeating with duration_in_months), or forever, and Stripe’s own example creates a coupon with percent_off=100. A full-discount coupon with a repeating duration gives a free year that ends by itself, and because it sits on the subscription, billing stays the record. To apply one, Stripe’s coupon documentation gives the path: open the subscription, then Actions, Update subscription, Add coupon.

The other route is an override on the account’s entitlement with an end date, written by one admin operation that checks the caller’s role and logs who ran it. Never a direct edit of the database row, and never a flag with no end date. The monthly look at active comps is a runbook line of its own: anything past its date or with no reason gets ended or written up.

What is a payment dispute process?

A payment dispute process is what your team does between the provider’s dispute email and the card network’s decision. It has 4 parts: a named owner, a decision to refund or fight, evidence such as usage logs and accepted terms submitted before the deadline, and a recorded outcome with the access rule applied.

The stages below are the ones in Stripe’s guide to how disputes work. A payment dispute is a cardholder challenging a payment through their card issuer. On this page “dispute” means the whole case, and “chargeback” means the formal stage where the card network pulls the funds.

StageWhat happensWho actsThe clock
Early fraud warningA card issuer on the Visa, Mastercard or JCB network flags a payment it suspects might be fraudulentYou, if you choose to refund; no response is requiredNone stated
InquiryA preliminary phase some networks use; American Express and Discover use it most often, and Mastercard and Visa no longer doYour dispute owner: satisfactory evidence that answers it, or a full refund, resolves it with no dispute feeMarked closed if open 120 days without escalating
ChargebackThe card network pulls the disputed amount from your balance and holds it for the whole dispute; Stripe debits a dispute feeYour dispute owner decides to accept or counterUsually 7-21 days to respond, depending on the card network
ResponseYou submit evidence through the Dashboard or the APIYour dispute ownerInside the response window above
DecisionThe issuer decides, and Stripe sets the dispute to won or lostThe card issuerUsually 60-75 days after evidence; the whole lifecycle can take 2-3 months
Unchallengeable disputeA dispute the network’s rules or local regulations do not let you challengeNobody; record the lossIn general closed as lost as soon as Stripe notifies you

The procedure on top of those stages is mine. The dispute email goes to a shared inbox with a named owner. The first decision is refund or fight, made early, because while a dispute is open you can’t issue a refund outside the dispute process. For a SaaS, the evidence that matters is sign-in and usage records after the payment date, the customer’s emails, the terms accepted at checkout and the invoice. Submit it before the deadline, remove or keep access as the written rule says, and record the outcome.

The lost chargeback is the fifth procedure. When the issuer upholds the dispute, no money moves from your side: the disputed amount already left your balance, and the fee for receiving the dispute is not returned unless your Stripe contract says otherwise. Access is then decided by the rule, and the customer is not charged again without a conversation first. A card that declines on renewal is a different problem, covered by how to handle failed subscription payments.

A billing support checklist: what to check before touching money

A billing support checklist is 6 checks before anyone touches money: confirm the sender owns the account, find the provider customer id, read status and recent invoices, compare with the app, offer the self-serve portal, then pick the procedure and its owner.

  1. 01 Confirm the sender owns the account: reply to the account's email address, and never act on a request from a new address
  2. 02 Find the provider customer id on the app's record of that account
  3. 03 Read the subscription status and the last three invoices in the provider
  4. 04 Compare what the provider shows with what the app shows
  5. 05 If the change is one the customer portal lets customers make themselves, send the portal link
  6. 06 Otherwise pick the procedure from the runbook and name the person allowed to run it

Check 5 needs a portal that exists, so knowing how to set up the Stripe customer portal comes first. This list is for whoever answers billing email for a software subscription; medical billing support is a different field with its own checklists.

The provider audit log: who changed access, and who issued the refund

A Stripe audit of your own account shows who changed access: its activity log records API key changes, user invitations and role changes. Refunds are not in that log, so the runbook keeps its own record of who refunded what, and team roles decide who can refund at all.

The meaning of “stripe audit” here is checking your own account. Stripe’s Activity Logs documentation describes programmatic access to the account’s security history, “including API key management, user invitations, role changes, authentication, and money movement configuration”, and Stripe retains the logs for 6 months. It is a preview API, so requests carry a preview version header. Its tracked actions cover API keys, invitations and roles, with sign-in and account security changes listed from October 5, 2026 and payout destinations and Issuing from October 9, 2026; refunds of customer payments are not among them. The Dashboard’s security history adds audit logs of “sensitive account activity, such as logging in or changing bank account information”. Whether the Dashboard shows which team member issued a given refund is not stated in Stripe’s docs, which is why checklist line 8 is the team’s own record.

Checklist lines 1 and 2 are set in the roles. Stripe’s team roles describe Refund Analyst as “for people who need to refund payments and issue credit notes on invoices” and Support Specialist as for people who need to “refund payments, resolve disputes”; a Dispute Analyst can answer disputes but cannot create or refund payments. For the second factor on every team member, the Dashboard supports several forms of multi-factor authentication, and Stripe recommends passkeys or hardware security keys because they resist phishing. That setting in the provider mirrors the rule the app applies to its own admins.

Two apps I audited kept no record of who changed what, the gap that audit logging best practices are meant to close. The lesson I take from it is that a record of who acted has to be written at the time, by the team, because nothing downstream adds it later.

The other meanings of “stripe audit” are Stripe’s own compliance audits, in the last question below, and the balance confirmation an external audit firm requests, which Stripe’s support page answers with a PDF report of your funds movement exported from the Dashboard.

How to verify it: walk through refund steps in test mode

A billing runbook is verified by having the person named in it, not the founder, walk all 5 procedures in test mode with their own role. Any step that fails, a role that cannot refund, or an app that does not change is fixed and the date recorded.

To walk through refund steps in test mode, set up a sandbox with a test customer tied to a test user of the app, then run the six steps below with the evidence each one leaves.

  1. 01 Invite the person named in the runbook into the sandbox with the role they hold in the live account, and have them sign in with it. Evidence: the role name in the sandbox team list and in the live one
  2. 02 They run the full refund from the page as written and note every step that was missing or wrong. Evidence: the refund id and the notes
  3. 03 They run the partial refund the same way. Evidence: the refund id and the amount rule they applied
  4. 04 They grant complimentary access and confirm the app unlocks and shows the end date. Evidence: the coupon or override id and the date on the app screen
  5. 05 They create a payment with the dispute test card, find the dispute, and submit placeholder evidence. Evidence: the dispute id and where the dispute showed up
  6. 06 After each one, they check the app's state and the team's record. Evidence: the record line

Step 1 matters because of how sandboxes inherit roles. New sandboxes use the Private access level by default, where team members must be invited and only the Admin, Super Admin and Sandbox Administrator roles are inherited, and a person can hold a different role in a sandbox than in the live account. A refund that works for an Admin proves nothing about a Refund Analyst, so the role names in both team lists have to match.

For step 5, Stripe’s testing documentation lists 4000000000000259 as a card whose charge succeeds “only to be disputed as fraudulent”, and 4000000000001976 for an inquiry; responding with winning_evidence or losing_evidence closes the test dispute as won or lost, so a losing_evidence response is how the lost-chargeback procedure gets its walk-through. Whether Stripe sends its dispute email for a sandbox dispute is not stated in Stripe’s docs, so the evidence is where the dispute appeared: the Dashboard’s disputes list and the app’s webhook log. In a sandbox the payments you create aren’t processed by card networks or payment providers, so nothing in the walk-through moves real money and a test-mode order needs no real refund.

The failures worth looking for are a role that cannot refund, an app that did not change after the event, and a dispute nobody noticed. Keep the table below with names and dates, the test object ids and the list of roles, and walk the procedures again whenever a person or a role changes.

ProcedureWalked byDateResultFix
Full refund(named person, role)(date)Passed, or failed at step (n)(what changed in the page or the app)
Partial refund(named person, role)(date)Passed, or failed at step (n)(what changed)
Complimentary access(named person, role)(date)Passed, or failed at step (n)(what changed)
Dispute response(named person, role)(date)Passed, or failed at step (n)(what changed)
Lost chargeback(named person, role)(date)Passed, or failed at step (n)(what changed)

This is also how we verify deliverable 5.10 in the sprint: “walk through the procedures in test mode and identify the responsible account permissions.”

Where the sprint does this

The 5.10 result goes into the production readiness report, deliverable 13.1, which accounts for all 123 IDs, keeps failures visible until resolved and explains genuine non-applicable items. Building new product features or modules sits outside the sprint. Every other deliverable is listed in the sprint’s published scope.

Common questions about refunds and disputes

How long does a payment dispute take?

A card dispute can take 2-3 months from initiation to the final decision, by Stripe’s account. The response window is usually 7-21 days once a chargeback is created, and after you submit evidence the issuer usually has 60-75 days to decide, both depending on the card network.

Who loses money when you dispute a charge?

The business does, at least until the decision: Stripe debits the disputed amount plus a dispute fee from its balance when the dispute opens. If the issuer rules for the business, the disputed amount comes back; the fee for receiving the dispute does not, unless the Stripe contract says otherwise, and the separate fee for countering is returned only on a win.

Do you need to give a reason for a refund?

Yes, in Stripe’s Dashboard: the refund steps ask you to select a reason, and if you choose Other you must add a note that explains it. Through the API the reasons you can give are duplicate, fraudulent and requested_by_customer. My rule is that the team’s own record carries a reason either way, in plain words.

What are valid reasons for a refund?

The valid reasons are the ones your terms and your runbook name. For a small SaaS, examples rather than law: a duplicate charge, a charge after the customer canceled, a credit for an outage, and goodwill inside a stated window, each with the person who approves it. Your terms and local law decide what you owe; this page does not.

Who audits Stripe?

A PCI-certified auditor evaluated Stripe and certified it to PCI Service Provider Level 1, according to Stripe’s security page, and its systems, processes and controls are regularly audited as part of its SOC 1 and SOC 2 compliance programs. That is Stripe’s own compliance, a different thing from a Stripe audit of your own account, which the provider audit log section above covers.