public static void Main()
PointOfSale pointOfSale = new PointOfSale(total: 1, tax: 1);
pointOfSale.ApplyTender(1);
AdjustedCheckValues expectedProposedOrderValues =
AdjustedCheckValues proposedOrderValues = ProposedOrderCalculator.CalculateCreateProposedOrderValues(
pointOfSale.TotalOutstandingAmount,
pointOfSale.TotalTaxAmount,
Console.WriteLine("spend_amount to use for the Proposed Order Request: " + proposedOrderValues.SpendAmount);
Console.WriteLine("tax_amount to use for the Proposed Order Request: " + proposedOrderValues.TaxAmount);
Console.WriteLine("exemption_amount to use for the Proposed Order Request: " + proposedOrderValues.ExemptionAmount);
int availableDiscountAmount = 0;
pointOfSale.ApplyDiscount(availableDiscountAmount);
AdjustedCheckValues expectedCompletedOrderValues =
AdjustedCheckValues completedOrderValues = ProposedOrderCalculator.CalculateCompleteOrderValues(
pointOfSale.TotalOutstandingAmount,
pointOfSale.TotalTaxAmount,
availableDiscountAmount);
Console.WriteLine("spend_amount to use for the Completed Order Request: " + completedOrderValues.SpendAmount);
Console.WriteLine("tax_amount to use for the Completed Order Request: " + completedOrderValues.TaxAmount);
Console.WriteLine("exemption_amount to use for the Completed Order Request: " + completedOrderValues.ExemptionAmount);
public class AdjustedCheckValues
public int SpendAmount { get; }
public int TaxAmount { get; }
public int ExemptionAmount { get; }
internal AdjustedCheckValues(int spendAmount, int taxAmount, int exemptionAmount)
SpendAmount = spendAmount;
ExemptionAmount = exemptionAmount;
public override string ToString()
return $"SpendAmount={SpendAmount};TaxAmount={TaxAmount};ExemptionAmount={ExemptionAmount};";
public int PaymentAmount { get; set; }
public int TaxAmount { get; set; }
public int ExemptionAmount { get; set; }
public int OutstandingAmount { get; set; }
public int PreTaxSubtotal => OutstandingAmount - TaxAmount;
public int NonExemptSubtotal => PreTaxSubtotal - ExemptionAmount;
public static class ProposedOrderCalculator
public static AdjustedCheckValues CalculateCreateProposedOrderValues(
return CalculateOrderValues(
public static AdjustedCheckValues CalculateCompleteOrderValues(
int appliedDiscountAmount
int outstandingAmountWithDiscount = outstandingAmount + Math.Abs(appliedDiscountAmount);
return CalculateOrderValues(
outstandingAmountWithDiscount,
internal static AdjustedCheckValues CalculateOrderValues(
CheckData checkData = SanitizeData(outstandingAmount, taxAmount, exemptionAmount, paymentAmount);
var adjustedTaxAmount = CalculateAdjustedTaxAmount(checkData);
var adjustedExemptionAmount = CalculateAdjustedExemptionAmount(checkData);
return new AdjustedCheckValues(checkData.PaymentAmount, adjustedTaxAmount, adjustedExemptionAmount);
internal static CheckData SanitizeData(int outstandingAmount, int taxAmount, int exemptionAmount, int paymentAmount)
var checkData = new CheckData
ExemptionAmount = exemptionAmount,
OutstandingAmount = outstandingAmount,
PaymentAmount = paymentAmount,
checkData.PaymentAmount = PaymentAmountCannotBeGreaterThanOutstandingAmount(
checkData.OutstandingAmount,
checkData.PaymentAmount);
checkData.TaxAmount = TaxAmountCannotBeGreaterThanOutstandingAmount(
checkData.OutstandingAmount,
checkData.ExemptionAmount = ExemptionAmountCannotBeGreaterThanPreTaxSubtotal(
checkData.ExemptionAmount,
checkData.PreTaxSubtotal);
internal static int CalculateAdjustedTaxAmount(CheckData checkData)
if (checkData.TaxAmount > checkData.OutstandingAmount)
throw new Exception("Tax amount cannot be greater than total outstanding amount.");
bool isTaxFullyPaid = checkData.PaymentAmount >= checkData.OutstandingAmount;
return checkData.TaxAmount;
return ZeroIfNegative(checkData.PaymentAmount - checkData.PreTaxSubtotal);
internal static int CalculateAdjustedExemptionAmount(CheckData checkData)
if (checkData.ExemptionAmount > checkData.PreTaxSubtotal)
throw new Exception("Exemption amount cannot be greater that the pre-tax total on the check.");
bool isExemptFullyPaid = checkData.PaymentAmount >= checkData.PreTaxSubtotal;
return checkData.ExemptionAmount;
return ZeroIfNegative(checkData.PaymentAmount - checkData.NonExemptSubtotal);
private static int PaymentAmountCannotBeGreaterThanOutstandingAmount(
=> SmallerOrZeroIfNegative(outstandingAmount, paymentAmount);
private static int TaxAmountCannotBeGreaterThanOutstandingAmount(int outstandingAmount, int taxAmount)
=> SmallerOrZeroIfNegative(taxAmount, outstandingAmount);
private static int ExemptionAmountCannotBeGreaterThanPreTaxSubtotal(
=> SmallerOrZeroIfNegative(exemptAmount, outstandingAmount);
internal static int SmallerOrZeroIfNegative(int a, int b) => ZeroIfNegative(Math.Min(a, b));
private static int ZeroIfNegative(int val) => Math.Max(0, val);
internal class PointOfSale
private int StartingTotal { get; }
private int StartingTax { get; }
private int StartingSubtotal => StartingTotal - StartingTax;
private decimal TaxRate => (decimal)StartingTax / (decimal)StartingSubtotal;
private int TotalDiscounts { get; set; }
private int TotalTenders { get; set; }
private int AdjustedSubtotal => StartingSubtotal - TotalDiscounts;
private decimal AdjustedSubtotalInDollars => AdjustedSubtotal / 100.0M;
private int AdjustedTax => decimal.ToInt32(AdjustedSubtotalInDollars * TaxRate * 100.0M);
public int TotalOutstandingAmount => AdjustedSubtotal + AdjustedTax - TotalTenders;
public int TotalTaxAmount => AdjustedTax;
public PointOfSale(int total, int tax)
throw new Exception($"The total must be greater than tax (a subtotal amount must exist). If the intent " +
$"is to test a partial payment scenario, use the ${nameof(ApplyTender)} method to " +
$"reduce the outstanding balance.");
public void ApplyDiscount(int discountAmountInCents) => TotalDiscounts += discountAmountInCents;
public void ApplyTender(int tenderAmountInCents) => TotalTenders += tenderAmountInCents;