Double discount calculator
A price of 100 with 20% off and then another 10% ends at 72, so you save 28 rather than 30, because the second cut only applies to the 80 left. Enter the original price and both discounts in order. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Price
- 100
- First discount
- 20
- Second discount
- 10
- Price after first
- 80
- Final price
- 72
- Savings
- 28
- Equivalent percent
- 28
Shoppers rarely see a single markdown. A storewide sale is followed by a coupon, an employee extra percent sits on top of a clearance tag, or a marketplace stacks a seller discount then a site-wide code. Adding the two percentages overstates the deal: twenty percent then ten percent is not thirty percent off, because the second cut applies only to what remains. This double discount calculator takes the original price and two successive percentage discounts, applies them in order, and returns the final price together with the total savings so a cart, a deal post, or a budget can lock the stacked figure before checkout. Zero percent on either step leaves that step unchanged; one hundred percent on either step yields a free remaining balance.
Where a double discount calculator earns its keep
Households stacking a storewide sale with a clipped coupon use a double discount calculator to turn the original ticket into one comparable final price before they add the item to a cart. Product teams wire the same contract into landing pages and email teasers that must show a stacked sale price for a sample SKU without calling a promotions engine.
This is a different search and a different job from a generic discount calculator, from a wholesale chain-discount net price factor, from Cyber Monday, and from a buy-one-get-one effective percent: those tools compute one ordinary markdown, a variable-length trade cascade aimed at NPF, a named one-day event, or a two-for-one equivalent rate. A double discount calculator prices one original amount against exactly two successive percents and returns the final price plus the dollars saved on that stack. Failed validation does not consume balance.
A third coupon, free-shipping thresholds, and sales tax stay outside this identity on purpose: fold them in your own layer after you have the raw final price and the dollars saved. Order of the two rates does not change the unrounded product, but money rounding after the first step can shift a cent if you swap them, so send them in the order the register will apply them.
What you can do with it
Preview an employee extra on an already-sale item
Apply the clearance percent first and the employee extra second, then pin savings so payroll or a staff shop can quote the stacked price.
FAQ
Why is twenty percent then ten percent not thirty percent off?
The second discount applies only to what remains after the first. On 100, twenty percent leaves 80; ten percent of 80 is 8; the final price is 72, so savings are 28 (an equivalent 28 percent), not 30.
What happens if a discount exceeds 100 percent?
A successive discount that exceeds 100 percent is not billed.
How is this different from a chain discount or a single discount calculator?
A generic discount calculator takes one percent. A chain-discount tool takes a variable-length wholesale cascade and returns a net price factor. This calculator is exactly two successive retail percents plus total savings.
Does the order of the two discounts matter?
Not for the exact result: the two percentages multiply the same way in either order. Rounding to cents after the first step can shift the final price by a cent, so enter them in the order the store applies them.
What if one of the discounts is 0% or 100%?
A 0% step leaves the price unchanged, so an item with only one offer can still be compared. A 100% step brings the remaining price to zero, and the following step keeps it at zero.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/fin/double-discount \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"price":100,"first_discount":20,"second_discount":10}'const res = await fetch("https://api.kit.forhosting.com/fin/double-discount", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"price": 100,
"first_discount": 20,
"second_discount": 10
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/fin/double-discount",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"price": 100,
"first_discount": 20,
"second_discount": 10
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/fin/double-discount", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"price":100,"first_discount":20,"second_discount":10}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"price":100,"first_discount":20,"second_discount":10}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/fin/double-discount", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"price": 100,
"first_discount": 20,
"second_discount": 10
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "fin.double_discount",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
A discount that exceeds one hundred percent is rejected as invalid_input and is not billed.
How two successive discounts actually compound
Two successive percentage discounts are not the same as one combined percent. A retailer prints twenty percent off, then a coupon takes another ten percent off what is left; an employee extra sits on a clearance tag; a marketplace stacks a seller markdown then a site-wide code. Adding twenty and ten and calling the deal thirty percent overstates the savings, because the second cut never sees the original price. Zero percent on either step is a legitimate outcome and leaves that step unchanged rather than an error: a SKU that only carries one of the two offers still belongs in a comparison cart. One hundred percent on either step is a free remaining balance: that step's result is zero and later steps stay zero. You supply three numbers; the calculator multiplies them in order and rounds each money figure half-up to two decimal places so goldens compare byte for byte across runtimes. The identity this calculator uses is final_price equals price times one minus first_discount divided by one hundred, times one minus second_discount divided by one hundred. The first markdown produces an intermediate price_after_first, which is then reduced by the second rate. Total savings is the original price minus that final_price, which is the same as price times the equivalent combined percent once money rounding is applied on each step so the parts still add back to the list. Price is the original amount in one currency; first_discount and second_discount are the two advertised rates typed the way banners print them, so twenty means twenty percent, not twenty hundredths. The single percent that would match the stacked result is also returned as equivalent_percent so a spreadsheet can keep one number next to the two percents people type. A discount that exceeds one hundred percent is not a markdown in this contract and is rejected as invalid_input, because a banner that prints one hundred twenty percent off is almost always a data error, not a rebate you meant to model. This endpoint does not scrape retailer catalogs, does not add a third coupon on top of the two percents you send, does not add sales tax or shipping, and does not convert currencies.
Input contract, response shape, and error behaviour
Zero percent on a step is valid and leaves that step's money unchanged. One hundred percent on a step is valid and zeros the remaining price. POST a JSON body with three required finite numbers. price is the original amount in one currency unit before either successive discount; aliases original_price, list_price, original, and amount map to the same field so a catalog export can keep its column names. first_discount is the first successive percent-off rate applied to the original price, not a fraction: twenty means twenty percent off; aliases first_percent, discount1, first, and percent1 are accepted. second_discount is the second successive percent-off rate applied to what remains after the first markdown; aliases second_percent, discount2, second, and percent2 are accepted. Each numeric field may arrive as a JSON number or a numeric string. Missing fields, empty strings, and non-finite values fail with invalid_input naming the field. On success the body echoes price rounded to two decimal places, first_discount, and second_discount, then reports price_after_first as the money after only the first markdown, final_price as the checkout figure after both cuts rounded to two decimal places, savings as price minus final_price, equivalent_percent as the single percent that would match the stacked result, and a formula string restating the identity. Example: one hundred at twenty then ten yields price_after_first of eighty, final_price of seventy-two, savings of twenty-eight, and an equivalent_percent of twenty-eight. Pair the JSON with your own rule for which SKUs share which two rates—storewide plus coupon, clearance plus employee extra, or seller markdown plus site code—because this endpoint multiplies the three numbers you send; it does not look up a retailer, apply a third coupon, or convert currencies. Keep requests small, pin the rounded final_price in tests, and you get a reproducible stacked figure for worksheets, LMS plugins, and edge workers without standing up a promotions engine of your own.
Where a double discount calculator earns its keep
Deal-site editors drop a published list and the two advertised percents, then pin final_price and savings so a post does not quote a made-up thirty-percent figure that later disagrees with checkout. Keep both discounts between zero and one hundred inclusive, pin the rounded final_price in tests, and you get a reproducible stacked figure for teasers, carts, and edge workers without standing up a promotions engine of your own.
Integration use cases
Stack a storewide sale with a coupon before checkout
Turn the original ticket plus two advertised percents into final_price so a twenty-then-ten stack is not quoted as a fake thirty percent off.
Check a deal post against two published markdowns
Drop the list from a retailer page and both percents, then confirm final_price and savings match the stacked figure you are about to publish.
Technical questions
Can either discount be zero or one hundred percent?
Yes. Zero percent leaves that step unchanged. One hundred percent zeros the remaining price, so final_price is 0 and savings equal the original price.
What does the double discount calculator cost?
The browser tool is free. API requests use prepaid KIT balance at $0.002 per successful call; invalid input is not billed.