Vouchers
Introduction
Vouchers are a way to apply discounts to orders. They can be used to reduce the cost of a checkout. Vouchers can be applied to the entire order or to specific products. They can also be limited to a minimum quantity of products in the checkout. Vouchers and promotions can be used together.
The vouchers on draft orders are applicable since v3.19.
Types
There are three types of vouchers:
ENTIRE_ORDER
: the discount is applied on the total value of checkoutSPECIFIC_PRODUCT
: the discount is applied on the total value of the productSHIPPING
: the discount is applied on the shipping price
We can also distinguish vouchers by their value type:
FIXED
: reduces the price by a specified value.PERCENTAGE
: reduces the price by a specified percentage value.
The voucher can be applied to all checkout products, or only to specified ones.
There is also an option to apply the discount only to the cheapest eligible product. If the voucher specifies certain products, the discount will be applied only to the cheapest item included in the discount. If the voucher applies to all products, the discount will be applied only to the cheapest item overall.
To apply the voucher on checkout use checkoutAddPromoCode
mutation. The discount will be visible both in the line prices and in the checkout.discount
field.
To apply the voucher on draft order use draftOrderCreate or draftOrderUpdate
and pass voucherCode
as an argument.
Usage limits
There are multiple ways to limit a voucher's usage, but default voucher codes can be used without any limits. To change that usageLimit
can be set
to limit the number of times a voucher can be used. The voucher will be unavailable for further use when the limit is reached.
If the voucher has multiple codes, the limit is calculated as the sum of all codes usage.
Another way to limit the usage of a voucher is to set the applyOncePerCustomer
field to true
. In this case, the voucher code will be
available for use only once per customer.
The voucher can be also set as single-use, when singleUse
flag is set to true
. In this case, each code from the voucher can be used only once and then the code will be deactivated after use.
This flag can be updated on existing voucher only when no code has been used yet.
Those limitation options can be combined. For example, if usageLimit
is set to 10
and applyOncePerCustomer
is set to true
, the voucher can be used by the first 10 users.
Shuffling voucher settings might result in a mismatch between the voucher.used
field and the number of orders utilizing the voucher.
Saleor operates on the general assumption that updating the voucher settings should not affect existing orders if possible.
Voucher usage in draft orders
Saleor is able to calculate voucher usage in draft orders. To turn on such a behavior, includeDraftOrderInVoucherUsage
flag for the given Channel
must be set to true
. Use channelUpdate mutation to switch the flag.
When the includeDraftOrderInVoucherUsage
flag is changed from false
to true
, vouchers will be disconnected from all draft orders.
When the includeDraftOrderInVoucherUsage
flag is changed from true
to false
, all vouchers will be released. It means:
- multiple-use voucher codes will reduce their usage by one (
VoucherCode.used
field) - single-use voucher codes will become active again (
VoucherCode.isActive
field) - in case of
applyOncePerCustomer
setting on, the usage will be dissociated from users (VoucherCustomer
entry)
Voucher code will also be released, after the draft order deletion.
When importing a draft order using bulkOrderCreate mutation, the voucher is not validated, therefore code usage will not be counted.
Permissions
Managing promotions is available for users and apps with the MANAGE_DISCOUNTS
permission.
Create and update a voucher
Creating voucher
To create a voucher use voucherCreate mutation.
API allows to create voucher with multiple codes. To do that use the addCodes
field to send a list of codes.
Input:
{
"input": {
"name": "NewVoucher",
"type": "ENTIRE_ORDER",
"addCodes": ["code1", "code2", "code3"],
"discountValueType": "FIXED",
"minCheckoutItemsQuantity": 10,
"applyOncePerOrder": true,
"applyOncePerCustomer": true,
"singleUse": false,
"usageLimit": 10
}
}
Mutation:
mutation voucherCreate($input: VoucherInput!) {
voucherCreate(input: $input) {
errors {
field
code
message
voucherCodes
}
voucher {
id
type
minCheckoutItemsQuantity
name
usageLimit
used
codes(first: 10) {
edges {
node {
code
used
}
}
}
discountValueType
startDate
endDate
applyOncePerOrder
applyOncePerCustomer
singleUse
}
}
}
Here is the response:
{
"data": {
"voucherCreate": {
"errors": [],
"voucher": {
"id": "Vm91Y2hlcjo0",
"type": "ENTIRE_ORDER",
"minCheckoutItemsQuantity": 10,
"name": "NewVoucher",
"usageLimit": 10,
"used": 0,
"codes": {
"edges": [
{
"node": {
"code": "code3",
"used": 0
}
},
{
"node": {
"code": "code2",
"used": 0
}
},
{
"node": {
"code": "code1",
"used": 0
}
}
]
},
"discountValueType": "FIXED",
"startDate": "2023-10-23T08:20:12.215018+00:00",
"endDate": null,
"applyOncePerOrder": true,
"applyOncePerCustomer": true,
"singleUse": false
}
}
}
}
Updating voucher
To update a voucher use voucherUpdate mutation.
Using this mutation voucher codes can be only added. To remove codes use voucherDelete mutation.
Input:
{
"input": {
"addCodes": ["NewCode"]
}
}
Mutation:
mutation voucherUpdate($input: VoucherInput!) {
voucherUpdate(input: $input) {
errors {
field
code
message
voucherCodes
}
voucher {
id
type
minCheckoutItemsQuantity
name
usageLimit
used
codes(first: 10) {
edges {
node {
code
used
}
}
}
discountValueType
startDate
endDate
applyOncePerOrder
applyOncePerCustomer
singleUse
}
}
}
Here is the response:
{
"data": {
"voucherUpdate": {
"errors": [],
"voucher": {
"id": "Vm91Y2hlcjo0",
"type": "ENTIRE_ORDER",
"minCheckoutItemsQuantity": 10,
"name": "NewVoucher",
"usageLimit": 10,
"used": 0,
"codes": {
"edges": [
{
"node": {
"code": "NewCode",
"used": 0
}
},
{
"node": {
"code": "code3",
"used": 0
}
},
{
"node": {
"code": "code2",
"used": 0
}
},
{
"node": {
"code": "code1",
"used": 0
}
}
]
},
"discountValueType": "FIXED",
"startDate": "2023-10-23T08:20:12.215018+00:00",
"endDate": null,
"applyOncePerOrder": true,
"applyOncePerCustomer": true,
"singleUse": false
}
}
}
}
Applying a voucher
Applying the entire order voucher
In the example below, the entire order voucher with a fixed discount of $5 is applied at checkout. The order consists of two lines: the first for $4 and the second for $45.
mutation {
checkoutAddPromoCode(
token: 7303902b-dda8-4357-95a0-64b83b59e2e2, promoCode: "DISCOUNT"
) {
errors {
field
message
code
}
checkout {
id
token
voucherCode
discountName
discount {
amount
}
subtotalPrice {
tax {
amount
}
gross {
amount
}
net {
amount
}
}
lines {
quantity
totalPrice {
net {
amount
}
gross {
amount
}
}
undiscountedTotalPrice {
amount
}
unitPrice {
net {
amount
}
gross {
amount
}
}
undiscountedUnitPrice {
amount
}
}
}
}
}
Here is the response:
{
"data": {
"checkoutAddPromoCode": {
"errors": [],
"checkout": {
"id": "Q2hlY2tvdXQ6OTNmMWQxZjItMjBjNC00ZWMyLTkwYzgtOThjYmEzY2YyNTU1",
"token": "93f1d1f2-20c4-4ec2-90c8-98cba3cf2555",
"voucherCode": "DISCOUNT",
"discountName": "Big order discount",
"discount": {
"amount": 5.0
},
"subtotalPrice": {
"tax": {
"amount": 0.0
},
"gross": {
"amount": 44.0
},
"net": {
"amount": 44.0
}
},
"lines": [
{
"quantity": 1,
"totalPrice": {
"net": {
"amount": 3.59
},
"gross": {
"amount": 3.59
}
},
"undiscountedTotalPrice": {
"amount": 4.0
},
"unitPrice": {
"net": {
"amount": 3.59
},
"gross": {
"amount": 3.59
}
},
"undiscountedUnitPrice": {
"amount": 4.0
}
},
{
"quantity": 1,
"totalPrice": {
"net": {
"amount": 40.41
},
"gross": {
"amount": 40.41
}
},
"undiscountedTotalPrice": {
"amount": 45.0
},
"unitPrice": {
"net": {
"amount": 40.41
},
"gross": {
"amount": 40.41
}
},
"undiscountedUnitPrice": {
"amount": 45.0
}
}
]
}
}
}
}
The discount is visible on both the checkout.discount
field and the prices in checkout.lines
.
In the first line, the totalPrice
is $0.41 less than the undiscountedTotalPrice
,
while the difference in the second line is $4.59.
The checkout.discount
is the sum of the differences between totalPrice
and undiscountedTotalPrice
of the lines.
If the user applied a fixed-amount order voucher during checkout, and the order contains multiple lines, the discount will be distributed evenly in proportion to the total price of each line.
Applying the once-per-order entire order voucher
If a voucher with the applyOncePerOrder
flag set to True
is used in a similar scenario,
the discount will only apply to the cheapest eligible product. In this checkout,
the cheapest eligible product is priced at $4.
Therefore, the discount will be $4 and will only appear on one line.
Refer to the response from running the same mutation as before.
The checkout.discount
is 4.0. The cheapest line's totalPrice
is 0.0,
and the undiscountedTotalPrice
is 4.0.
{
"data": {
"checkoutAddPromoCode": {
"errors": [],
"checkout": {
"id": "Q2hlY2tvdXQ6OTNmMWQxZjItMjBjNC00ZWMyLTkwYzgtOThjYmEzY2YyNTU1",
"token": "93f1d1f2-20c4-4ec2-90c8-98cba3cf2555",
"voucherCode": "DISCOUNT",
"discountName": "Big order discount",
"discount": {
"amount": 4.0
},
"subtotalPrice": {
"tax": {
"amount": 0.0
},
"gross": {
"amount": 45.0
},
"net": {
"amount": 45.0
}
},
"lines": [
{
"quantity": 1,
"totalPrice": {
"net": {
"amount": 0.0
},
"gross": {
"amount": 0.0
}
},
"undiscountedTotalPrice": {
"amount": 4.0
},
"unitPrice": {
"net": {
"amount": 0.0
},
"gross": {
"amount": 0.0
}
},
"undiscountedUnitPrice": {
"amount": 4.0
}
},
{
"quantity": 1,
"totalPrice": {
"net": {
"amount": 45.0
},
"gross": {
"amount": 45.0
}
},
"undiscountedTotalPrice": {
"amount": 45.0
},
"unitPrice": {
"net": {
"amount": 45.0
},
"gross": {
"amount": 45.0
}
},
"undiscountedUnitPrice": {
"amount": 45.0
}
}
]
}
}
}
Applying the specific product voucher
In the following example, a 10% voucher for a specific product is applied during checkout.
The discount applies to the first two lines, one for $45 and the other for $20, but not to the third line for $1.99.
The response of running the checkoutAddPromoCode
mutation with the voucher code for this discount is shown below:
{
"data": {
"checkoutAddPromoCode": {
"errors": [],
"checkout": {
"id": "Q2hlY2tvdXQ6YWJlZTQzNTEtMGZjMS00MWYzLTk1YzEtMTIyMTc4NWMwYzY2",
"token": "abee4351-0fc1-41f3-95c1-1221785c0c66",
"voucherCode": "SPECIFIC PRODUCT",
"discountName": null,
"discount": {
"amount": 6.5
},
"subtotalPrice": {
"tax": {
"amount": 0.0
},
"gross": {
"amount": 60.49
},
"net": {
"amount": 60.49
}
},
"lines": [
{
"quantity": 1,
"totalPrice": {
"net": {
"amount": 40.5
},
"gross": {
"amount": 40.5
}
},
"undiscountedTotalPrice": {
"amount": 45.0
},
"unitPrice": {
"net": {
"amount": 40.5
},
"gross": {
"amount": 40.5
}
},
"undiscountedUnitPrice": {
"amount": 45.0
}
},
{
"quantity": 1,
"totalPrice": {
"net": {
"amount": 18.0
},
"gross": {
"amount": 18.0
}
},
"undiscountedTotalPrice": {
"amount": 20.0
},
"unitPrice": {
"net": {
"amount": 18.0
},
"gross": {
"amount": 18.0
}
},
"undiscountedUnitPrice": {
"amount": 20.0
}
},
{
"quantity": 1,
"totalPrice": {
"net": {
"amount": 1.99
},
"gross": {
"amount": 1.99
}
},
"undiscountedTotalPrice": {
"amount": 1.99
},
"unitPrice": {
"net": {
"amount": 1.99
},
"gross": {
"amount": 1.99
}
},
"undiscountedUnitPrice": {
"amount": 1.99
}
}
]
}
}
}
}
As we can see, the 10% discount has been applied to the first two lines.
The total discount amount is visible in the checkout.discount
field, which is equal to
the sum of the differences between the undiscountedTotalPrice
and totalPrice
of all lines.
Applying the once-per-order specific product voucher
If the voucher has the applyOncePerOrder
flag set to True
, the discount will only be applied to
the single cheapest product eligible for the discount. In the scenario described,
the discount would only be applied to the product with a price of $20, and would be visible on only one line of the order.
{
"data": {
"checkoutAddPromoCode": {
"errors": [],
"checkout": {
"id": "Q2hlY2tvdXQ6YWJlZTQzNTEtMGZjMS00MWYzLTk1YzEtMTIyMTc4NWMwYzY2",
"token": "abee4351-0fc1-41f3-95c1-1221785c0c66",
"voucherCode": "SPECIFIC PRODUCT",
"discountName": null,
"discount": {
"amount": 2.0
},
"subtotalPrice": {
"tax": {
"amount": 0.0
},
"gross": {
"amount": 64.99
},
"net": {
"amount": 64.99
}
},
"lines": [
{
"quantity": 1,
"totalPrice": {
"net": {
"amount": 45.0
},
"gross": {
"amount": 45.0
}
},
"undiscountedTotalPrice": {
"amount": 45.0
},
"unitPrice": {
"net": {
"amount": 45.0
},
"gross": {
"amount": 45.0
}
},
"undiscountedUnitPrice": {
"amount": 45.0
}
},
{
"quantity": 1,
"totalPrice": {
"net": {
"amount": 18.0
},
"gross": {
"amount": 18.0
}
},
"undiscountedTotalPrice": {
"amount": 20.0
},
"unitPrice": {
"net": {
"amount": 18.0
},
"gross": {
"amount": 18.0
}
},
"undiscountedUnitPrice": {
"amount": 20.0
}
},
{
"quantity": 1,
"totalPrice": {
"net": {
"amount": 1.99
},
"gross": {
"amount": 1.99
}
},
"undiscountedTotalPrice": {
"amount": 1.99
},
"unitPrice": {
"net": {
"amount": 1.99
},
"gross": {
"amount": 1.99
}
},
"undiscountedUnitPrice": {
"amount": 1.99
}
}
]
}
}
}
}
Here, we can see that the discount was only applied to the cheapest line included in the voucher discount.
The total checkout.discount
is $2.0 in this case. The difference between
totalPrice
and undiscountedTotalPrice
is only visible on the second line.
Catalogue Promotion and Voucher together
Catalogue promotions and vouchers can be combined. In this case, the voucher discount is applied to the price after the promotion discount. Let's consider an example: the checkout has two items, and the first item is on $5 fixed promotion. A percentage discount of 50% is being applied to the entire order.
Here is the checkout data before applying the voucher code (only the promotion is included in the price).
{
"data": {
"checkout": {
"id": "Q2hlY2tvdXQ6ZDdjMjY5M2MtOTU0ZS00YTM4LWJkMjQtMzM1Y2NiODk0YWMy",
"email": "admin@example.com",
"token": "d7c2693c-954e-4a38-bd24-335ccb894ac2",
"channel": {
"slug": "default-channel"
},
"voucherCode": null,
"discount": {
"amount": 0.0
},
"discountName": null,
"giftCards": [],
"totalPrice": {
"tax": {
"amount": 0.0
},
"gross": {
"amount": 65.0,
"currency": "USD",
"__typename": "Money"
},
"net": {
"amount": 65.0,
"currency": "USD",
"__typename": "Money"
},
"__typename": "TaxedMoney"
},
"subtotalPrice": {
"tax": {
"amount": 0.0
},
"gross": {
"amount": 65.0,
"currency": "USD",
"__typename": "Money"
},
"net": {
"amount": 65.0,
"currency": "USD",
"__typename": "Money"
},
"__typename": "TaxedMoney"
},
"lines": [
{
"id": "Q2hlY2tvdXRMaW5lOmNlNzJiY2IyLWJjZjYtNGQxZS04MDRmLWMwZDdjZWI2NWM3OQ==",
"quantity": 2,
"undiscountedTotalPrice": {
"amount": 40.0
},
"totalPrice": {
"gross": {
"amount": 30.0
},
"net": {
"amount": 30.0
}
},
"unitPrice": {
"net": {
"amount": 15.0
}
},
"undiscountedUnitPrice": {
"amount": 20.0
},
"variant": {
"id": "UHJvZHVjdFZhcmlhbnQ6MzQ4",
"name": "S",
"pricing": {
"price": {
"gross": {
"amount": 15.0
}
}
},
"product": {
"name": "Monospace Tee",
"chargeTaxes": false
}
}
},
{
"id": "Q2hlY2tvdXRMaW5lOjI5ZDA4Zjc2LTdlMTItNGE5OC05ZWFkLThhNzFjMDMyNGQyOQ==",
"quantity": 1,
"undiscountedTotalPrice": {
"amount": 35.0
},
"totalPrice": {
"gross": {
"amount": 35.0
},
"net": {
"amount": 35.0
}
},
"unitPrice": {
"net": {
"amount": 35.0
}
},
"undiscountedUnitPrice": {
"amount": 35.0
},
"variant": {
"id": "UHJvZHVjdFZhcmlhbnQ6MzQ2",
"name": "UHJvZHVjdFZhcmlhbnQ6MzQ2",
"pricing": {
"price": {
"gross": {
"amount": 35.0
}
}
},
"product": {
"name": "Blue Hoodie",
"chargeTaxes": false
}
}
}
]
}
}
}
As we can see the price of the first line is reduced by the promotion discount. The $5 discount is applied on each line so we have $10 total discount on the fist line.
Below is the checkout data after applying the 50% entire order voucher.
{
"data": {
"checkout": {
"id": "Q2hlY2tvdXQ6ZDdjMjY5M2MtOTU0ZS00YTM4LWJkMjQtMzM1Y2NiODk0YWMy",
"email": "admin@example.com",
"token": "d7c2693c-954e-4a38-bd24-335ccb894ac2",
"channel": {
"slug": "default-channel"
},
"voucherCode": "DISCOUNT",
"discount": {
"amount": 32.5
},
"discountName": "Big order discount",
"giftCards": [],
"totalPrice": {
"tax": {
"amount": 0.0
},
"gross": {
"amount": 32.5,
"currency": "USD",
"__typename": "Money"
},
"net": {
"amount": 32.5,
"currency": "USD",
"__typename": "Money"
},
"__typename": "TaxedMoney"
},
"subtotalPrice": {
"tax": {
"amount": 0.0
},
"gross": {
"amount": 32.5,
"currency": "USD",
"__typename": "Money"
},
"net": {
"amount": 32.5,
"currency": "USD",
"__typename": "Money"
},
"__typename": "TaxedMoney"
},
"lines": [
{
"id": "Q2hlY2tvdXRMaW5lOmNlNzJiY2IyLWJjZjYtNGQxZS04MDRmLWMwZDdjZWI2NWM3OQ==",
"quantity": 2,
"undiscountedTotalPrice": {
"amount": 40.0
},
"totalPrice": {
"gross": {
"amount": 15.0
},
"net": {
"amount": 15.0
}
},
"unitPrice": {
"net": {
"amount": 7.5
}
},
"undiscountedUnitPrice": {
"amount": 20.0
},
"variant": {
"id": "UHJvZHVjdFZhcmlhbnQ6MzQ4",
"name": "S",
"pricing": {
"price": {
"gross": {
"amount": 15.0
}
}
},
"product": {
"name": "Monospace Tee",
"chargeTaxes": false
}
}
},
{
"id": "Q2hlY2tvdXRMaW5lOjI5ZDA4Zjc2LTdlMTItNGE5OC05ZWFkLThhNzFjMDMyNGQyOQ==",
"quantity": 1,
"undiscountedTotalPrice": {
"amount": 35.0
},
"totalPrice": {
"gross": {
"amount": 17.5
},
"net": {
"amount": 17.5
}
},
"unitPrice": {
"net": {
"amount": 17.5
}
},
"undiscountedUnitPrice": {
"amount": 35.0
},
"variant": {
"id": "UHJvZHVjdFZhcmlhbnQ6MzQ2",
"name": "UHJvZHVjdFZhcmlhbnQ6MzQ2",
"pricing": {
"price": {
"gross": {
"amount": 35.0
}
}
},
"product": {
"name": "Blue Hoodie",
"chargeTaxes": false
}
}
}
]
}
}
}
As we can see, the total discount is $32.5.0.
In the first line, the difference between totalPrice
and undiscountedTotalPrice
($25)
is the total discount applied to this line, which includes both promotion and voucher discounts.
However, in the second line, the difference between those values ($17.5) comes from
the voucher discount.
To calculate the value of the applied promotion, we can sum up the line discounts and subtract
checkout.discount
. In this example, the calculation would be: ($25 + $17.5) - $32.5 = $10.
Therefore, we end up with the same value as before applying the voucher.
Completing checkout with a voucher discount
When completing the checkout with an assigned voucher, the applied voucher discount
will be visible on the order and order line prices. Additionally, the sum of voucher
discounts will be reflected in the order.discounts
field. This behavior is consistent
across all voucher types.
The following example shows the response from the checkoutComplete mutation for a checkout that includes two items of the same variant with a 10% voucher discount applied.
{
"data": {
"checkoutComplete": {
"order": {
"id": "T3JkZXI6NmM1MjhkNGYtZjc5YS00OGM1LTk2ZWUtYjI0M2U2ZjdmMDBm",
"status": "UNFULFILLED",
"totalCaptured": {
"amount": 113.51
},
"subtotal": {
"net": {
"amount": 33.96
},
"gross": {
"amount": 36.0
}
},
"total": {
"currency": "USD",
"net": {
"amount": 107.08
},
"gross": {
"amount": 113.51
}
},
"undiscountedTotal": {
"currency": "USD",
"net": {
"amount": 113.12
},
"gross": {
"amount": 117.51
}
},
"discounts": [
{
"name": null,
"type": "VOUCHER",
"valueType": "FIXED",
"amount": {
"amount": 4.0
}
}
],
"lines": [
{
"quantity": 2,
"totalPrice": {
"gross": {
"amount": 36.0
},
"net": {
"amount": 33.96
}
},
"unitPrice": {
"gross": {
"amount": 18.0
},
"net": {
"amount": 16.98
}
},
"undiscountedUnitPrice": {
"gross": {
"amount": 20.0
},
"net": {
"amount": 20.0
}
},
"unitDiscount": {
"amount": 2.0
}
}
]
},
"errors": []
}
}
}
The discount amount can be found in order.discounts.amount
, the value is equal to the
order.lines.unitDiscount
multiplied by the line quantity.
The discount can also be seen in the line prices - compare the undiscountedUnitPrice
and the unitPrice
.
Webhooks
All voucher actions also generate asynchronous webhooks that can be used for reacting
to the events emitted by Saleor, e.g. VOUCHER_CREATED
, VOUCHER_UPDATED
, VOUCHER_DELETED
.
The WebhookEventTypeAsyncEnum
represents the full list of asynchronous webhooks.
You can learn more about webhooks here.
Exporting voucher codes
Voucher codes can be exported to a CSV or XLSX file.
You can learn more about voucher codes export here.