/refund

Initiate full or partial refunds on orders that were charged through FlexFactor.





❗️

Prerequisites to invoke this API:

📘

Idempotency — full vs. partial refunds

Full refunds are de-duplicated server-side via the order ID: repeated full-refund calls for the same order will not double-credit, with or without a key.

Partial refunds are not auto-deduplicated. Because multiple distinct partial refunds against one order are legitimate, the order-ID guard cannot tell a retry apart from an intentional second partial refund. Send an idempotencyKey (a GUID, in the JSON body) on every partial refund and reuse it on retries — within the 24-hour replay window FlexFactor returns the original response instead of issuing a second partial credit, and a reused key carrying a different payload is rejected as a 409 conflict. See the Idempotency reference (§3.4) for the full replay-window, response-code, and retry behavior.

Retrying a failed refund with the same key. A refund the processor declined is not stored against the key, so a retry with the same key runs the refund again. A refund that failed for an unexpected reason (errors contains REFUND_FAILED) is stored, and a retry with the same key replays the FAILED response without running the refund again, because the processor might already have credited the cardholder. Before issuing that refund again under a new key, confirm with GET /v1/orders/{id} that refundsAmount does not already include it.


Request

Details

HTTP MethodPOST
Endpoint - Sandboxhttps://api-sandbox.flexfactor.io/v1/orders/{id}/refund
Endpoint - Productionhttps://api.flexfactor.io/v1/orders/{id}/refund
{id}FlexFactor unique transaction identifier.\ This is the FlexFactor orderId returned by /evaluate and/or GET /v1/orders/{id} and/or webhook and/or SFTP batch.

Body

amountIntegerRequired — send amount or amountToRefund, not bothAmount to refund, in cents, for example 2500 for $25.00. If you send neither, the request fails validation with HTTP 400.
amountToRefundDecimalDeprecatedAmount to refund, in dollars, for example 25.00. Still accepted for existing integrations; use amount in new integrations.
refundMessageStringOptionalAdditional message you want to send with the request, for example "Customer request".
idempotencyKeyGuidOptional — recommended for partial refundsFull refunds are de-duplicated server-side by order ID; partial refunds are not. Send a GUID here and reuse it on retries to prevent duplicate partial refunds — it replays the original response within the 24h window and detects reused-key conflicts. See Idempotency.
isConsumerRequestedRefundBooleanOptionaltrue when the cardholder requested the refund.
transactionIdGuidOptionalFlexFactor identifier of the transaction to refund.
supportedGatewayIdGuidOptionalFlexFactor identifier of the gateway that processes the refund.

Full and partial refunds

Full and partial refunds use the same request. amount is the amount to return to the cardholder, in cents, which matches /capture and /void. An amount equal to the captured amount is a full refund; a lower amount is a partial refund. You can issue several partial refunds against one order.

  • There is no currency field. The refund is applied in the currency of the original order.
  • Send an idempotencyKey on every partial refund and reuse it on retries (see the callout above). Full refunds are de-duplicated by order ID.
  • After a refund, the order stays completed — a refund does not move it to returned. GET /v1/orders/{id} reports the refunded total in refundsAmount (dollars), and the order.refunded webhook fires with EventData.Amount in cents. See the state model in Workflows §3.5.

If the refund amount exceeds the remaining refundable balance, the request fails validation and returns HTTP 400.

📘

Migrating from amountToRefund

amountToRefund (dollars) is deprecated but still accepted. To migrate, multiply by 100 and send the result as amount: amountToRefund: 25.00 becomes amount: 2500. Send one field or the other, not both. GET /v1/orders/{id} still reports refundsAmount in dollars.


Try it right now:


Response

The refund response uses the common FlexFactor envelope, but on this endpoint status carries the refund outcome (SUCCESS or FAILED) rather than a transaction status such as APPROVED. Read success and status; result and statusCode are never populated on this endpoint and are always null. The envelope and the transaction state model are defined in Workflows §3.5.

statusstringRefund outcome: SUCCESS | FAILED. Key your refund logic on this field.
successbooleantrue when errors is empty, that is, when the refund succeeded.
resultstringAlways null on this endpoint. Do not use it.
responseCodestringProcessor response code when the refund failed. null on success. Optional
responseMessagestringProcessor response message when the refund failed. null on success. Optional
transactionIdguidIdentifier of the refund transaction. Set on success; null on failure.
statusCodestringAlways null on this endpoint. It is not the HTTP status code. Do not use it.
errorsarray of objectsError details for a failed refund; empty on success. Each item is serialized positionally: item1 message, item2 key, item3 error code, item4 friendly flag (boolean). An unexpected failure carries REFUND_FAILED.
customPropertiesobjectAdditional custom properties for the API request Optional

HTTP status codes

❗️

A failed refund normally returns HTTP 200

A refund the processor declined, and a refund that failed for an unexpected reason, both return HTTP 200 with status: FAILED and success: false, so that you can inspect the body and retry if appropriate. Check success and status on every 200; do not treat the HTTP code alone as the outcome.

SituationHTTPBody
Refund succeeded200status: SUCCESS, success: true
Processor declined or returned a failure200status: FAILED, success: false, responseCode, responseMessage, errors
Unexpected error (for example a gateway or transport failure)200status: FAILED, success: false, errors contains REFUND_FAILED
Validation error (for example the refund amount exceeds the remaining refundable balance, or neither amount nor amountToRefund was sent)400Validation problem details — see Error Handling
Invalid or expired bearer token401—
Error raised before the refund ran400Refund envelope with status: FAILED
No order matches {id}, or it belongs to another merchant404Problem details
Same idempotencyKey reused with a different body, or the first request is still running409Problem details — see Idempotency §3.4
An earlier attempt with the same idempotencyKey failed with an unknown outcome500Problem details
Duplicate-request check temporarily unavailable503Problem details, with a Retry-After header; safe to retry

❕

Updating the status order

After receiving a successful response from /refund, you need to update this order in your CRM with the corresponding status.




Payload examples

Request


curl --request POST \
     --url https://api-sandbox.flexfactor.io/v1/orders/{id}/refund \ //insert the orderId returned by the response to /evaluate
     --header 'Authorization: Bearer abc123def456' \ //replace with bearer returned by /oauth2
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "amount": 9900, //in cents — equal to the captured amount
  "refundMessage": "Customer request"
}
curl --request POST \
     --url https://api-sandbox.flexfactor.io/v1/orders/{id}/refund \ //insert the orderId returned by the response to /evaluate
     --header 'Authorization: Bearer abc123def456' \ //replace with bearer returned by /oauth2
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "amount": 2500, //in cents — lower than the captured amount
  "refundMessage": "Partial return",
  "idempotencyKey": "{generate_a_GUID_and_reuse_it_on_retries}"
}


Response


{
   "status": "SUCCESS",
   "success": true,
   "result": null,
   "responseCode": null,
   "responseMessage": null,
   "transactionId": "{{$guid}}",
   "statusCode": null,
   "errors": [],
   "customProperties": {}
}
{
   "status": "FAILED",
   "success": false,
   "result": null,
   "responseCode": "{processor response code}",
   "responseMessage": "{processor response message}",
   "transactionId": null,
   "statusCode": null,
   "errors": [
      { "item1": "{failure reason}", "item2": "{error key}", "item3": "{error code}", "item4": true }
   ],
   "customProperties": {}
}

Did this page help you?