Understand how InStore moves a delegated refund to a fallback refund option when the primary refund method fails, and how a store associate cancels a credit card refund that is in progress.
- Refund fallbacks: what InStore does when an attempt fails and the refund instruction names other payment options.
- Cancellation: how the associate cancels a refund, including a credit card refund that is waiting on your payment processor.
Refund fallbacks
fallbackRefundTypes. When the attempt on the primary option fails, the associate can complete the same refund through one of them. For the fields, see Refund object.How InStore handles fallbacks
InStore doesn't choose a fallback or run it automatically. Your predetermined refund alternatives are presented during checkout, and the associate decides what happens after a failure.
- Session creation. InStore resolves the primary payment option and every fallback option, and the processors they point to. A reference that doesn't resolve fails the whole request, so no refund session is created. The session response lists each instruction in
refundProcess[], with its fallbacks inrefundProcess[].fallbacks[]. - Identifiers. A fallback has no
transactionIdof its own. InStore copiesorderIdandoriginalCTPaymentIdfrom the parent instruction into each fallback, so a fallback always refunds the same Order and Payment. - Order of options. The associate sees the primary option first, followed by the fallbacks in the order you listed them.
- One option at a time. Each refund instruction is attempted through one option at a time. InStore never splits a single instruction across options.
What counts as a failure
InStore treats each of these as a failed attempt on the active option:
| Cause | Where it comes from |
|---|---|
Your processor sends a result event whose effective status isn't Success. | Your processor |
The associate answers a prompt with anything other than the primary option. | The associate |
| The attempt times out, or your processor rejects the request. | Your processor |
| The associate cancels a credit card refund. | The associate |
| The selected option resolves to no payment processor. | Configuration |
| A cash entry can't be paid out because there isn't enough cash in the drawer. | The drawer |
What the associate can do after a failure
InStore processes refund entries one at a time, in order. If an entry fails, InStore doesn't attempt the remaining entries until the associate resolves it. After a failure on an entry, the associate sees a dialog for that entry with these choices:
| Choice | Result |
|---|---|
| Retry the failed option | InStore sends your processor a new request. Every request carries additionalData.retryCount alongside any pspData from the refund instruction. The first attempt on an option sends 0, and each retry adds 1 (1, 2, and so on). |
| One of the untried fallbacks | InStore starts the entry on that option, and retryCount resets to 0. A fallback starts straight away, without another Next click. |
| Cancel | The refund is canceled. Shown only while no tender has succeeded. See When the refund is canceled. |
Options that aren't currently available, for example because their processor failed to set up, are shown but can't be selected.
A failed entry blocks the plan. InStore doesn't move on to the next entry until the associate has resolved the failed one.
Effects on your integration
- Your processor sees each attempt as a separate refund request. Each attempt, including a retry, gets its own
requestId. A fallback to another processor goes to that processor's URL, and a fallback to the same processor is a new request on the same URL. - Failed attempts aren't recorded. InStore records a tender only after an option succeeds, with the
paymentOptionIdof the option that actually succeeded. The finalize refund webhook therefore reports the fallback option, not the primary one, when a fallback completed the refund. - Only a
Successresult completes a refund. InStore moves on to a retry or a fallback after any other result, so make sure a failed or canceled attempt hasn't moved money. - The
customerIdfollows the active option. Your processor receives thecustomerIdof the instruction or fallback that is running. See Pay-on-account refund processing.
Cancellation
When the refund is canceled
mistStatus event to the host application, with process set to refund and status set to cancelled. The associate's own Cancel choices dispatch the same event.Cash entries are attempted together, as one batch. When the drawer can't cover the batch and another entry has already succeeded, InStore doesn't cancel the refund. It reopens the exhausted cash entries so that the associate can choose a different method for them.
Credit card refund cancellation
Enable cancellation
- The payment processor is a
BankCardprocessor. - Its
allowCancelparameter is set totruein the payment processor configuration. The parameter isfalseby default.
BankCard processor only. Stored value, wallet, and pay-on-account refunds have no Cancel button, and neither does cash.How cancellation works
- The associate starts the refund. InStore sends the refund request to your processor and receives a
requestId. - The associate taps Cancel. The button is on screen for the whole attempt, but a cancel can be sent only once InStore has the
requestIdof the refund request. Before that, InStore shows an error and sends nothing. After a tap, the button is deactivated, so the cancel can't be sent twice. - InStore marks the attempt as having a cancel pending, and removes any prompt that is on screen. From now on, any prompt that your processor streams for this attempt is discarded, not shown.
- InStore posts a cancel request to its own
custom/cancel_credit_paymentendpoint, which forwards it to thecancel_credit_paymentpath on your processor URL. - Your processor stops the operation on the card reader, then reports the outcome as the final result of the original refund request, on that request's
callbackUrl. - InStore treats the result as a failed attempt, as for any other failure.
connectionTimeout limits TCP connection setup. Its timeout limits the entire HTTP request, including reading the response body.Cash primary refunds and refund fallbacks to Cash are processed together at the end of the sequence.
Cancel request
{processorUrl}/cancel_credit_payment. It's wrapped like the other payment requests: the fields below arrive in payload, next to requestId, statusUrl, and callbackUrl. Verify X-Signature as for every other request.{
"requestId": "req-1",
"location": { "typeId": "location", "key": "store-1" },
"workstation": { "typeId": "workstation", "key": "register-1" },
"referencePayment": { "typeId": "payment", "id": "pay_3Nk82jLkQ4eR1mWx" },
"cartKey": "return-cart-1",
"processor": "credit-processor-1",
"paymentProcessorId": "66f1a2b3c4d5e6f708192a3b",
"amount": { "amount": 4999, "currency": "USD", "precision": 2 }
}
| Element | Data type | Description |
|---|---|---|
| requestId | String | The requestId of the refund attempt to cancel. It's the one InStore sent in the refund request. |
| location / workstation | Object | References to the location and workstation of the refund. |
| referencePayment | Object | A reference to the original commercetools Payment being refunded. |
| cartKey | String | The key of the return Cart. |
| processor | String | The key of the payment processor. |
| paymentProcessorId | String | The ID of the payment processor. |
| amount | Object | The refund amount of the attempt, when known, as InStore tender money: { amount, currency, precision }. |
2xx status code to acknowledge the request. Don't wait for the card reader to stop before you respond. InStore reads a non-2xx response, or no response within the timeout, as a failed cancel request: see If the cancel request fails.Report the cancellation
callbackUrl of the original refund request. Use a status other than Success, and Cancelled for a cancellation:{
"event": "RefundFailure",
"data": {
"status": "Cancelled",
"transactionId": "req-1",
"reason": "Refund canceled on the terminal"
}
}
data.reason to the associate. If you leave it out and status is Cancelled, the associate sees a generic cancellation message. With any other missing reason, they see a generic decline message.Refunded result as usual. InStore then records the tender, and the cancel has no effect.What InStore does with the result
A canceled refund follows the same path as a declined one:
- No tender is recorded for the attempt, and nothing is written to commercetools.
- If the workstation is set up to print nullified payment receipts, InStore prints one for the attempt.
- With fallbacks left on the instruction, the associate sees the failure dialog, described in What the associate can do after a failure.
- With none left, InStore cancels the refund.
If the cancel request fails
If InStore can't deliver the cancel request, the refund isn't canceled. InStore:
- Shows an error to the associate.
- Re-enables Cancel.
- Restores the prompt that was on screen, if there was one.
- Lets the refund continue, so your processor can still send its result.
Checklist for your processor
- Implement
cancel_credit_paymentand verify itsX-Signature. - Look up the in-flight operation by
requestId, and stop it on the card reader. - Acknowledge the cancel request without waiting for the reader.
- Send a result event for the original refund request, with
status: "Cancelled"and areason. - Send exactly one result per refund attempt: a late
Refundedevent after aCancelledone is ignored. - Make sure a canceled attempt hasn't moved money.