Commits

Bertrand Le Roy  committed 7625c69 Merge

Merged in joshberry/nwazet.commerce/zipcode_tax (pull request #12)

US zip code tax provider

  • Participants
  • Parent commits ebb9563, 057af31

Comments (0)

Files changed (9)

File Drivers/ZipCodeTaxPartDriver.cs

+using Nwazet.Commerce.Models;
+using Orchard.ContentManagement;
+using Orchard.ContentManagement.Drivers;
+using Orchard.ContentManagement.Handlers;
+using Orchard.Environment.Extensions;
+
+namespace Nwazet.Commerce.Drivers {
+    [OrchardFeature("Nwazet.Taxes")]
+    public class ZipCodeTaxPartDriver : ContentPartDriver<ZipCodeTaxPart> {
+
+        protected override string Prefix {
+            get { return "NwazetCommerceUnitesStatesZipCodeTax"; }
+        }
+
+        //GET
+        protected override DriverResult Editor(ZipCodeTaxPart part, dynamic shapeHelper) {
+            return ContentShape("Parts_ZipCodeTax_Edit",
+                () => shapeHelper.EditorTemplate(
+                    TemplateName: "Parts/ZipCodeTax",
+                    Model: shapeHelper.ZipCodeTax(
+                        Tax: part,
+                        Prefix: Prefix),
+                    Prefix: Prefix));
+        }
+
+        //POST
+        protected override DriverResult Editor(ZipCodeTaxPart part, IUpdateModel updater, dynamic shapeHelper) {
+            updater.TryUpdateModel(part, Prefix, null, null);
+            // User will input a percentage
+            return Editor(part, shapeHelper);
+        }
+    }
+}

File Migrations/TaxMigrations.cs

 
             return 1;
         }
+
+        public int UpdateFrom1() {
+            ContentDefinitionManager.AlterTypeDefinition("ZipCodeTax", cfg => cfg
+                .WithPart("ZipCodeTaxPart"));
+            return 2;
+        }
     }
 }

File Models/ZipCodeTaxPart.cs

+using System;
+using System.Collections.Generic;
+using Nwazet.Commerce.Services;
+using Orchard.ContentManagement;
+using Orchard.Environment.Extensions;
+using System.ComponentModel.DataAnnotations;
+
+namespace Nwazet.Commerce.Models {
+    [OrchardFeature("Nwazet.Taxes")]
+    public class ZipCodeTaxPart : ContentPart, ITax {
+
+        public string Name {
+            get { return this.Retrieve(r => r.Name); }
+            set { this.Store(r => r.Name, value); }
+        }
+
+        public int Priority {
+            get { return this.Retrieve(r => r.Priority); }
+            set { this.Store(r => r.Priority, value); }
+        }
+
+        [RegularExpression(@"^(\d{5}(,\s?|\t)0?\.\d*\r*\n*)*$", 
+            ErrorMessage="Invalid rate format. Requires comma or tab seperated zip and rate values, one per line.")]
+        public string Rates {
+            get { return this.Retrieve(r => r.Rates); }
+            set { this.Store(r => r.Rates, value); }
+        }
+
+        public Dictionary<string, double> GetRates() {
+
+            var rates = new Dictionary<string, double>();
+            string[] rateLines = Rates.Split(new string[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
+            foreach(var rateLine in rateLines) {
+                var rateSplit = rateLine.Split(new string[] { ",", "\t" }, StringSplitOptions.None);
+                rates.Add(rateSplit[0], Convert.ToDouble(rateSplit[1]));
+            }
+
+            return rates;
+        }
+
+        public double ComputeTax(IEnumerable<ShoppingCartQuantityProduct> productQuantities, double subtotal,
+            double shippingCost, string country, string zipCode) {
+
+            var rates = GetRates();
+            if (country == Country.UnitedStates && rates.ContainsKey(zipCode)) {
+                var rate = rates[zipCode];
+                var tax = (subtotal + shippingCost) * rate;
+                return tax;
+            }
+
+            return 0;
+        }
+    }
+}

File Nwazet.Commerce.Tests/Nwazet.Commerce.Tests.csproj

     <Compile Include="ProductAttributePricingTests.cs" />
     <Compile Include="ShippingServiceTest.cs" />
     <Compile Include="Stubs\SiteStub.cs" />
+    <Compile Include="TaxByZipTests.cs" />
     <Compile Include="TaxTests.cs" />
     <Compile Include="UspsServiceInternationalTests.cs" />
     <Compile Include="Stubs\UspsServiceStub.cs" />

File Nwazet.Commerce.Tests/TaxByZipTests.cs

+using System.Collections.Generic;
+using NUnit.Framework;
+using Nwazet.Commerce.Models;
+using Nwazet.Commerce.Services;
+using Nwazet.Commerce.Tests.Helpers;
+using System;
+using System.Linq;
+
+namespace Nwazet.Commerce.Tests
+{
+    class TaxByZipTests
+    {
+        private static readonly string CsvRates = "52411,.07\r\n52627,.05\r\n52405,.1\r\n52412,.08";
+        private static readonly string TabRates = "52411\t.07\n52627\t.05\n52405\t.1\n52412\t.08";
+
+        [Test]
+        public void RightTaxAppliesToCsvRates() {
+            var csvZipTax = new ZipCodeTaxPart();
+            ContentHelpers.PreparePart<ZipCodeTaxPart>(csvZipTax, "Tax");
+            csvZipTax.Rates = CsvRates;
+            
+            var taxProvider = new FakeTaxProvider(new[] { csvZipTax });
+            var cart = ShoppingCartHelpers.PrepareCart(null, new[] { taxProvider });
+            
+            var subtotal = cart.Subtotal();
+            cart.Country = "United States";
+            cart.ZipCode = "52627";
+
+            CheckTaxes(cart.Taxes().Amount, 6.95);
+        }
+
+        [Test]
+        public void RightTaxAppliesToTabRates() {
+            var tabZipTax = new ZipCodeTaxPart();
+            ContentHelpers.PreparePart<ZipCodeTaxPart>(tabZipTax, "Tax");
+            tabZipTax.Rates = TabRates;
+
+            var taxProvider = new FakeTaxProvider(new[] { tabZipTax });
+            var cart = ShoppingCartHelpers.PrepareCart(null, new[] { taxProvider });
+
+            var subtotal = cart.Subtotal();
+            cart.Country = "United States";
+            cart.ZipCode = "52412";
+
+            CheckTaxes(cart.Taxes().Amount, 11.12);
+        }
+
+        [Test]
+        public void TaxDoesNotApplyToNonMatchingZip() {
+            var csvZipTax = new ZipCodeTaxPart();
+            ContentHelpers.PreparePart<ZipCodeTaxPart>(csvZipTax, "Tax");
+            csvZipTax.Rates = CsvRates;
+
+            var taxProvider = new FakeTaxProvider(new[] { csvZipTax });
+            var cart = ShoppingCartHelpers.PrepareCart(null, new[] { taxProvider });
+
+            var subtotal = cart.Subtotal();
+            cart.Country = "United States";
+            cart.ZipCode = "90210";
+
+            Assert.IsNull(cart.Taxes());
+        }
+
+        private static void CheckTaxes(double actualTax, double expectedTax) {
+            const double epsilon = 0.001;
+            Assert.That(
+                    Math.Abs(expectedTax - actualTax),
+                    Is.LessThan(epsilon));
+        }
+
+        private class FakeTaxProvider : ITaxProvider {
+            private readonly IEnumerable<ZipCodeTaxPart> _taxes;
+
+            public FakeTaxProvider(IEnumerable<ZipCodeTaxPart> taxes) {
+                _taxes = taxes;
+            }
+
+            public string Name { get { return "FakeTaxProvider"; } }
+            public string ContentTypeName { get { return ""; } }
+            public IEnumerable<ITax> GetTaxes() {
+                return _taxes;
+            }
+        }
+    }
+}

File Nwazet.Commerce.csproj

     <Compile Include="Drivers\BundlePartDriver.cs" />
     <Compile Include="Drivers\OrderPartDriver.cs" />
     <Compile Include="Drivers\ProductSettingsPartDriver.cs" />
+    <Compile Include="Drivers\ZipCodeTaxPartDriver.cs" />
     <Compile Include="Drivers\StateOrCountryTaxPartDriver.cs" />
     <Compile Include="Drivers\UspsShippingMethodPartDriver.cs" />
     <Compile Include="Drivers\UspsSettingsPartDriver.cs" />
     <Compile Include="Models\PriceTier.cs" />
     <Compile Include="Models\ProductAttributeValue.cs" />
     <Compile Include="Models\ProductSettingsPart.cs" />
+    <Compile Include="Models\ZipCodeTaxPart.cs" />
     <Compile Include="Models\StateOrCountryTaxPart.cs" />
     <Compile Include="Models\StateOrCountryTaxPartRecord.cs" />
     <Compile Include="Models\CreditCardCharge.cs" />
     <Compile Include="Services\OrderService.cs" />
     <Compile Include="Services\ProductAttributeService.cs" />
     <Compile Include="Services\ShippingService.cs" />
+    <Compile Include="Services\ZipCodeTaxProvider.cs" />
     <Compile Include="Services\StateOrCountryTaxProvider.cs" />
     <Compile Include="Services\StripeWebService.cs" />
     <Compile Include="Services\TieredPriceProvider.cs" />
     <Content Include="Views\EditorTemplates\Parts\ProductSettings.cshtml" />
     <Content Include="Views\Parts\Product.PriceTiers.cshtml" />
   </ItemGroup>
+  <ItemGroup>
+    <Content Include="Views\EditorTemplates\Parts\ZipCodeTax.cshtml" />
+  </ItemGroup>
   <PropertyGroup>
     <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
     <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

File Services/ZipCodeTaxProvider.cs

+using System.Collections.Generic;
+using Nwazet.Commerce.Models;
+using Orchard.ContentManagement;
+using Orchard.Environment.Extensions;
+using Orchard.Localization;
+
+namespace Nwazet.Commerce.Services {
+    [OrchardFeature("Nwazet.Taxes")]
+    public class ZipCodeTaxProvider : ITaxProvider {
+        private readonly IContentManager _contentManager;
+        private Localizer T { get; set; }
+
+        public ZipCodeTaxProvider(IContentManager contentManager) {
+            _contentManager = contentManager;
+            T = NullLocalizer.Instance;
+        }
+
+        public string ContentTypeName {
+            get { return "ZipCodeTax"; }
+        }
+
+        public string Name {
+            get { return T("US Zip Code Tax").Text; }
+        }
+
+        public IEnumerable<ITax> GetTaxes() {
+            return _contentManager
+                .Query<ZipCodeTaxPart>()
+                .ForVersion(VersionOptions.Published)
+                .List();
+        }
+    }
+}

File Views/EditorTemplates/Parts/ZipCodeTax.cshtml

+@using Nwazet.Commerce.Models
+@using Nwazet.Commerce.Services
+@{
+    var zipCodeTaxPart = (ZipCodeTaxPart)Model.Tax;
+}
+<fieldset>
+    <label class="sub" for="@Html.Id("Name")">@T("Name")</label><br />
+    @Html.TextBox("Name", zipCodeTaxPart.Name, new { @class = "text text-large" })<br />
+    <span class="hint">@T("Title displayed to users at checkout.")</span><br />
+    <label class="sub" for="@Html.Id("Rates")">@T("Zip Code Rates")</label><br />
+    @Html.TextArea("Rates", zipCodeTaxPart.Rates)<br />
+    <span class="hint">@T("One line for each zip/rate with the values seperated by a comma or tab. Tab seperated values are also supported to allow copying and pasting from spreadsheets.")</span><br />
+    <label class="sub" for="@Html.Id("Priority")">@T("Priority")</label><br />
+    @Html.TextBox("Priority", zipCodeTaxPart.Priority, new { @class = "text text-small" })<br />
+    <span class="hint">@T("The priority to apply this tax. The highest priority wins. This makes it possible to create a hierarchy of taxes.")</span><br />
+</fieldset>

File placement.info

   <Place Parts_WeightBasedShippingMethod_Edit="Content:2"/>
   <Place Parts_SizeBasedShippingMethod_Edit="Content:2"/>
   <Place Parts_StateOrCountryTax_Edit="Content:2"/>
+  <Place Parts_ZipCodeTax_Edit="Content:2"/>
   <Place Parts_Bundle="Content:3.2"/>
   <Place Parts_Bundle_Edit="Content:3.2"/>
   <Place Parts_Discount_Edit="Content:2"/>