/// <summary> /// Gets the shipping addresses for the basket /// </summary> /// <param name="basket">The basket to parse; can be null to return the default shipping address</param> /// <returns>The shipping addresses for the basket</returns> public static List <TaxAddress> GetShippingAddresses(Basket basket) { List <TaxAddress> shippingAddresses = new List <TaxAddress>(); string shippingCountry; int shippingProvinceId; string shippingPostalCode; if (basket != null) { foreach (BasketShipment shipment in basket.Shipments) { Address shippingAddress = shipment.Address; if (shippingAddress != null && shippingAddress.IsValid) { shippingCountry = shippingAddress.CountryCode; shippingProvinceId = shippingAddress.ProvinceId; shippingPostalCode = shippingAddress.PostalCode; TaxAddress tempShippingAddress = new TaxAddress(shippingCountry, shippingProvinceId, shippingPostalCode); if (shippingAddresses.IndexOf(tempShippingAddress) < 0) { shippingAddresses.Add(tempShippingAddress); } } } } if (shippingAddresses.Count == 0) { Warehouse shippingAddress = Token.Instance.Store.DefaultWarehouse; shippingCountry = shippingAddress.CountryCode; shippingProvinceId = ProvinceDataSource.GetProvinceIdByName(shippingCountry, shippingAddress.Province); shippingPostalCode = shippingAddress.PostalCode; shippingAddresses.Add(new TaxAddress(shippingCountry, shippingProvinceId, shippingPostalCode)); } return(shippingAddresses); }
/// <summary> /// Gets the tax rules that may apply /// </summary> /// <param name="taxCodeId">The tax code of the taxable item</param> /// <param name="billingAddress">The billing address for the item</param> /// <param name="shippingAddress">The shipping address for the item</param> /// <param name="user">The user shopping for the item</param> /// <returns>A List of TaxRule records that may apply</returns> public static List <TaxRule> GetPotentialTaxRules(int taxCodeId, TaxAddress billingAddress, TaxAddress shippingAddress, User user) { int[] taxCodeIds = { taxCodeId }; List <TaxAddress> shippingAddresses = new List <TaxAddress>(); shippingAddresses.Add(shippingAddress); return(GetPotentialTaxRules(taxCodeIds, billingAddress, shippingAddresses, user)); }
/// <summary> /// Gets the tax rules that may apply /// </summary> /// <param name="taxCodeIds">The tax code of the taxable item(s)</param> /// <param name="billingAddress">The billing address for the item(s)</param> /// <param name="shippingAddresses">The shipping addresses for the item(s)</param> /// <param name="user">The user shopping for the item(s)</param> /// <returns>A List of TaxRule records that may apply</returns> public static List <TaxRule> GetPotentialTaxRules(int[] taxCodeIds, TaxAddress billingAddress, List <TaxAddress> shippingAddresses, User user) { // BUILD THE KEY String cacheKey = GetTaxCodesKey(taxCodeIds) + "~" + GetAddressKey(billingAddress) + "~" + GetAddressKey(shippingAddresses) + "~" + user.UserId; Dictionary <string, List <TaxRule> > taxRulesDic = null; // CHECK THE HTTP CONTEXT FOR EXISTING RULES if (HttpContext.Current != null) { taxRulesDic = HttpContext.Current.Items["PotentialTaxRules"] as Dictionary <string, List <TaxRule> >; if (taxRulesDic != null) { if (taxRulesDic.ContainsKey(cacheKey)) { return(taxRulesDic[cacheKey]); } } else { taxRulesDic = new Dictionary <string, List <TaxRule> >(); } } List <TaxRule> allRules = new List <TaxRule>(); TaxRuleCollection countryRules = TaxRuleDataSource.LoadForTaxCodes(taxCodeIds, billingAddress, user); foreach (TaxRule rule in countryRules) { allRules.Add(rule); } foreach (TaxAddress shippingAddress in shippingAddresses) { countryRules = TaxRuleDataSource.LoadForTaxCodes(taxCodeIds, shippingAddress, user); foreach (TaxRule rule in countryRules) { if (!ListContainsTaxRule(allRules, rule.TaxRuleId)) { allRules.Add(rule); } } } // IF CONTEXT IS AVAILABLE STORE THE RESULTS if (HttpContext.Current != null) { taxRulesDic[cacheKey] = allRules; HttpContext.Current.Items["PotentialTaxRules"] = taxRulesDic; } return(allRules); }
public static TaxAddress ConvertTaxAddress(IAddressBase address) { var result = new TaxAddress { Country = address.CountryID, Region = address.State, City = address.City, PostalCode = address.PostalCode, AddressLine1 = address.AddressLine1, AddressLine2 = address.AddressLine2, AddressLine3 = address.AddressLine3, }; return(result); }
/// <summary> /// Constructor /// </summary> /// <param name="payment">The Payment object associated with this request</param> /// <param name="subscriptionPlan">The SubscriptionPlan associated witht this request</param> /// <param name="remoteIP">Remote IP of the user initiating the request</param> public AuthorizeRecurringTransactionRequest(Payment payment, SubscriptionPlan subscriptionPlan, string remoteIP) : base(payment, remoteIP) { this._TransactionOrigin = TransactionOrigin.Internet; if (subscriptionPlan != null) { this.SubscriptionName = subscriptionPlan.Name; this.Amount = payment.Amount; this.RecurringChargeSpecified = subscriptionPlan.RecurringChargeSpecified; // GET THE SUBSCRIPTION CHARGE WITH TAX Order order = payment.Order; int billToProvinceId = ProvinceDataSource.GetProvinceIdByName(order.BillToCountryCode, order.BillToProvince); TaxAddress billingAddress = new TaxAddress(order.BillToCountryCode, billToProvinceId, order.BillToPostalCode); this.RecurringCharge = TaxHelper.GetPriceWithTax(subscriptionPlan.RecurringCharge, subscriptionPlan.TaxCodeId, billingAddress, billingAddress); this.NumberOfPayments = subscriptionPlan.NumberOfPayments; this.PaymentFrequency = subscriptionPlan.PaymentFrequency; this.PaymentFrequencyUnit = subscriptionPlan.PaymentFrequencyUnit; } }
/// <summary> /// Calculates the Tax for the given price /// </summary> /// <param name="unitPrice">The unit price to calculate Tax for</param> /// <param name="quantity" >The Quantity of the line item</param> /// <param name="taxCodeId">The tax code that applies to this price</param> /// <param name="priority">The priority of the item - 0 for products, can be greater than 0 for non products items.</param> /// <param name="billingAddress">The billing address that applies to the price</param> /// <param name="shippingAddress">The shipping address that applies to the price</param> /// <param name="user">The user being taxed</param> /// <returns>The Tax details for the given price</returns> public static TaxInfo InternalGetTaxInfo(LSDecimal unitPrice, int quantity, int taxCodeId, int priority, TaxAddress billingAddress, TaxAddress shippingAddress, User user, bool priceIncludesTax) { bool isNegativePrice = (unitPrice < 0); LSDecimal absPrice = isNegativePrice ? (unitPrice * -1) : unitPrice; //INITIALIZE TAXES LSDecimal totalTax = 0; LSDecimal totalTaxRate = 0; //GET ANY RULES THAT MAY APPLY TO THESE ADDRESSES List <TaxRule> taxRules = TaxRuleHelper.GetPotentialTaxRules(taxCodeId, billingAddress, shippingAddress, user); List <ShopTaxItem> taxItems = new List <ShopTaxItem>(); //LOOP ANY RULES AND CALCULATE THE IMPACT OF Tax foreach (TaxRule taxRule in taxRules) { //PREVENT INCORRECT COMPOUNDING, AND ENSURE TAX MEETS ADDRESS CRITERIA if (priority < taxRule.Priority && taxRule.AppliesToAddress(billingAddress, shippingAddress)) { int tempTaxItemCount = taxItems.Count; if (taxRule.AppliesToTaxCode(taxCodeId)) { LSDecimal tempTax = LineItemCalculator.CalculateTaxForItem(taxRule, absPrice, quantity, priceIncludesTax); taxItems.Add(new ShopTaxItem(taxRule.TaxCodeId, tempTax, taxRule.TaxRate)); totalTax += tempTax; totalTaxRate += taxRule.TaxRate; } //CHECK IF THIS TAX APPLIES ON ANY PREEXISTING SHOPTAX ITEMS for (int i = 0; i < tempTaxItemCount; i++) { ShopTaxItem thisItem = taxItems[i]; if (taxRule.AppliesToTaxCode(thisItem.TaxCodeId)) { //TAX ON TAX, RATE MUST BE CALCULATED LSDecimal tempTax = LineItemCalculator.CalculateTaxForItem(taxRule, thisItem.Price, 1, priceIncludesTax); taxItems.Add(new ShopTaxItem(taxRule.TaxCodeId, tempTax, taxRule.TaxRate)); totalTax += tempTax; totalTaxRate += TaxHelper.Round(((decimal)thisItem.TaxRate * (decimal)taxRule.TaxRate) / 100, 2, taxRule.RoundingRule); } } } } if (priceIncludesTax) { //IF PRICE IS Tax INCLUSIVE, THEN CALCULATED Tax CANNOT EXCEED PRICE //THIS WOULD INDICATE A CONFIGURATION ERROR if (totalTax > absPrice) { totalTax = unitPrice; } if (totalTaxRate > 100) { totalTaxRate = 100; } if (isNegativePrice) { totalTax = totalTax * -1; } LSDecimal actualPrice = unitPrice - totalTax; return(new TaxInfo(actualPrice, totalTax, totalTaxRate)); } else { if (isNegativePrice) { totalTax = totalTax * -1; } return(new TaxInfo(unitPrice, totalTax, totalTaxRate)); } }
private static string GetAddressKey(TaxAddress address) { return(address.CountryCode + "~" + address.ProvinceId + "~" + address.PostalCode); }
/// <summary> /// Generates payment records for an order /// </summary> /// <param name="order">The order being created</param> /// <param name="checkoutRequest">The checkout request</param> /// <param name="giftCertPayments">The collection of gift certificate payments for this order</param> /// <param name="giftCertPaymentMethodId">The ID of the gift certificate payment method</param> /// <param name="orderItemSubscriptions">Order Item subscriptions</param> internal static void GenerateOrderPayments(Order order, CheckoutRequest checkoutRequest, List <BasketPaymentItem> giftCertPayments, int giftCertPaymentMethodId, Dictionary <int, Subscription[]> orderItemSubscriptions) { //THIS VARIABLE SHOULD ONLY CONTAIN DATA IF POST-CHECKOUT GATEWAY PROCESSING IS REQUIRED string saveAccountData = string.Empty; //USE A COMMON DATE FOR ALL PAYMENTS REGISTERED DateTime paymentDate = LocaleHelper.LocalNow; //CONVERT GIFT CERTIFICATE PLACEHOLDERS INTO PAYMENT ITEMS LSDecimal totalGiftCertPayment = 0; foreach (BasketPaymentItem giftCertItem in giftCertPayments) { Payment giftCertPayment = giftCertItem.GetPaymentObject(); totalGiftCertPayment += giftCertPayment.Amount; giftCertPayment.OrderId = order.OrderId; giftCertPayment.PaymentMethodId = giftCertPaymentMethodId; giftCertPayment.PaymentDate = paymentDate; order.Payments.Add(giftCertPayment); giftCertPayment.Save(); } //IF PAYMENT DATA WAS PASSED WITH CHECKOUT REQUEST, ADD TO ORDER RECORD NOW if (checkoutRequest != null && checkoutRequest.Payment != null) { //BUILD A LIST OF PAYMENTS TO ADD TO THE ORDER BASED ON CONTENTS Payment originalPayment = checkoutRequest.Payment; //DETERMINE TOTAL PAYMENT REQUIRED FOR ITEMS LSDecimal remainingPaymentAmount = order.Items.TotalPrice(); //PRESERVE ACCOUNT DATA saveAccountData = originalPayment.AccountData; //DECIDE WHETHER PAYMENTS MUST BE DIVIDED BECAUSE OF ARB SUBSCRIPTIONS if (orderItemSubscriptions.Count > 0) { //LOOP EACH ORDER ITEM WITH A RECURRING SUBSCRIPTION foreach (int orderItemId in orderItemSubscriptions.Keys) { // THIS STORES THE DISCOUNT TO APPLY TO EACH SUBSCRIPTION PAYMENT LSDecimal subscriptionDiscount = 0; // THIS STORES ADDITIONAL DISCOUNT TO APPLY ONLY TO THE FIRST SUBSCRIPTION LSDecimal firstSubscriptionAdjustment = 0; // GET THE TOTAL DISCOUNTS APPLIED TO THIS ORDER ITEM (VALUE SHOULD BE NEGATIVE) LSDecimal totalItemAdjustments = GetOrderItemAdjustments(order, orderItemId); // IF THERE IS A DISCOUNT, DETERMINE PRO-RATED DISCOUNT FOR EACH PAYMENT (ITEM) if (totalItemAdjustments != 0) { // THERE IS A DISCOUNT THAT APPLIES TO THIS ORDER ITEM, WE NEED TO DETERMINE AMOUNT // SUBSCRIPTION DISCOUNT IS TOTAL DISCOUNT BY THE NUMBER OF SUBSCRIPTION ITEMS subscriptionDiscount = (LSDecimal)Math.Floor((decimal)(totalItemAdjustments / orderItemSubscriptions[orderItemId].Length)); // DETERMINE THE TOTAL DISCOUNT AMOUNT USING CALCULATED PER-SUBSCRIPTION VALUE LSDecimal calculatedTotal = subscriptionDiscount * orderItemSubscriptions[orderItemId].Length; // THE TOTAL DISCOUNT MUST MATCH THE LUMP SUM, SO CALCULATE ANY LEFTOVER DISCOUNT FOR THE FIRST PAYMENT firstSubscriptionAdjustment = (totalItemAdjustments - calculatedTotal); } // WE MUST DETERMINE THE PRICE OF THE ITEM (INCLUDING TAX) // GET THE ORDER ITEM FOR THIS RECURRING SUBSCRIPTION OrderItem orderItem = order.Items[order.Items.IndexOf(orderItemId)]; // GET THE PRICE OF THE ITEM (INCLUDING TAX) int billToProvinceId = ProvinceDataSource.GetProvinceIdByName(order.BillToCountryCode, order.BillToProvince); TaxAddress billingAddress = new TaxAddress(order.BillToCountryCode, billToProvinceId, order.BillToPostalCode); LSDecimal subscriptionPrice = TaxHelper.GetPriceWithTax(orderItem.Price + subscriptionDiscount, orderItem.TaxCodeId, billingAddress, billingAddress); // LOOP ALL THE SUBSCRIPTIONS AND CREATE CORRESPONDING PAYMENT ITEMS foreach (Subscription s in orderItemSubscriptions[orderItemId]) { // ADD PAYMENT ITEM ASSOCIATED WITH THE SUBSCRIPTION Payment arbPayment = new Payment(); arbPayment.SubscriptionId = s.SubscriptionId; arbPayment.Amount = subscriptionPrice + firstSubscriptionAdjustment; arbPayment.CurrencyCode = originalPayment.CurrencyCode; arbPayment.OrderId = order.OrderId; arbPayment.PaymentDate = paymentDate; arbPayment.PaymentMethodId = originalPayment.PaymentMethodId; arbPayment.PaymentMethodName = originalPayment.PaymentMethodName; arbPayment.PaymentStatus = PaymentStatus.Unprocessed; arbPayment.ReferenceNumber = originalPayment.ReferenceNumber; arbPayment.AccountData = saveAccountData; order.Payments.Add(arbPayment); arbPayment.Save(); //ACCOUNT DATA IS RESET AFTER SAVE IN CASE THE SAVE METHOD ALTERS IT BASED //ON MERCHANT SECURITY SETTINGS, WE STILL NEED THE VALUE FOR THE CHECKOUT PROCESS arbPayment.AccountData = saveAccountData; arbPayment.IsDirty = false; // SUBTRACT THIS PAYMENT FROM THE REMAINING TOTAL remainingPaymentAmount -= arbPayment.Amount; // RESET ADJUSTMENT THAT SHOULD APPLY TO FIRST SUBSCRIPTION ONLY firstSubscriptionAdjustment = 0; } } //CREATE AN ADDITIONAL PAYMENT IF THERE IS ANY AMOUNT LEFT TO BE COLLECTED if (remainingPaymentAmount > 0) { //NEED ONE PAYMENT FOR EACH SUBSCRIPTION Payment remainingPayment = new Payment(); remainingPayment.SubscriptionId = 0; if (remainingPaymentAmount >= originalPayment.Amount) { remainingPayment.Amount = originalPayment.Amount; } else { remainingPayment.Amount = remainingPaymentAmount; } remainingPayment.CurrencyCode = originalPayment.CurrencyCode; remainingPayment.OrderId = order.OrderId; remainingPayment.PaymentDate = paymentDate; remainingPayment.PaymentMethodId = originalPayment.PaymentMethodId; remainingPayment.PaymentMethodName = originalPayment.PaymentMethodName; remainingPayment.PaymentStatus = PaymentStatus.Unprocessed; remainingPayment.ReferenceNumber = originalPayment.ReferenceNumber; remainingPayment.AccountData = saveAccountData; order.Payments.Add(remainingPayment); remainingPayment.Save(); //ACCOUNT DATA IS RESET AFTER SAVE IN CASE THE SAVE METHOD ALTERS IT BASED //ON MERCHANT SECURITY SETTINGS, WE STILL NEED THE VALUE FOR THE CHECKOUT PROCESS remainingPayment.AccountData = saveAccountData; remainingPayment.IsDirty = false; } } else { originalPayment.PaymentId = 0; originalPayment.SubscriptionId = 0; originalPayment.PaymentStatus = PaymentStatus.Unprocessed; originalPayment.OrderId = order.OrderId; originalPayment.PaymentDate = paymentDate; order.Payments.Add(originalPayment); originalPayment.Save(); //ACCOUNT DATA IS RESET AFTER SAVE IN CASE THE SAVE METHOD ALTERS IT BASED //ON MERCHANT SECURITY SETTINGS, WE STILL NEED THE VALUE FOR THE CHECKOUT PROCESS originalPayment.AccountData = saveAccountData; originalPayment.IsDirty = false; } } }