From 5665b67288e5dba275dded5f1e28b1f17e20bbaa Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Wed, 29 Aug 2018 17:28:51 -0600 Subject: [PATCH] Fixes issue where tax exemption results in negative taxes - Occurs if a merchant sets all items on a checkout to be tax exempt - If a blanket amount discount is applied, then taxes will be subtracted according to the discount amount. Since discounts are not examptable, a negative tax amount would occur Signed-off-by: Christopher Rogers --- lib/util/tax-ceil.js | 3 ++ test/pricing/checkout/checkout.test.js | 39 +++++++++++++++---- .../fixtures/coupons/coop-fixed-all-5.json | 21 ++++++++++ 3 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 test/server/fixtures/coupons/coop-fixed-all-5.json diff --git a/lib/util/tax-ceil.js b/lib/util/tax-ceil.js index b04dc297f..4f7b1ec14 100644 --- a/lib/util/tax-ceil.js +++ b/lib/util/tax-ceil.js @@ -2,10 +2,13 @@ * Ceilings the second decimal of a number without risk of * floating point math errors * + * - returns zero if the result is negative + * * @param {Number} number * @return {Number} */ export default function taxCeil (number) { + number = Math.max(number, 0); return +(Math.ceil(number + 'e+2') + 'e-2'); } diff --git a/test/pricing/checkout/checkout.test.js b/test/pricing/checkout/checkout.test.js index 43d6d17d5..71d76fa8b 100644 --- a/test/pricing/checkout/checkout.test.js +++ b/test/pricing/checkout/checkout.test.js @@ -14,6 +14,13 @@ describe('CheckoutPricing', function () { }); }); + beforeEach(function (done) { + subscriptionPricingFactory('tax_exempt', this.recurly, sub => { + this.subscriptionPricingExampleTaxExempt = sub; + done(); + }); + }) + /** * Subscriptions */ @@ -1413,13 +1420,6 @@ describe('CheckoutPricing', function () { }); describe('given some tax exempt adjustments and subscriptions', () => { - beforeEach(function (done) { - subscriptionPricingFactory('tax_exempt', this.recurly, sub => { - this.subscriptionPricingExampleTaxExempt = sub; - done(); - }); - }); - beforeEach(function () { return this.pricing .subscription(this.subscriptionPricingExampleTaxExempt) // $2 setup fee @@ -1504,6 +1504,31 @@ describe('CheckoutPricing', function () { }); }); + describe('given tax exempt items and a discount', () => { + beforeEach(function () { + // Reset to eliminate the pre-defined adjustments + this.pricing = this.recurly.Pricing.Checkout(); + return this.pricing + .address({ country: 'US', postalCode: '94110' }) + .subscription(this.subscriptionPricingExampleTaxExempt) + .adjustment({ amount: 10, taxExempt: true }) + .adjustment({ amount: 27.25, taxExempt: true }) + .coupon('coop-fixed-all-5') + .reprice(); + }); + + it('does not discount negatively', function (done) { + this.pricing + .reprice() + .then(price => { + assert.equal(price.now.taxes, 0); + assert.equal(price.now.discount, 5); + done(); + }) + .done(); + }); + }); + describe('given VAT numbers on address and tax info', () => { it('takes the VAT number from the tax info', function (done) { sinon.spy(this.recurly, 'tax'); diff --git a/test/server/fixtures/coupons/coop-fixed-all-5.json b/test/server/fixtures/coupons/coop-fixed-all-5.json new file mode 100644 index 000000000..1eb47a21c --- /dev/null +++ b/test/server/fixtures/coupons/coop-fixed-all-5.json @@ -0,0 +1,21 @@ +{ + "code": "coop-fixed-all-5", + "name": "Test coupon: $5 off all charges", + "discount": { + "type": "dollars", + "amount": + { + "USD": 5.0 + } + }, + "single_use": false, + "applies_for_months": null, + "duration": "forever", + "temporal_unit": null, + "temporal_amount": null, + "plans": [], + "applies_to_non_plan_charges": true, + "applies_to_plans": true, + "applies_to_all_plans": true, + "redemption_resource": "account" +}