Handling Refunds
Refund logic shouldn't be complicated, and this guide discusses how to handle the various scenarios you may encounter when issuing returns, credit memos, and whole or partial refunds.
If reporting sale transactions to Zamp creates liability owed by a business to a taxable jurisdiction, reporting refund transactions to Zamp reduces liability owed by a business to a taxable jursidiction. Accurately reducing liability ensures a business does not remit more than they have to.
We recommend understanding the transaction object before diving into the information in this guide.
Refund and sale transaction relationships
When creating a refund transaction, the original sale transaction must exist in Zamp. The sale transaction must have its original "id"
represented by a "parentId"
when creating the refund transaction. This signals to our software that the transaction is a refund.
The "id"
and "name"
properties of the refund transaction can be anything you'd like. We recommend prepending with a value that accommodates a business's practices. For example, "CM-*"
for credit memos or "REF-*"
for refunds.
If you anticipate multiple refunds for a single sale transaction, you could append increments to the end. For example, "REF-*-01"
, "REF-*-02"
, etc.
The "parentId" property on /transactions
- Name
parentId
- Type
- string (optional, required for refunds)
The original
"id"
property of the sale transaction.
Example: refund transaction "id" and "parentId"
{
"id": "REF-123",
"name": "REF-123",
"parentId": "123",
// ...
}
Handling refund transaction dates
It's important to not manipulate the original sale transaction when creating a refund transaction. For example, issuing a PUT /transactions/[:id]
request might seem sensible from a reporting perspective. However, the sales tax collected from the sale transaction may have already been filed and remitted to a taxable jurisdiction. Updating the sale transaction may fail to credit a business.
To absolutely ensure a refund transaction credits a business from remitted liability that is ultimately being given back to a customer, the "transactedAt"
property in a refund transaction should reflect the time that the refund transaction is created. This will reduce the amount of liability in the current filing period.
Calculating sales tax for refunds
With the exception of whole-transaction refunds, calculating sales tax for refund transactions is just as important as calculating sales tax for the original sale. Your integration would want to calculate for refunds so you know how much sales tax should be returned to a customer.
Whole-transaction refund calculations
When a transaction is intended to be a complete refund, it's not always necessary to issue a calculation. However, if you're integrating Zamp's API into a third-party system, like an ERP or accounting software, it may be a good practice to call /calculations
.
Partial-transaction refund calculations
Partial-transaction refunds are those that meet one of the following conditions:
- One or more
"lineItems[*].quantity"
changes by a lesser quantity than the original transaction - One or more
"lineItems[*]"
are canceled entirely, but not all - A
"discount"
at the transaction level -or-"lineItems[*].discount
is applied to the order after the original transaction has already been sent to Zamp.- This same condition applies when a credit is issued to a customer (i.e., savings on a future product purchase, subscription, or service).
The easiest way to achieve a sales tax calculation for partial-transaction refunds is by following the logic pattern below. This effectively gathers the liability from Zamp off of the original sale transaction and subtracts the amount of sales tax from what was reported.
- Issue a
GET
request against the original transaction. i.e.,GET /transactions/[:id]
- Using the JSON response object, subtract
"taxCollected"
from the"total"
- Retain the
"taxCollected"
amount, but remove the"taxCollected"
property from the JSON response object. This property is not used in/calculations
requests. - Make any necessary changes to the JSON response object. For example, increasing a
"discount"
or"lineItems[*].discount"
amount, removing the returned"lineItems[*]"
, or changing the"lineItems[*].quantity"
properties.When computating changes for
"discount"
,"lineItems[*].discount"
,"lineItems"
or their quantities, ensure you reprocess the transaction-level"subtotal"
and"total"
. - Issue a
POST
request to/calculations
using the transformed transaction data. Retain the/calculations
request body, since this will be used to create the refund transaction later. - Subtract the calculation request's
"taxDue"
response from the retained"taxCollected"
.
The result of the difference between the calculation's response "taxDue"
and the retained "taxCollected"
of the original sale transaction is the amount of sales tax owed to a customer.
Reporting refund transactions
Once you know how much sales tax should be refunded to a customer, reporting the refund transaction to Zamp is easy. The only differences between reporting a sale transaction and reporting a refund transaction are the following:
- Giving a refund transaction accurate
"id"
,"name"
, and"parentId"
properties - Providing
"transactedAt"
as the date of the refund - not the date of the sale (unless it's indeed the same) - Accurately transforming positive floating-point numbers to negative floating-point numbers
The last key difference is the focus of what hasn't been discussed yet - transforming positive floating-point numbers to negative floating-point numbers.
For ease of comparison, consider the whole-transaction refund below. The sale transaction POST /transactions
request body is on the left, while the POST /transactions
refund transaction is on the right.
Sale Transaction Request
{
"id": "123",
"name": "INV-123",
"transactedAt": "2023-07-01T00:00:00.000Z",
"isResale": false,
"discount": 2,
"subtotal": 18,
"shippingHandling": 5,
"taxCollected": 2.25,
"total": 25.25,
"shipToAddress": {
"line1": "120 SW 10TH AVE",
"line2": null,
"state": "KS",
"city": "TOPEKA",
"zip": "66612"
},
"lineItems": [
{
"id": "LI-123",
"amount": 10,
"quantity": 2,
"discount": 0,
"shippingHandling": 0,
"productName": "The Ultimate Sampler",
"productSku": "SAMPLER-100",
"productTaxCode": "R_TPP_FOOD-BEVERAGE_HOME-CONSUMPTION"
}
]
}
Refund Transaction Request
{
"id": "REF-123-01",
"name": "REF-INV-123-01",
"parentId": "123",
"transactedAt": "2024-01-01T00:00:00.000Z",
"isResale": false,
"discount": -2,
"subtotal": -18,
"shippingHandling": -5,
"taxCollected": -2.25,
"total": -25.25,
"shipToAddress": {
"line1": "120 SW 10TH AVE",
"line2": null,
"state": "KS",
"city": "TOPEKA",
"zip": "66612"
},
"lineItems": [
{
"id": "LI-123",
"amount": 10,
"quantity": -2,
"discount": 0,
"shippingHandling": 0,
"productName": "The Ultimate Sampler",
"productSku": "SAMPLER-100",
"productTaxCode": "R_TPP_FOOD-BEVERAGE_HOME-CONSUMPTION"
}
]
}
Notice the only floating-point property that has not changed: "lineItems[*].amount"
. The amount of a product being refunded should never be a negative, but instead, negate the quantity.
Never pass a "lineItems[*].amount"
property that is a negative. Indicate a
credit by reducing its quantity from the original sale.
Reporting refund transactions and negatives can be tricky in any accounting system, but we try to uncomplicate the process by using the same endpoints and properties you would use for processing sale transactions. If you run into any questions or issues, we're here to help! You can reach out to us at support@zamp.com.