Big Commerce Order Refund
Summary of Changes: (The purpose of this table is to keep traceability and Product team to highlight the things that were changed into the scope, based on comments or discussions)
Date | Version | Name | Applied changes |
---|---|---|---|
09.08.2023 | v1.0 | Bogomil Pavlov | First Publish |
01.12.2023 | v1.1 | Bogomil Pavlov | Gift Wrapping Handle |
11.01.2024 | v1.2 | Bogomil Pavlov | Mapping Changes |
29.01.2024 | v1.3 | Beatris Bunova | Dev Notes added (processing refunds for order 1 by 1) |
05.02.2024 | v1.4 | Hristiyan Georgiev | Added logic for the error handling |
15.02.2024 | v1.5 | Bogomil Pavlov | Pre and Post Shipment Refund flow |
03.04.2024 | v1.6 | Bogomil Pavlov | VAT Rounding logic |
06.08.2024 | v1.7 | Bogomil Pavlov | Introduce Refund Quotes |
22.08.2024 | v1.8 | Bogomil Pavov | Refund Tax Control |
25.11.2024 | v1.9 | Bogomil Pavov | Online/Offline Refund Control |
<v1.7>
Order Refund Quote
Before each refund we are making we want to obtain the provider_id and validate the amount and in order to do so we are doing additional request. If everything is fine with details we will proceed and make the refund otherwise we will return an error on this step first. We will be sending all the relevant details for the refund and we are following the same logic as for the actual order refund request and basically we are validating the whole refund we already have for the order. The trigger is to have a “pending” refund and we want both request to work together - obtain the provider_id, validate the refund amounts and do the refund. The first step is the refund quote:
API call: POST [https://api.bigcommerce.com/stores/{hash}/v3/orders/{order_marketplace_id}/payment_actions/refund_quotes](https://api.bigcommerce.com/stores/i3kt5xjesl/v3/orders/16438/payment_actions/refund_quotes)
API doc: https://developer.bigcommerce.com/docs/rest-management/transactions/payment-actions#create-a-refund-quote
Body:
{
"items": [
{
"item_type": "PRODUCT",
"item_id": 8292,
"amount": 27.5000,
"quantity": 1,
"reason": "Customer requested refund"
},
{
"item_type":"GIFT_WRAPPING",
"item_id":93,
"quantity":4,
"amount":null,
"reason":""
},
{
"item_type": "SHIPPING",
"item_id": 7,
"quantity": null,
"amount": 10,
"reason": "Customer requested refund"
}
]
}
Sample Request:
POST [https://api.bigcommerce.com/stores/i3kt5xjesl/v3/orders/16438/payment_actions/refund_quotes](https://api.bigcommerce.com/stores/i3kt5xjesl/v3/orders/16438/payment_actions/refund_quotes)
Body:
{
"items": [
{
"item_type": "PRODUCT",
"item_id": 8292,
"amount": 27.5000,
"quantity": 1,
"reason": "Customer requested refund"
}
]
}
Mapping: In the actual url we want to use the store hash and the Order > Marketplace Order Id
Big Commerce Field | Hemi Field | Required | Comment | |
---|---|---|---|---|
items |
||||
item_type |
Yes | Possible values GIFT_WRAPPING , PRODUCT , SHIPPING |
||
Based what we have for the order in the refund. | ||||
item_id |
Product In Order > Item Order Line ID |
OR
Order > Shipping Address ID | Yes | If we have "item_type": "GIFT_WRAPPING"
or “PRODUCT”
we need to use the Product In Order > Item Order Line ID if we have “SHIPPING”
we want to use the Order > Shipping Address ID |
| | amount
| Order Refund Row > Amount | Yes/No | This is required only if we have "item_type": "SHIPPING"
<v1.8>Based on the Account BigCommerce > Refund with Tax
we will decide if we have to refund the shipping amount with or without VAT.
</1.8>
Please note we are allowed to do only full shipping refunds. |
| | quantity
| | Yes/No | Need to calculate this based on the Amount in Order refund Row and get the correct quantity and not required for "item_type":“SHIPPING”
|
| | reason
| N/A | No | |
Sample Response:
{
"data": {
"total_refund_amount": 27.5,
"total_refund_tax_amount": 4.58,
"rounding": 0,
"adjustment": 0,
"is_tax_included": true,
"order_level_refund_amount": 0,
"refund_methods": [
[
{
"provider_id": "paypalcommercecreditcards",
"provider_description": "PayPal (PayPal Credit Cards)",
"amount": 27.5,
"offline": false,
"offline_provider": false,
"offline_reason": ""
}
],
[
{
"provider_id": "custom",
"provider_description": "Custom",
"amount": 27.5,
"offline": true,
"offline_provider": true,
"offline_reason": "This is an offline payment provider."
}
]
]
},
"meta": {}
}
Response Mapping:
Big Commerce Field | Hemi Field | Required | Comment | ||
---|---|---|---|---|---|
data | |||||
total_refund_amount | N/A | No | |||
total_refund_tax_amount | N/A | No | |||
rounding | N/A | No | |||
adjustment | N/A | No | |||
is_tax_included | N/A | No | |||
order_level_refund_amount | N/A | No | |||
refund_methods | Please note we may have more than one refund methods and we want to use always the first one received. | ||||
provider_id | Yes | Used for the next request as provider_id in order to make the actual refund. |
|||
provider_description | N/A | No | |||
amount | No | Used to validate the refund total amount Order Payment > Total Amount. If there are discrepancies between the total amount of the quote and the refund we want to return an error stating “The amount you are trying to refund is incorrect. The available total amount for refund is {amount}” | |||
This way the use will know what is available for refund. | |||||
offline | N/A | No | |||
offline_provider | N/A | No | |||
offline_reason | N/A | No |
</v1.7>
There are a couple types of refunds and depends what we are refunding different payloads will be generated however the refund requests are per order. <v1.5>The refunds are available for all order statuses and we need to handle both Pre and Post shipment orders.</v1.5>
We want all triggers, status, error handling, etc. to be as per the Refunds send general logic
API Call: POST https://api.bigcommerce.com/stores/{store_hash}/v3/orders/{order_id}/payment_actions/refunds
Docs: https://developer.bigcommerce.com/docs/rest-management/transactions/payment-actions#create-a-refund
Shipping Refunds
Big Commerce support only full shipping cost refund
Sample Shipping Refund Request:
{
"items": [
{
"item_type": "SHIPPING",
"item_id": 3,
"amount": 10,
"reason": "Customer requested refund"
}
],
"payments": [
{
"provider_id": "cod",
"amount": 12,
"offline": true
}
]
}
Mapping:
Big Commerce Field | Hemi Field | Required | Comment | |
---|---|---|---|---|
items |
||||
item_type |
“SHIPPING “ |
Yes | Hard coded as SHIPPING |
|
item_id |
Order > Shipping Address ID | Yes | ||
amount |
Order Refund Row > Amount | Yes | <v1.8>Based on the Account BigCommerce > Refund with Tax we will decide if we have to refund the shipping amount with or without VAT. |
</1.8>
Please note we are allowed to do only full shipping refunds. |
| | reason
| Order Payment > Reason | No | |
| payments
| | | | |
| | provider_id
| | Yes | <v1.7>Obtained from Get Refund Quotes.
Before each refund we will be checking what is the provider id because different payment providers have different ids and we need to be flexible</v1.7> |
| | amount
| Order Payment > Total Amount | Yes | This is the shipping cost amount with VAT as it is taken from the Hemi Refund Row |
| | offline
| <v1.9>Account Big Commerce > Offline Refund | Yes | If we have Account Big Commerce > Offline Refund = Yes we push this as “true
” if we have Account Big Commerce > Offline Refund = No we push this as “false
”
By default we want to have Account Big Commerce > Offline Refund = Yes</v1.9> |
Sample Response:Status 200 OK
{
"data": {
"id": 15,
"order_id": 114,
"user_id": 0,
"created": "2023-08-23T20:52:04+00:00",
"reason": "",
"total_amount": 210.35,
"total_tax": 35.058,
"uses_merchant_override_values": false,
"payments": [
{
"id": null,
"provider_id": "cod",
"amount": 210.35,
"offline": true,
"is_declined": false,
"declined_message": ""
}
],
"items": [
{
"item_type": "SHIPPING",
"item_id": 18,
"amount": 12,
"requested_amount": null,
"reason": "Customer requested refund"
}
]
},
"meta": {}
}
From the success response we want to store the id
as an Order Payment > Transaction Id
Product Full Refund
Big Commerce supports partial and full product refunds however we will be supporting only FULL LINE refunds and if we try to refund partial line we want to return internal error. This means if we have an order with single product with x2 quantity we allow to refund x1 quantity but we want to stop all partial line refunds and be able to refund partial amount of the quanitty. For full product or products refund we must have the following structure
Sample Product Full Refund Request:
{
"items": [
{
"item_type": "PRODUCT",
"item_id": 3,
"quantity": 1,
"reason": "Customer requested refund"
},
{
"item_type": "PRODUCT",
"item_id": 4,
"quantity": 2,
"reason": "Customer requested refund"
}
],
"payments": [
{
"provider_id": "cod",
"amount": 49.50,
"offline": true
}
]
}
Mapping:
Big Commerce Field | Hemi Field | Required | Comment | |
---|---|---|---|---|
items |
||||
item_type |
“PRODUCT “ |
Yes | Hard coded as PRODUCT |
|
item_id |
Product In Order > Item Order Line ID | Yes | ||
quantity |
Order Refund Row > Amount | Yes | Need to calculate this based on the Amount in Order refund Row | |
reason |
Order Payment > Reason | No | ||
payments |
||||
provider_id |
Yes | <v1.7>Obtained from Get Refund Quotes. | ||
Before each refund we will be checking what is the provider id because different payment providers have different ids and we need to be flexible</v1.7> | ||||
amount |
Order Payment > Total Amount | Yes | This is the product total amount with tax as it is taken from the Hemi Refund Row | |
offline |
<v1.9>Account Big Commerce > Offline Refund | Yes | If we have Account Big Commerce > Offline Refund = Yes we push this as “true ” if we have Account Big Commerce > Offline Refund = No we push this as “false ” |
|
By default we want to have Account Big Commerce > Online Offline = Yes </v1.9> |
Sample Response: Status 200 OK
{
"data": {
"id": 16,
"order_id": 114,
"user_id": 0,
"created": "2023-08-23T20:52:04+00:00",
"reason": "",
"total_amount": 210.35,
"total_tax": 35.058,
"uses_merchant_override_values": false,
"payments": [
{
"id": null,
"provider_id": "cod",
"amount": 210.35,
"offline": true,
"is_declined": false,
"declined_message": ""
}
],
"items": [
{
"item_type": "PRODUCT",
"item_id": 18,
"quantity": 1,
"requested_amount": null,
"reason": "Customer requested refund"
}
]
},
"meta": {}
}
From the success response we want to store the id
as a Order Payment > Transaction Id
Product and Shipping Cost Refund
We can combine different types of refunds in single request which allows us to make product or products and shipping cost refund in the same time
Sample Order Full + Shipping Cost Request:
{
"items": [
{
"item_type": "PRODUCT",
"item_id": 3,
"quantity": 1,
"reason": "Customer requested refund"
},
{
"item_type": "SHIPPING",
"item_id": 3,
"amount": 10,
"reason": "Customer requested refund"
}
],
"payments": [
{
"provider_id": "cod",
"amount": 59.50,
"offline": true
}
]
}
Mapping:
Big Commerce Field | Hemi Field | Required | Comment | |
---|---|---|---|---|
items |
||||
item_type |
Yes | Possible values PRODUCT , SHIPPING, GIFT_WRAPPING |
||
item_id |
Product In Order > Item Order Line ID |
OR
Order > Shipping Address ID | Yes | If we have "item_type": "PRODUCT"
we need to use the Product In Order > Item Order Line ID
If we have "item_type": "SHIPPING"
we need to use the Order > Shipping Address ID |
| | amount
| Order Refund Row > Amount | Yes/No | Please note depends on what type of refund we are making this field is not required.
NOT required only for "item_type": "PRODUCT"
<v1.8>Based on the Account BigCommerce > Refund with Tax
we will decide if we have to refund the shipping amount with or without VAT.
</1.8>
Please note this amount must be without the value in Hemi which is stored as the full amount with Tax. For this we will have to use Orders
> Total Shipping Marketplace VAT
to calculate the correct amount.Please note we are allowed to do only full shipping refunds. |
| | quantity
| | Yes/No | Please note depends on what type of refund we are making this field is not required.
NOT required for "item_type": "SHIPPING"
Need to calculate this based on the Amount in Order refund Row |
| | reason
| Order Payment > Reason | No | |
| payments
| | | | |
| | provider_id
| | Yes | <v1.7>Obtained from Get Refund Quotes.
Before each refund we will be checking what is the provider id because different payment providers have different ids and we need to be flexible</v1.7> |
| | amount
| Order Payment > Total Amount | Yes | This is the amount including tax/vat from the refund |
| | offline
| “true
“ | Yes | Hardcoded as “true
“ |
Sample Response: Status 200 OK
{
"data": {
"id": 6,
"order_id": 106,
"user_id": 0,
"created": "2023-08-09T11:26:08+00:00",
"reason": "",
"total_amount": 71.4,
"total_tax": 11.9,
"uses_merchant_override_values": false,
"payments": [
{
"id": null,
"provider_id": "cod",
"amount": 71.4,
"offline": true,
"is_declined": false,
"declined_message": ""
}
],
"items": [
{
"item_type": "PRODUCT",
"item_id": 8,
"quantity": 1,
"requested_amount": null,
"reason": "Customer requested refund"
},
{
"item_type": "SHIPPING",
"item_id": 7,
"quantity": null,
"requested_amount": 10,
"reason": "Customer requested refund"
}
]
},
"meta": {}
}
From the success response we want to store the id
as a Order Payment > Transaction Id
<v1.1>Gift Wrap Cost Refund
We will have to track if we have a Gift Wrap Costs in the refund and send it as follows
Sample Gift Wrap Cost Request:
{
"items": [
{
"item_type":"GIFT_WRAPPING",
"item_id":93,
"quantity":4,
"requested_amount":null,
"reason":""
}
"payments": [
{
"provider_id": "cod",
"amount": 59.50,
"offline": true
}
]
}
Mapping:
Big Commerce Field | Hemi Field | Required | Comment | |
---|---|---|---|---|
items |
||||
item_type |
Yes | Possible values GIFT_WRAPPING |
||
item_id |
Product In Order > Item Order Line ID | Yes | If we have "item_type": "GIFT_WRAPPING" we need to use the Product In Order > Item Order Line ID |
|
requested_amount |
N/A | No | ||
quantity |
Yes | Need to calculate this based on the Amount in Order refund Row and get the correct quantity | ||
reason |
Order Payment > Reason | No | ||
payments |
||||
provider_id |
Yes | <v1.7>Obtained from Get Refund Quotes. | ||
Before each refund we will be checking what is the provider id because different payment providers have different ids and we need to be flexible</v1.7> | ||||
amount |
Order Payment > Total Amount | Yes | This is the amount including tax/vat from the refund | |
offline |
“true “ |
Yes | Hardcoded as “true “ |
Sample Response: Status 200 OK
{
"data": {
"id": 6,
"order_id": 139,
"user_id": 0,
"created": "2023-08-09T11:26:08+00:00",
"reason": "",
"total_amount": 71.4,
"total_tax": 11.9,
"uses_merchant_override_values": false,
"payments": [
{
"id": null,
"provider_id": "cod",
"amount": 71.4,
"offline": true,
"is_declined": false,
"declined_message": ""
}
],
"items": [
{
"item_type": "GIFT_WRAPPING",
"item_id": 93,
"quantity": 1,
"requested_amount": null,
"reason": "Customer requested refund"
}
]
},
"meta": {}
}
From the success response we want to store the id
as a Order Payment > Transaction Id
</v1.1>
General Error Responses
Sample Error Response #1:
{
"status": 422,
"title": "Order with ID 106 can not be refunded.",
"type": "https://developer.bigcommerce.com/api-docs/getting-started/api-status-codes"
}
Sample Error Response #2:
{
"status": 400,
"title": "Input is invalid",
"type": "https://developer.bigcommerce.com/api-docs/getting-started/api-status-codes",
"detail": "Syntax error"
}
Sample Error Response #3:
{
"status": 422,
"title": "Invalid field(s): items",
"type": "https://developer.bigcommerce.com/api-docs/getting-started/api-status-codes",
"errors": {
"items": "item_type must be in `{ \"PRODUCT\", \"GIFT_WRAPPING\", \"SHIPPING\", \"HANDLING\", \"ORDER\" }`"
}
}
Sample Error Response #4:
{
"status": 422,
"title": "Order with ID 106 can not be refunded.",
"type": "https://developer.bigcommerce.com/api-docs/getting-started/api-status-codes"
}
We want to store the title as error message in Order Error Table.<v1.4> Since there are some errors which will not show in the title, we also want to make a check and if we have a node errors
to store the error from there, if there is no errors
node we store the title as error message.<v1.4>
<v1.3> Dev notes: Because of the way we are calculating refunded VAT for the order in order to have the correct amount to deduct from the last shipping refund (please see the table above for more info on this part), we decided to load only one refund for a given order on a cron run. This is to ensure the calculations are correct and also to save some time for development and avoid code complications. This means that if an order has two refunds on “Pending” status, it will take two runs to process them. </v1.3> <v1.6> Please note the way the roundings need to be calculated is:
</v1.6>