Refunds
A refund returns money to the customer who paid you. You can start one on the card machine, in the merchant portal, or through the API, and all three end up in the same place: a refund recorded against the original payment, with an audit trail showing who authorised it.
Two things decide how a refund behaves, and it is worth knowing them before you start.
Has the payment settled? A payment that has not yet settled is cancelled rather than refunded. Cancelling voids the whole thing, so nothing ever reaches your bank and nothing reaches your customer’s statement. A settled payment is refunded instead, in full or in part.
Was it card or Bitcoin? Card refunds move money. Bitcoin refunds do not: Musqet records them for your books, and you return the funds yourself. See Bitcoin refunds are recorded, not sent.
Where you do it
Section titled “Where you do it”On the card machine
Section titled “On the card machine”From the sale screen, tap the grid menu icon at the top to open the menu. Under the Refunds heading, choose Card Refund, labelled “Return money to customer”. The steps are the same on a PAX and on a Verifone: both run the Musqet app and share these screens.
With password protection on, which is the default, the terminal shows a Refund password prompt and asks the staff member for their staff ID and password before the refund goes anywhere. The refund is then recorded against that person rather than against the device.
If the terminal is running unattended as a kiosk, it shows Refund verification and waits for a staff member. That request cancels itself after five minutes.
With it off, the terminal refunds straight away. No password, nobody named, and nothing written to the refund log, because the log is written by the password check. See The refund log. The setting is Password protect refunds & voids in the portal, per business, and turning it off trades your refund audit trail for a couple of seconds at the till.
In the merchant portal
Section titled “In the merchant portal”Open the payment link, find the payment, and choose Refund. The dialog asks for a refund type:
- Refund sale, for a settled sale, in full or in part. The amount box opens pre-filled with everything that is still refundable.
- Cancel, for a payment that has not settled yet. Cancel has no amount, because it voids the payment in full.
Through the API
Section titled “Through the API”Refunds are issued against the payment link that took the payment. Card and Bitcoin have separate endpoints, because one moves money and the other only records that you did.
curl -X POST https://api.musqet.tech/api/v1/payment-links/{id}/refunds/card \ -H "Authorization: Bearer msqt_api_live_..." \ -H "Content-Type: application/json" \ -d '{ "kind": "REFUND_SALE", "amountInCents": 1250 }'Amounts are always integers in the smallest unit of the payment’s currency, so £12.50 is 1250.
kind takes one of three values, and the third is not like the other two:
kind | What it does | Needs |
|---|---|---|
CANCEL | Voids an unsettled payment in full. No amount | TAKE_PAYMENTS_WRITE |
REFUND_SALE | Refunds a settled payment, in full or in part, with amountInCents. Capped at what the payment took | TAKE_PAYMENTS_WRITE |
REFUND | An uncapped refund with amountInCents, not limited by the payment it refunds against | TAKE_PAYMENTS_WRITE and REFUNDS_WRITE |
REFUND is the one to be careful with. It is not limited by the original payment, so it can send more
than the customer paid, and it is the reason REFUNDS_WRITE exists as a separate permission: an API
key without it gets 403 for this kind and works normally for the other two. The portal never issues
it, so a refund of this kind always came from the API.
Bitcoin
Section titled “Bitcoin”Recording a manual Bitcoin refund has its own endpoint. It moves no funds: see Bitcoin refunds are recorded, not sent.
curl -X POST https://api.musqet.tech/api/v1/payment-links/{id}/refunds/bitcoin \ -H "Authorization: Bearer msqt_api_live_..." \ -H "Content-Type: application/json" \ -d '{ "amount": 25000, "reason": "Returned in store" }'Three differences from the card endpoint are worth reading twice:
amountis in satoshis, not the smallest unit of a fiat currency, and there is noamountInCentsfield.reasonis optional and stored against the refund.- There is no
kind. The amount is always capped at what the original Bitcoin payment received, so there is no uncapped form and no equivalent ofREFUNDS_WRITE.TAKE_PAYMENTS_WRITEis enough.
Before you start
Section titled “Before you start”You need all of these, or the refund will be refused:
- The right permission.
TAKE_PAYMENTS_WRITEcovers cancelling and refunding a sale. On the terminal, the staff member also needs the Refund & void permission, which is set per staff member in the portal. - A refund password that has been changed at least once, if password protection is on. New staff members are created with a temporary password and must set their own before their first refund. With password protection off, no password is asked for and this does not apply.
- Headroom on the original payment, for everything except an uncapped
REFUNDthrough the API. A cancel or a refund-sale cannot exceed what the payment took, counting refunds already made against it. AREFUNDis not capped at all, which is why it needsREFUNDS_WRITE. - A payment that reached the gateway. A payment with no gateway reference cannot be refunded, because there is nothing to refund against.
Staff members can also carry a per-transaction limit and a daily limit. Both are optional, and a staff member with neither set has no ceiling of their own.
Cancel and refund sale are mutually exclusive
Section titled “Cancel and refund sale are mutually exclusive”A payment can be cancelled, or it can be refunded, but never both.
Once a payment is cancelled there is nothing left to refund, and once any part of it has been refunded it can no longer be cancelled. Partial refunds can be repeated until the original amount is used up, and the total is capped at what the payment took.
Bitcoin refunds are recorded, not sent
Section titled “Bitcoin refunds are recorded, not sent”Recording a manual Bitcoin refund does not move any Bitcoin. It writes the refund into your records so your books balance, and returning the funds is something you arrange yourself, out of band. The portal is explicit about this at the point of use, and the payment is badged afterwards as “Manual refund. Funds returned out of band”.
Like card refunds, the recorded amount is capped at what the original Bitcoin payment received.
How a card refund travels
Section titled “How a card refund travels”For a cancel or a refund-sale, Musqet reserves the refund before asking the gateway for it, then settles the reservation to whatever the gateway says. The reservation is what stops two people refunding the same payment at the same moment and taking it past its limit between them.
An uncapped REFUND through the API is not reserved. It goes straight to the gateway and is
recorded afterwards, because there is no limit for a reservation to protect. Two of them issued at
once will both go through.
sequenceDiagram
accTitle: How a card refund travels
accDescr: The merchant asks Musqet for a refund. Musqet checks the permission and the remaining headroom, reserves the refund as pending, sends it to the card gateway, and settles the reservation to whatever the gateway returns before reporting the final status back.
participant Merchant
participant Musqet
participant Gateway
Merchant->>Musqet: Refund this payment
Musqet->>Musqet: Check permission and remaining headroom
Musqet->>Musqet: Reserve the refund as pending
Musqet->>Gateway: Send the refund
Gateway-->>Musqet: Approved or declined
Musqet->>Musqet: Confirm the reservation to that outcome
Musqet-->>Merchant: Final refund statusIf the gateway cannot be reached at all, the reservation is released and the refund is marked failed, so you can try again. A timeout does not prove the refund failed at the card scheme, so check the refund against the payment before retrying a large one.
When a refund is refused
Section titled “When a refund is refused”Match what the person in front of you is seeing.
| What they see | What it means | What to do |
|---|---|---|
| Incorrect password | The refund password did not match | Re-enter it. Repeated failures lock the staff member out |
| Incorrect staff ID | The staff number entered does not exist at this business | Check the number in the portal under staff members |
| Too large transaction! | Over that staff member’s per-transaction limit | A staff member with a higher limit must authorise it, or raise the limit in the portal |
| Daily limit reached! | Their refunds today already add up to their daily limit | As above, or wait until tomorrow |
| Locked out! or Locked out until … | Too many failed password attempts | Wait for the lockout to expire. It lengthens with each round of failures |
| Too many attempts, try again later! | Same lockout, hit during verification | As above |
| Could not reach the server! | The terminal has no route to Musqet | Check the terminal’s connection, then retry |
| Payment has already been cancelled | The payment was voided, so there is nothing to refund | Nothing to do. The customer was never charged |
| Payment has already been refunded | A partial refund exists, so the payment cannot be cancelled | Refund the remaining amount instead of cancelling |
| Refund exceeds original sale amount | The amount asked for is more than the headroom left | Refund the remaining amount. The portal pre-fills it for you |
The refund log
Section titled “The refund log”The refund log is a record of password checks, not of refunds. Every terminal refund attempt that goes through the password check is logged against the staff member who tried, whether it succeeded or not, which is what makes rejections visible as well as approvals.
So if Password protect refunds & voids is off for a business, terminal refunds write nothing here at all. The log is not merely missing a name: there is no entry. If you are relying on this log to account for refunds, check that setting first.
The log is in the portal, and available through the API for anyone building their own reporting.
Entries carry one of four statuses. Note the American spelling, which is what the API returns:
| Status | Meaning |
|---|---|
authorized | The staff member was authorised and the refund went ahead |
failed | The attempt was rejected |
limit_exceeded | Rejected for exceeding the per-transaction limit |
daily_limit_exceeded | Rejected for exceeding the daily limit |
A rejection also records why: invalid_signature, permission_denied, limit_exceeded or
daily_limit_exceeded. invalid_signature means the request was not signed by the staff member it
claimed to be from, and is worth investigating rather than retrying.
The void log
Section titled “The void log”The void log is the void equivalent of the refund log, and it lives in the portal under Staff members > Void log. It lists every void taken on the terminal, newest first, and you can filter it by the staff member who took them.
Each entry records who voided the sale and what it voided:
- Who, by name and staff number.
- The original sale, by invoice number, amount and currency.
- When that sale was taken, and which terminal the void was taken on.
It also records the outcome:
| Status | Meaning |
|---|---|
authorized | The staff member was authorised and the void went ahead |
failed | The attempt was rejected: either the void password did not match (invalid_signature), or the staff member does not have the refund & void permission (permission_denied) |
Unlike a refund, a void has no amount of its own to weigh against a limit, so the per-transaction and daily limits that apply to refunds do not apply to voids.
Like the refund log, the void log is written by the void password check, so it follows the same Password protect refunds & voids setting. With password protection off, a terminal void is not recorded here, for the same reason a refund is not. See The refund log.
When a refund reaches the customer
Section titled “When a refund reaches the customer”Musqet sends a card refund on to the gateway straight away, but when it lands in the customer’s account is not something Musqet controls. A card refund travels back through the card scheme, the acquirer and the customer’s own bank, and each of those adds its own time. In practice a refund usually shows on the customer’s statement within one to five working days, and it can be longer over a weekend or a bank holiday.
So a customer who has been refunded but cannot yet see the money has not lost it: it is in transit between the banks. There is nothing to re-send from Musqet, and issuing the refund again would return the money twice. If it still has not arrived after several working days, the customer’s own bank is the place to chase it.
If a customer disputes a payment
Section titled “If a customer disputes a payment”A chargeback is not the same as a refund. A refund is money you choose to return. A chargeback is the customer asking their own bank to reverse a card payment, for example because they do not recognise it or believe something went wrong, and the card scheme, not Musqet, decides the outcome.
If a dispute is upheld, the payment is reversed and the money goes back to the customer. The process runs on the card scheme’s own timetable and can take weeks. It sits outside anything you do on the terminal or in the portal, so it is not something you approve or decline there.
If you receive a chargeback, or a request for information about a payment, contact Musqet support so we can help you work through it.
See also
Section titled “See also”- The v1 API reference for every field on the refund endpoints, generated from the same code that serves them