From f9d0f927da578de4124320c1943461d0078a040a Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Wed, 26 Jan 2022 10:59:54 -0600 Subject: [PATCH 01/40] adding vertex and writing a contributing guide --- .../Integrations/OCIntegrationCommand.cs | 20 ++ .../Integrations/OCIntegrationConfig.cs | 20 ++ .../Integrations/README.md | 52 +++++ .../Integrations/Tax/ITaxCalculator.cs | 104 ++++++++++ .../Tax/Vertex/Mappers/VertexRequestMapper.cs | 102 ++++++++++ .../Vertex/Mappers/VertexResponseMapper.cs | 54 ++++++ .../Models/VertexCalculateTaxRequest.cs | 75 ++++++++ .../Models/VertexCalculateTaxResponse.cs | 37 ++++ .../Tax/Vertex/Models/VertexCurrency.cs | 13 ++ .../Tax/Vertex/Models/VertexCustomer.cs | 21 ++ .../Tax/Vertex/Models/VertexDiscount.cs | 15 ++ .../Tax/Vertex/Models/VertexException.cs | 19 ++ .../Tax/Vertex/Models/VertexJurisdiction.cs | 52 +++++ .../Tax/Vertex/Models/VertexLineItem.cs | 42 ++++ .../Tax/Vertex/Models/VertexLocation.cs | 18 ++ .../Vertex/Models/VertexResponseLineItem.cs | 180 ++++++++++++++++++ .../Tax/Vertex/Models/VertexSeller.cs | 11 ++ .../Tax/Vertex/Models/VertexTokenResponse.cs | 13 ++ .../Integrations/Tax/Vertex/README.md | 42 ++++ .../Integrations/Tax/Vertex/VertexClient.cs | 70 +++++++ .../Tax/Vertex/VertexOCIntegrationCommand.cs | 41 ++++ .../Tax/Vertex/VertexOCIntegrationConfig.cs | 13 ++ 22 files changed, 1014 insertions(+) create mode 100644 library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/README.md create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/ITaxCalculator.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Mappers/VertexRequestMapper.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Mappers/VertexResponseMapper.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCalculateTaxRequest.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCalculateTaxResponse.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCurrency.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCustomer.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexDiscount.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexException.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexJurisdiction.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexLineItem.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexLocation.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexResponseLineItem.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexSeller.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexTokenResponse.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/README.md create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexClient.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexOCIntegrationCommand.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexOCIntegrationConfig.cs diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs b/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs new file mode 100644 index 0000000..668cd75 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + /// + /// A base class that all Integration Config classes should extend. Contains environment variables needed for that integration. + /// + public class OCIntegrationConfig + { + } + + /// + /// A base class that all Integration Command classes should extend. Exposes methods that are the behaviors of the integration. + /// + public class OCIntegrationCommand + { + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs new file mode 100644 index 0000000..668cd75 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + /// + /// A base class that all Integration Config classes should extend. Contains environment variables needed for that integration. + /// + public class OCIntegrationConfig + { + } + + /// + /// A base class that all Integration Command classes should extend. Exposes methods that are the behaviors of the integration. + /// + public class OCIntegrationCommand + { + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md new file mode 100644 index 0000000..2d01fd8 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -0,0 +1,52 @@ +# Contributing Guide For New Integrations + +Guidelines for adding a new integration to the Catalyst library. + +## Basics + +Creating integrations as part of this .NET Catalyst project means that they will be delivered as a code library through a Nuget package. Each integration should expose functionality to interact with 1 external service and should not depend any other integrations. There is a tension between providing too little functionality (creating a generic API client) and too much (limiting future flexibility). The key to this balance is the methods of the contract your integration exposes. + +## Exposed Contracts + +All integrations should include two classes designed to be exposed and consumed by solutions - an `OCIntegrationConfig` and an `OCIntegrationCommand`. The config is a POCO which contains properties for all the environment variables and secrets needed to authenticate to the service. The command exposes the functionality of your integration in methods. So, for an example service called "Mississippi" you would create the classes below. + +```c# +public class MississippiOCIntegrationConfig : OCIntegrationConfig +{ + public string ApiKey { get; set;} + ... ect +} + +public class MississippiOCIntegrationCommand : OCIntegrationCommand, IRiver +{ + protected MississippiOCIntegrationConfig _config; + + public MississippiOCIntegrationCommand(MississippiOCIntegrationConfig config) + { + _config = config; // used to auth to service + } + + public async Task GetRiverLength() + { + + } + + ... ect +} +``` + +Your integration can contain other classes, even public ones, but these two manditory classes form the exposed surface of your integration - designed for use in other projects. + +## Interfaces + +A key goal of these integrations is interopability. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like ITaxCalculator. Please check the existing interfaces and see if any apply to your integration's problem domain. If they do, make sure your OCIntegrationCommand implements those interfaces. Feel free open issues recommending changes to the interfaces. + +## Other Guidelines + + - Keep the number of properties and methods on your exposed contracts to the minimum required. Do a small amount well. + - Handle error scenarios within your integration by throwing a CatalystBaseException. + - Avoid adding a nuget package for your service's SDK. This will lead to bloat as many projects may use this library without using your service. Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. + - Write unit tests against the Command methods. Mock API reponses from your service using Flurl (https://flurl.dev/docs/testable-http/) or something simuliar. + - Please make sure to include a README.md file with your integration. It should match the format of the existing integrations. + - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. + - Generally follow the folder structure and code patterns of existing integrations. \ No newline at end of file diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/ITaxCalculator.cs b/library/OrderCloud.Catalyst/Integrations/Tax/ITaxCalculator.cs new file mode 100644 index 0000000..e9f16ef --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/ITaxCalculator.cs @@ -0,0 +1,104 @@ +using OrderCloud.SDK; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace OrderCloud.Catalyst +{ + /// + /// An interface to define expected responses from tax calculation. Meant to be used in OrderCloud ecommerce checkout. + /// + public interface ITaxCalculator + { + /// + /// Calculates tax for an order without creating any records. Use this to display tax amount to user prior to order submit. + /// + Task CalculateEstimateAsync(OrderWorksheet orderWorksheet, List promotions); + /// + /// Creates a tax transaction record in the calculating system. Use this once per order - on order submit, payment capture, or fulfillment. + /// + Task CommitTransactionAsync(OrderWorksheet orderWorksheet, List promotions); + } + + /// + /// Represents the details of the tax costs on one Order. + /// + public class OrderTaxCalculation + { + /// + /// ID of the OrderCloud Order + /// + public string OrderID { get; set; } + /// + /// An ID from the tax calculation system that identifies this transaction or calculation + /// + public string ExternalTransactionID { get; set; } + /// + /// The total tax to be paid by the purchaser on the order + /// + public decimal TotalTax { get; set; } + /// + /// Tax details by line item. Does not include possible shipping tax. + /// + public List LineItems { get; set; } = new List(); + /// + /// Taxes that apply across the order. For example, shipping tax. + /// + public List OrderLevelTaxes { get; set; } = new List(); + } + + /// + /// Represents the details of the tax costs on one LineItem. Does not include possible shipping tax. + /// + public class LineItemTaxCalculation + { + /// + /// ID of the OrderCloud line item + /// + public string LineItemID { get; set; } + /// + /// The sum of taxes that apply specifically to this line item. Does not include possible shipping tax. + /// + public decimal LineItemTotalTax { get; set; } + /// + /// Taxes that apply specifically to this line item. Does not include possible shipping tax. + /// + public List LineItemLevelTaxes { get; set; } = new List(); + } + + /// + /// A tax cost levied by one juristdiction and applied to some element of an Order (lineItem, shipping, ect.). + /// + public class TaxDetails + { + /// + /// The tax to be paid by the purchaser + /// + public decimal Tax { get; set; } + /// + /// The amount of the line item cost on which tax applies + /// + public decimal Taxable { get; set; } + /// + /// The amount of the line item cost on which tax does not apply + /// + public decimal Exempt { get; set; } + /// + /// A description of the tax. + /// + public string TaxDescription { get; set; } + /// + /// The level of the authority collecting tax, e.g. federal, state, city. + /// + public string JurisdictionLevel { get; set; } + /// + /// The name of the authority collecting tax. + /// + public string JurisdictionValue { get; set; } + /// + /// ID of the ship estimate this tax applies to. Null if not a shipping tax. + /// + public string ShipEstimateID { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Mappers/VertexRequestMapper.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Mappers/VertexRequestMapper.cs new file mode 100644 index 0000000..e62915c --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Mappers/VertexRequestMapper.cs @@ -0,0 +1,102 @@ +using OrderCloud.SDK; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public static class VertexRequestMapper + { + public static VertexCalculateTaxRequest ToVertexCalculateTaxRequest(this OrderWorksheet order, List promosOnOrder, string companyCode, VertexSaleMessageType type) + { + var itemLines = order.LineItems.Select(li => ToVertexLineItem(li)); + var shippingLines = order.ShipEstimateResponse.ShipEstimates.Select(se => + { + var firstLi = order.LineItems.First(li => li.ID == se.ShipEstimateItems.First().LineItemID); + return ToVertexLineItem(se, firstLi.ShippingAddress); + }); + + return new VertexCalculateTaxRequest() + { + postingDate = DateTime.Now.ToString("yyyy-MM-dd"), + saleMessageType = type, + transactionType = VertexTransactionType.SALE, + transactionId = order.Order.ID, + seller = new VertexSeller() + { + company = companyCode + }, + customer = new VertexCustomer() + { + customerCode = new VertexCustomerCode() + { + classCode = order.Order.FromUserID, + value = order.Order.FromUser.Email + }, + }, + lineItems = itemLines.Concat(shippingLines).ToList() + }; + } + + public static VertexLineItem ToVertexLineItem(LineItem lineItem) + { + return new VertexLineItem() + { + customer = new VertexCustomer() + { + destination = lineItem.ShippingAddress.ToVertexLocation(), + }, + product = new VertexProduct() + { + productClass = lineItem.Product.ID, + value = lineItem.Product.Name + }, + quantity = new VertexMeasure() + { + value = lineItem.Quantity + }, + unitPrice = (double) lineItem.UnitPrice, + lineItemId = lineItem.ID, + extendedPrice = (double) lineItem.LineTotal // this takes precedence over quanitity and unit price in determining tax cost + }; + } + + public static VertexLineItem ToVertexLineItem(ShipEstimate shipEstimate, Address shipTo) + { + var selectedMethod = shipEstimate.ShipMethods.First(m => m.ID == shipEstimate.SelectedShipMethodID); + return new VertexLineItem() + { + customer = new VertexCustomer() + { + destination = shipTo.ToVertexLocation(), + }, + product = new VertexProduct() + { + productClass = "shipping_code", + value = selectedMethod.Name + }, + quantity = new VertexMeasure() + { + value = 1 + }, + unitPrice = (double) selectedMethod.Cost, + lineItemId = shipEstimate.ID, + }; + } + + public static VertexLocation ToVertexLocation(this Address address) + { + return new VertexLocation() + { + streetAddress1 = address.Street1, + streetAddress2 = address.Street2, + city = address.City, + mainDivision = address.State, + postalCode = address.Zip, + country = address.Country + }; + } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Mappers/VertexResponseMapper.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Mappers/VertexResponseMapper.cs new file mode 100644 index 0000000..616a147 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Mappers/VertexResponseMapper.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public static class VertexResponseMapper + { + public static OrderTaxCalculation ToOrderTaxCalculation(this VertexCalculateTaxResponse response) + { + var shippingLines = response.lineItems?.Where(line => line.product.productClass == "shipping_code") ?? new List(); + var itemLines = response.lineItems?.Where(line => line.product.productClass != "shipping_code") ?? new List(); + + return new OrderTaxCalculation() + { + OrderID = response.transactionId, + ExternalTransactionID = response.transactionId, + TotalTax = (decimal) response.totalTax, + LineItems = itemLines.Select(ToItemTaxDetails).ToList(), + OrderLevelTaxes = shippingLines.SelectMany(ToShippingTaxDetails).ToList() + }; + } + + public static IEnumerable ToShippingTaxDetails(this VertexResponseLineItem transactionLineModel) + { + return transactionLineModel.taxes?.Select(detail => detail.ToTaxDetails(transactionLineModel.lineItemId)) ?? new List(); + } + + public static LineItemTaxCalculation ToItemTaxDetails(this VertexResponseLineItem transactionLineModel) + { + return new LineItemTaxCalculation() + { + LineItemID = transactionLineModel.lineItemId, + LineItemTotalTax = (decimal) transactionLineModel.totalTax, + LineItemLevelTaxes = transactionLineModel.taxes?.Select(detail => detail.ToTaxDetails(null)).ToList() ?? new List() + }; + } + + public static TaxDetails ToTaxDetails(this VertexTax detail, string shipEstimateID) + { + return new TaxDetails() + { + Tax = (decimal) detail.calculatedTax, + Taxable = (decimal) detail.taxable, + Exempt = 0, // we don't get a property back for exempt + TaxDescription = detail.impositionType.value, + JurisdictionLevel = detail.jurisdiction.jurisdictionLevel.ToString(), + JurisdictionValue = detail.jurisdiction.value, + ShipEstimateID = shipEstimateID + }; + } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCalculateTaxRequest.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCalculateTaxRequest.cs new file mode 100644 index 0000000..0f323e8 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCalculateTaxRequest.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class VertexCalculateTaxRequest + { + public VertexSaleMessageType saleMessageType { get; set; } + public VertexSeller seller { get; set; } + public VertexCustomer customer { get; set; } + public VertexDiscount discount { get; set; } + public List lineItems { get; set; } = new List(); + public string postingDate { get; set; } + public string transactionId { get; set; } + public VertexTransactionType transactionType { get; set; } + } + + public class VertexSitusOverride + { + public VertexTaxingLocation taxingLocation { get; set; } + } + + + public enum VertexTaxingLocation + { + ADMINISTRATIVE_DESTINATION, ADMINISTRATIVE_ORIGIN, DESTINATION, PHYSICAL_ORIGIN + } + + public class VertexImposition + { + public string impositionType { get; set; } + public VertexJurisdictionLevel value { get; set; } + } + + + public enum VertexTaxOverrideType + { + TAXABLE, NONTAXABLE + } + + public class VertexTaxOverride + { + public VertexTaxOverrideType overrideType { get; set; } + public string overrideReasonCode { get; set; } + } + + public enum VertexSaleMessageType + { + QUOTATION, + INVOICE, + DISTRIBUTE_TAX + } + + public class VertexTaxRegistrations + { + public string taxRegistrationNumber { get; set; } + public string isoCountryCode { get; set; } + public string mainDivision { get; set; } + public string hasPhysicalPresenceIndicator { get; set; } + public string impositionType { get; set; } + } + + + public enum VertexLocationCustomsStatus + { + FREE_CIRCULATION, BONDED_WAREHOUSE, FREE_TRADE_ZONE, TEMPORARY_IMPORT, INWARD_PROCESSING_RELIEF, OUTWARD_PROCESSING_RELIEF + } + + public class VertexExemptionCertificate + { + public string exemptionCertificateNumber { get; set; } + public string value { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCalculateTaxResponse.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCalculateTaxResponse.cs new file mode 100644 index 0000000..48b64b2 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCalculateTaxResponse.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class VertexResponse + { + public VertexResponseMeta meta { get; set; } + public T data { get; set; } + public List errors { get; set; } = new List(); + } + + public class VertexResponseMeta + { + public string app { get; set; } + public DateTime timeReceived { get; set; } + public int timeElapsed { get; set; } + } + + public class VertexResponseError + { + public string status { get; set; } // e.g. "401" + public string code { get; set; } // e.g. "UNAUTHORIZED" + public string title { get; set; } // e.g. "Unauthorized" + public string detail { get; set; } // e.g. "invalid access token" + } + + public class VertexCalculateTaxResponse : VertexCalculateTaxRequest + { + + public double subTotal { get; set; } + public double total { get; set; } + public double totalTax { get; set; } + public new List lineItems { get; set; } = new List(); + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCurrency.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCurrency.cs new file mode 100644 index 0000000..1c18678 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCurrency.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class VertexCurrency + { + public string isoCurrencyName { get; set; } + public string isoCurrencyCodeAlpha { get; set; } + public int isoCurrencyCodeNum { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCustomer.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCustomer.cs new file mode 100644 index 0000000..ce815f5 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCustomer.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class VertexCustomer + { + public VertexCustomerCode customerCode { get; set; } + public VertexLocation destination { get; set; } + } + + public class VertexCustomerCode + { + /// + /// A code used to represent groups of customers, vendors, dispatchers, or recipients who have similar taxability. Note: If you pass a classCode, you should not pass a TaxRegistrationNumber because the registration number does not apply to the entire class. + /// + public string classCode { get; set; } + public string value { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexDiscount.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexDiscount.cs new file mode 100644 index 0000000..ccd029d --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexDiscount.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class VertexDiscount + { + public double discountValue { get; set; } + public VertexDiscountType discountType { get; set; } + public string userDefinedDiscountCode { get; set; } + } + + public enum VertexDiscountType { DiscountAmount, DiscountPercent } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexException.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexException.cs new file mode 100644 index 0000000..c2a3cb4 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexException.cs @@ -0,0 +1,19 @@ +using OrderCloud.Catalyst; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class VertexException : CatalystBaseException + { + public VertexException(List errors) : + base( + "VertexTaxCalculationError", + "The vertex api returned an error: https://restconnect.vertexsmb.com/vertex-restapi/v1/sale", + errors, + int.Parse(errors.First().status + )) { } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexJurisdiction.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexJurisdiction.cs new file mode 100644 index 0000000..f0ff1cd --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexJurisdiction.cs @@ -0,0 +1,52 @@ +using System; + +namespace OrderCloud.Catalyst +{ + public class VertexJurisdiction + { + /// + /// The level of the jurisdiction for which the tax on the line item is applied. + /// + public VertexJurisdictionLevel jurisdictionLevel { get; set; } + /// + /// The Vertex-specific number that identifies a jurisdiction. + /// + public int jurisdictionId { get; set; } + /// + /// The date when the tax for the jurisdiction became effective. + /// + public DateTime effectiveDate { get; set; } + /// + /// The date after which the tax for the jurisdiction is no longer effective. + /// + public string expirationDate { get; set; } + /// + /// Jurisdiction code assigned by the relevant governmental authority. + /// + public string externalJurisdictionCode { get; set; } + public string value { get; set; } + } + + public class VertexJurisdictionOverride + { + public string impositionType { get; set; } + public VertexJurisdictionLevel jurisdictionLevel { get; set; } + public VertexRateOverride rateOverride { get; set; } + public VertexDeductionOverride deductionOverride { get; set; } + } + + public enum VertexJurisdictionLevel + { + APO, BOROUGH, CITY, COUNTRY, COUNTY, DISTRICT, FPO, LOCAL_IMPROVEMENT_DISTRICT, PARISH, PROVINCE, SPECIAL_PURPOSE_DISTRICT, STATE, TERRITORY, TOWNSHIP, TRADE_BLOCK, TRANSIT_DISTRICT + } + + public class VertexRateOverride + { + public double value { get; set; } + } + + public class VertexDeductionOverride + { + } + +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexLineItem.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexLineItem.cs new file mode 100644 index 0000000..c5c5de4 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexLineItem.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class VertexLineItem + { + public VertexCustomer customer { get; set; } + /// + /// A code from the host system that identifies the product, material, service, or SKU number. You can use the Vertex Central user interface to map your products to Product Categories. If the supplied Product and Product Class information is not recognized by the calculation engine, a general category indicating TPP is assigned. Required (1) if no Product Class information is supplied. If both are supplied, Product information supersedes Product Class information. + /// + public VertexProduct product { get; set; } + /// + /// A standardized, unique code for the product or service. + /// + public VertexMeasure quantity { get; set; } + public double unitPrice { get; set; } + public double extendedPrice { get; set; } + public VertexDiscount discount { get; set; } + /// + /// An identifier that further defines the line item and corresponds to the transaction stored in the host system. This parameter is needed to perform synchronization services, but it is not used for reporting purposes. + /// + public string lineItemId { get; set; } + } + + public class VertexMeasure + { + public double value { get; set; } + } + + public class VertexProduct + { + public string productClass { get; set; } + public string value { get; set; } + } + + public enum VertexTransactionType + { + SALE, RENTAL, LEASE + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexLocation.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexLocation.cs new file mode 100644 index 0000000..464751a --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexLocation.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class VertexLocation + { + public string streetAddress1 { get; set; } + public string streetAddress2 { get; set; } + public string city { get; set; } + public string mainDivision { get; set; } // e.g. state + public string subDivision { get; set; } // e.g. county + public string postalCode { get; set; } + public string country { get; set; } + + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexResponseLineItem.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexResponseLineItem.cs new file mode 100644 index 0000000..f3a858d --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexResponseLineItem.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class VertexResponseLineItem : VertexLineItem + { + public double totalTax { get; set; } + public List taxes { get; set; } = new List(); + } + + public class VertexTax + { + /// + /// The name of jurisdiction to which a tax is applied. + /// + public VertexJurisdiction jurisdiction { get; set; } + /// + /// Amount of tax calculated by the calculation engine. + /// + public double calculatedTax { get; set; } + /// + /// For Buyer Input tax and Seller Import tax, this rate is calculated based on the Extended Price and Tax Amount (Import or Input) passed in the Request message. If you total the Extended Price and Tax Amounts before passing them in, this rate is an average. + /// + public double effectiveRate { get; set; } + /// + /// Amount of the line item not subject to tax due to exempt status. + /// + public double exempt { get; set; } + /// + /// Amount of the line item not subject to tax due to nontaxable status. + /// + public double nonTaxable { get; set; } + /// + /// Amount of the line item subject to tax. + /// + public double taxable { get; set; } + /// + /// The name of the imposition to which the relevant tax rule belongs. This is assigned either by Vertex or by the user when setting up a user-defined imposition in the Vertex Central user interface. + /// + public VertexResponseImposition imposition { get; set; } + /// + /// The type description assigned to the imposition. When multiple impositions are imposed within a jurisdiction, each must have a different type. This is assigned either by Vertex or by the user when setting up a user-defined imposition in the Vertex Central user interface. + /// + public VertexResponseImposition impositionType { get; set; } + public VertexRule taxRuleId { get; set; } + public VertexRule maxTaxRuleId { get; set; } + public VertexRule basisRuleId { get; set; } + public VertexRule recoverableRuleId { get; set; } + public VertexCertificateNumber certificateNumber { get; set; } + /// + /// The Registration ID for the Seller associated with this line item tax. + /// + public string sellerRegistrationId { get; set; } + /// + /// The Registration ID for the Buyer associated with this line item tax. + /// + public string buyerRegistrationId { get; set; } + /// + /// The Registration ID for the Owner associated with this line item tax. + /// + public string ownerRegistrationId { get; set; } + public List invoiceTextCodes { get; set; } = new List(); + /// + /// Additional information about a tax, to be returned to your host system after tax calculation. To supply a value for this field, you need to set up a post-calculation Tax Assist rule. Note: The text you add to this field is not stored in the Tax Journal. + /// + public string summaryInvoiceText { get; set; } + /// + /// System determination of taxable status based on situs and item type. Note: Tax amounts with a value of Deferred are payable at a later time and should NOT be written to the invoice. + /// + public VertexTaxResult taxResult { get; set; } + /// + /// System determined tax type, based on situs, transaction type, and party role type (perspective) + /// + public VertexTaxType taxType { get; set; } + /// + /// A code that can be used to classify the transaction at the tax level for reporting or tax return filing purposes. + /// + public string vertexTaxCode { get; set; } + /// + /// Indicates that max tax was applied to this line item of the invoice. + /// + public bool maxTaxIndicator { get; set; } + /// + /// The situs determined by the calculation engine for the line item. + /// + public VertexTaxingLocation situs { get; set; } + /// + /// Indicates that the taxpayer company is not registered in the taxing jurisdiction. + /// + public bool notRegisteredIndicator { get; set; } + /// + /// Identifies whether tax is Input VAT, Output VAT, or both according to the perspective of the transaction to allow for Reverse Charges. + /// + public VertexInputOutput inputOutputType { get; set; } + /// + /// A user-defined code for the tax, to be returned to your host system after tax calculation. To return a value for this field, set up a post-calculation Tax Assist rule. + /// + public string taxCode { get; set; } + /// + /// A code that indicates the reason for the exception that was applied to this tax. + /// + public string reasonCode { get; set; } + public int filingCategoryCode { get; set; } + /// + /// Indicates whether or not the product is a service. + /// + public bool isService { get; set;} + /// + /// A Vertex-defined classification of the applicable tax rule for returns filing purposes. Possible values are: Luxury Rate, Standard Rate, Reduced Rate 1, Reduced Rate 2, Reduced Rate 3, Reduced Rate 4, Reduced Rate 5, User Defined, Zero Rate, Outside the Scope. + /// + public string rateClassification { get; set; } + /// + /// The party who is responsible for the sales tax. Note: Tax amounts with a value of Seller cannot be charged to your customer and should NOT be written to the invoice. + /// + public VertexParty taxCollectedFromParty { get; set; } + /// + /// Specifies the applicable tax structure when the transaction includes a flat fee or quantity-based fee. + /// + public VertexTaxStructure taxStructure { get; set; } + } + + public enum VertexTaxStructure + { + BRACKET, FLAT_TAX, QUANTITY, SINGLE_RATE, TIERED + } + + public enum VertexParty + { + SELLER, BUYER + } + + public enum VertexInputOutput + { + INPUT, IMPORT, OUTPUT, INPUT_OUTPUT + } + + public enum VertexTaxType + { + SALES, SELLER_USE, CONSUMERS_USE, VAT, IMPORT_VAT, NONE + } + + public enum VertexTaxResult + { + TAXABLE, NONTAXABLE, EXEMPT, DPPAPPLIED, NO_TAX, DEFERRED + } + + public class VertexCertificateNumber + { + public string certificateType { get; set; } + public string value { get; set; } + } + + public class VertexRule + { + /// + /// Identifier for user-defined rules. This identifier is useful for troubleshooting. The default value is False, Vertex-defined rule. + /// + public bool userDefined { get; set; } + /// + /// Indicates whether this rule applies as a Sales Tax Holiday. + /// + public bool salesTaxHolidayIndicator { get; set; } + public string value { get; set; } + } + + public class VertexResponseImposition + { + /// + /// Identifier for user-defined rules. This identifier is useful for troubleshooting. The default value is False, Vertex-defined rule. + /// + public bool userDefined { get; set; } + /// + /// The Vertex internal identifier for the impositionType. + /// + public int impositionTypeId { get; set; } + public string value { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexSeller.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexSeller.cs new file mode 100644 index 0000000..397230c --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexSeller.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class VertexSeller + { + public string company { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexTokenResponse.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexTokenResponse.cs new file mode 100644 index 0000000..7f0ca8c --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexTokenResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class VertexTokenResponse + { + public string access_token { get; set; } + public int expires_in { get; set; } + public string token_type { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/README.md b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/README.md new file mode 100644 index 0000000..3b176fa --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/README.md @@ -0,0 +1,42 @@ +# Vertex Integration For OrderCloud Headstart + +## Scope of this integration +This integration calculates sales tax for an Order using the vertex cloud API. It is part of the open source Headstart project, which provides a complete, opinionated OrderCloud solution. It conforms to the [ITaxCalculator](../ordercloud.integrations.library/interfaces/ITaxCalculator.cs) interface. + +Use Cases: +- Sales Tax Estimate +- Finalized Order Forwarding + +## Vertex Basics +[Vertex](https://www.vertexinc.com/) is a cloud or on premise **sales and use tax solution**. Vertex Cloud integrates with leading e-commerce platforms and mid-market ERP systems. Customers can use Vertex Cloud to manage complex sales and use tax across multiple jurisdictions. Vertex Cloud provides tax calculations and signature-ready PDF returns in one comprehensive solution. + +## Sales Tax Estimate +The sales tax cost on an Order is first calculated in checkout after shipping selections are made and before payment. Following that, they are updated whenever the order is changed. + +**Vertex Side -** Get a tax estimate in Vertex Cloud by calling the [Tax Calculate for Sellers API endpoint](https://developer.vertexcloud.com/api/docs/#operation/Sale_Post) with `saleMessageType` set to `QUOTATION`. + +**OrderCloud Side -** This integration should be triggered by the **`OrderCalculate`** Checkout Integration Event. Learn more about [checkout integration events](https://ordercloud.io/knowledge-base/order-checkout-integration); + +## Order Forwarding +A taxable transaction is committed to vertex asynchronously shortly following order submit. This enables businesses to easily file sales tax returns. OrderCloud guarantees the submitted order details provided will be unchanged since the most recent tax estimate displayed to the user. + +**Vertex Side -** Commit a transaction in Vertex Cloud by calling the [Tax Calculate for Sellers API endpoint](https://developer.vertexcloud.com/api/docs/#operation/Sale_Post) with `saleMessageType` set to `INVOICE`. + +**OrderCloud Side -** This integration should be triggered by the **`PostOrderSubmit`** Checkout Integration Event. Learn more about [checkout integration events](https://ordercloud.io/knowledge-base/order-checkout-integration); + +## Steps to use +- Set up the headstart application. This is process is throughly documented [here](https://github.com/ordercloud-api/headstart#initial-setup). +- Sign up for vertex and login to the [Vertex Portal](https://portal.vertexsmb.com/Home) to get the credentials required in the next step. +- Set environment variables required for Vertex authentication. See the vertex [authentication guide](https://developer.vertexcloud.com/access-token/). If you follow the headstart set up process env vars are stored in an Azure Config. +``` +VertexSettings:CompanyName +VertexSettings:ClientID +VertexSettings:ClientSecret +VertexSettings:Username +VertexSettings:Password +``` +- Set an environment variable to indicate you want to use Vertex for tax calculation. +``` +EnvironmentSettings:TaxProvider=Vertex +``` +- Redeploy your middleware and on the storefront, go through checkout pausing before entering payment. You will see tax calculated by Vertex! diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexClient.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexClient.cs new file mode 100644 index 0000000..bbdfe5c --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexClient.cs @@ -0,0 +1,70 @@ +using Flurl.Http; +using Flurl; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using OrderCloud.Catalyst; + +namespace OrderCloud.Catalyst +{ + public class VertexClient + { + protected const string ApiUrl = "https://restconnect.vertexsmb.com"; + protected const string AuthUrl = "https://auth.vertexsmb.com"; + protected readonly VertexOCIntegrationConfig _config; + protected VertexTokenResponse _token; + + public VertexClient(VertexOCIntegrationConfig config) + { + _config = config; + } + + public async Task CalculateTax(VertexCalculateTaxRequest request) + { + return await MakeRequest(() => + $"{ApiUrl}/vertex-restapi/v1/sale" + .WithOAuthBearerToken(_token.access_token) + .AllowAnyHttpStatus() + .PostJsonAsync(request) + ); + } + + protected async Task MakeRequest(Func> request) + { + if (_token?.access_token == null) + { + _token = await GetToken(_config); + } + var response = await (await request()).GetJsonAsync>(); + if (response.errors.Exists(e => e.detail == "invalid access token")) + { + // refresh the token + _token = await GetToken(_config); + // try the request again + response = await (await request()).GetJsonAsync>(); + } + + // Catch and throw any api errors + Require.That(response.errors.Count == 0, new VertexException(response.errors)); + + return response.data; + } + + protected async Task GetToken(VertexOCIntegrationConfig config) + { + var body = new + { + scope = "calc-rest-api", + grant_type = "password", + client_id = config.ClientID, + client_secret = config.ClientSecret, + username = config.Username, + password = config.Password + }; + var response = await $"{AuthUrl}/identity/connect/token".PostUrlEncodedAsync(body); + var token = await response.GetJsonAsync(); + return token; + } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexOCIntegrationCommand.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexOCIntegrationCommand.cs new file mode 100644 index 0000000..7b09670 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexOCIntegrationCommand.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using OrderCloud.SDK; + + +namespace OrderCloud.Catalyst +{ + public class VertexOCIntegrationCommand : ITaxCalculator + { + protected readonly VertexClient _client; + protected readonly VertexOCIntegrationConfig _config; + + public VertexOCIntegrationCommand(VertexOCIntegrationConfig config) + { + _config = config; + _client = new VertexClient(config); + } + + /// + /// Calculates tax for an order without creating any records. Use this to display tax amount to user prior to order submit. + /// + public async Task CalculateEstimateAsync(OrderWorksheet orderWorksheet, List promotions) => + await CalculateTaxAsync(orderWorksheet, promotions, VertexSaleMessageType.QUOTATION); + + /// + /// Creates a tax transaction record in the calculating system. Use this once on purchase, payment capture, or fulfillment. + /// + public async Task CommitTransactionAsync(OrderWorksheet orderWorksheet, List promotions) => + await CalculateTaxAsync(orderWorksheet, promotions, VertexSaleMessageType.INVOICE); + + protected async Task CalculateTaxAsync(OrderWorksheet orderWorksheet, List promotions, VertexSaleMessageType type) + { + var request = orderWorksheet.ToVertexCalculateTaxRequest(promotions, _config.CompanyName, type); + var response = await _client.CalculateTax(request); + var orderTaxCalculation = response.ToOrderTaxCalculation(); + return orderTaxCalculation; + } + } + } diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexOCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexOCIntegrationConfig.cs new file mode 100644 index 0000000..a2a76c1 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexOCIntegrationConfig.cs @@ -0,0 +1,13 @@ +using System; + +namespace OrderCloud.Catalyst +{ + public class VertexOCIntegrationConfig : OCIntegrationConfig + { + public string CompanyName { get; set; } + public string ClientID { get; set; } + public string ClientSecret { get; set; } + public string Username { get; set; } + public string Password { get; set; } + } +} From e0a6b187caeeb38d254604cae07e36296cf67a22 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Wed, 26 Jan 2022 11:12:58 -0600 Subject: [PATCH 02/40] update readme and folder structure --- .../Vertex/Mappers/VertexRequestMapper.cs | 0 .../Vertex/Mappers/VertexResponseMapper.cs | 0 .../Vertex/Models/VertexCalculateTaxRequest.cs | 0 .../Vertex/Models/VertexCalculateTaxResponse.cs | 0 .../Vertex/Models/VertexCurrency.cs | 0 .../Vertex/Models/VertexCustomer.cs | 0 .../Vertex/Models/VertexDiscount.cs | 0 .../Vertex/Models/VertexException.cs | 0 .../Vertex/Models/VertexJurisdiction.cs | 0 .../Vertex/Models/VertexLineItem.cs | 0 .../Vertex/Models/VertexLocation.cs | 0 .../Vertex/Models/VertexResponseLineItem.cs | 0 .../Vertex/Models/VertexSeller.cs | 0 .../Vertex/Models/VertexTokenResponse.cs | 0 .../{Tax => Implementations}/Vertex/README.md | 0 .../{Tax => Implementations}/Vertex/VertexClient.cs | 0 .../Vertex/VertexOCIntegrationCommand.cs | 0 .../Vertex/VertexOCIntegrationConfig.cs | 0 .../Integrations/{Tax => Interfaces}/ITaxCalculator.cs | 0 library/OrderCloud.Catalyst/Integrations/README.md | 8 ++++---- 20 files changed, 4 insertions(+), 4 deletions(-) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Mappers/VertexRequestMapper.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Mappers/VertexResponseMapper.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Models/VertexCalculateTaxRequest.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Models/VertexCalculateTaxResponse.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Models/VertexCurrency.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Models/VertexCustomer.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Models/VertexDiscount.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Models/VertexException.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Models/VertexJurisdiction.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Models/VertexLineItem.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Models/VertexLocation.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Models/VertexResponseLineItem.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Models/VertexSeller.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/Models/VertexTokenResponse.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/README.md (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/VertexClient.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/VertexOCIntegrationCommand.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Implementations}/Vertex/VertexOCIntegrationConfig.cs (100%) rename library/OrderCloud.Catalyst/Integrations/{Tax => Interfaces}/ITaxCalculator.cs (100%) diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Mappers/VertexRequestMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Mappers/VertexRequestMapper.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Mappers/VertexResponseMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Mappers/VertexResponseMapper.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCalculateTaxRequest.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxRequest.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCalculateTaxRequest.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxRequest.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCalculateTaxResponse.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxResponse.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCalculateTaxResponse.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxResponse.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCurrency.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCurrency.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCurrency.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCurrency.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCustomer.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCustomer.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexCustomer.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCustomer.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexDiscount.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexDiscount.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexDiscount.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexDiscount.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexException.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexException.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexException.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexException.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexJurisdiction.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexJurisdiction.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexJurisdiction.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexJurisdiction.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexLineItem.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLineItem.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexLineItem.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLineItem.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexLocation.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLocation.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexLocation.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLocation.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexResponseLineItem.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexResponseLineItem.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexResponseLineItem.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexResponseLineItem.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexSeller.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexSeller.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexSeller.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexSeller.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexTokenResponse.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexTokenResponse.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/Models/VertexTokenResponse.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexTokenResponse.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/README.md b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/README.md rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexClient.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexOCIntegrationCommand.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationCommand.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexOCIntegrationCommand.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationCommand.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexOCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationConfig.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/Vertex/VertexOCIntegrationConfig.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationConfig.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Tax/ITaxCalculator.cs b/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Tax/ITaxCalculator.cs rename to library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 2d01fd8..a16d7e4 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -35,18 +35,18 @@ public class MississippiOCIntegrationCommand : OCIntegrationCommand, IRiver } ``` -Your integration can contain other classes, even public ones, but these two manditory classes form the exposed surface of your integration - designed for use in other projects. +Your integration will likely contain other public classes but these two mandatory classes form the exposed surface of your integration - designed for use in other projects. ## Interfaces -A key goal of these integrations is interopability. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like ITaxCalculator. Please check the existing interfaces and see if any apply to your integration's problem domain. If they do, make sure your OCIntegrationCommand implements those interfaces. Feel free open issues recommending changes to the interfaces. +A key goal of these integrations is interoperability. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like ITaxCalculator. Please check under /Integrations/Interfaces to see if any apply to your integration's problem domain. If they do, make sure your OCIntegrationCommand implements those interfaces. Feel free open issues recommending changes to the interfaces. ## Other Guidelines - Keep the number of properties and methods on your exposed contracts to the minimum required. Do a small amount well. + - Under the folder `/Integrations/Implementations` create a folder with your service name to contain your files. At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. - Handle error scenarios within your integration by throwing a CatalystBaseException. - Avoid adding a nuget package for your service's SDK. This will lead to bloat as many projects may use this library without using your service. Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. - Write unit tests against the Command methods. Mock API reponses from your service using Flurl (https://flurl.dev/docs/testable-http/) or something simuliar. - - Please make sure to include a README.md file with your integration. It should match the format of the existing integrations. - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. - - Generally follow the folder structure and code patterns of existing integrations. \ No newline at end of file + - Aim to follow the code patterns of existing integrations. \ No newline at end of file From bfd45b6c325badfc2f01b5d13179520c63565d77 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Wed, 26 Jan 2022 11:16:51 -0600 Subject: [PATCH 03/40] wording tweak --- library/OrderCloud.Catalyst/Integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index a16d7e4..59bed97 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -4,7 +4,7 @@ Guidelines for adding a new integration to the Catalyst library. ## Basics -Creating integrations as part of this .NET Catalyst project means that they will be delivered as a code library through a Nuget package. Each integration should expose functionality to interact with 1 external service and should not depend any other integrations. There is a tension between providing too little functionality (creating a generic API client) and too much (limiting future flexibility). The key to this balance is the methods of the contract your integration exposes. +Creating integrations as part of this .NET Catalyst project means that they will be delivered as Nuget library. Each integration should expose functionality to interact with 1 external service and should not depend any other integrations. There is a tension between providing too little functionality (creating a generic API client) and too much (limiting future flexibility). The key to this balance is the methods of the contract your integration exposes. ## Exposed Contracts From 281ce1f1adacc7d8c71c5767654106bb95308774 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Wed, 26 Jan 2022 11:28:31 -0600 Subject: [PATCH 04/40] wording --- .../OrderCloud.Catalyst/Integrations/README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 59bed97..fe3f883 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -1,14 +1,14 @@ -# Contributing Guide For New Integrations +# Contributing Guide For Integrations Guidelines for adding a new integration to the Catalyst library. ## Basics -Creating integrations as part of this .NET Catalyst project means that they will be delivered as Nuget library. Each integration should expose functionality to interact with 1 external service and should not depend any other integrations. There is a tension between providing too little functionality (creating a generic API client) and too much (limiting future flexibility). The key to this balance is the methods of the contract your integration exposes. +Creating an integration in this project means it will be published in this [Nuget code library](https://www.nuget.org/packages/ordercloud-dotnet-catalyst/). Each integration should expose functionality to interact with 1 external service and should not depend any other integrations. There is a tension between providing too little "wrapper" functionality (creating a generic API client) and too much "wrapper" (an opinionated solution that limits use cases). The key to this balance is the method signatures of the contract your integration exposes. ## Exposed Contracts -All integrations should include two classes designed to be exposed and consumed by solutions - an `OCIntegrationConfig` and an `OCIntegrationCommand`. The config is a POCO which contains properties for all the environment variables and secrets needed to authenticate to the service. The command exposes the functionality of your integration in methods. So, for an example service called "Mississippi" you would create the classes below. +All integrations should include two classes designed to be exposed and consumed by solutions - an `OCIntegrationConfig` and an `OCIntegrationCommand`. The config is a POCO which contains properties for all the environment variables and secrets needed to authenticate to the service. The command exposes the functionality of your integration through methods. For an example service called "Mississippi" you would create the classes below. ```c# public class MississippiOCIntegrationConfig : OCIntegrationConfig @@ -39,14 +39,18 @@ Your integration will likely contain other public classes but these two mandator ## Interfaces -A key goal of these integrations is interoperability. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like ITaxCalculator. Please check under /Integrations/Interfaces to see if any apply to your integration's problem domain. If they do, make sure your OCIntegrationCommand implements those interfaces. Feel free open issues recommending changes to the interfaces. +A key goal of these integrations is interoperability. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like ITaxCalculator. Please check under [/Integrations/Interfaces](./Interfaces) to see if any apply to your integration's problem domain. If some do, make sure your OCIntegrationCommand implements those interfaces. If none do still create your integration but we aware we may look to standardize it in the future. Feel free open issues recommending changes or additions to the interfaces. ## Other Guidelines - Keep the number of properties and methods on your exposed contracts to the minimum required. Do a small amount well. - - Under the folder `/Integrations/Implementations` create a folder with your service name to contain your files. At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. + - Under [/Integrations/Implementations](./Implementations) create a folder with your service name (e.g. "Mississippi") to contain your files. At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. - Handle error scenarios within your integration by throwing a CatalystBaseException. - Avoid adding a nuget package for your service's SDK. This will lead to bloat as many projects may use this library without using your service. Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. - Write unit tests against the Command methods. Mock API reponses from your service using Flurl (https://flurl.dev/docs/testable-http/) or something simuliar. - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. - - Aim to follow the code patterns of existing integrations. \ No newline at end of file + - Aim to follow the code patterns of existing integrations. + +## Approval + +A disclaimer, whether a pull request with a new integration is accepted and published will ultimately depend on the approval of the OrderCloud team. \ No newline at end of file From 591f0f6eca7b7f9fe883fc421ed77b6735b7b7a9 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Wed, 26 Jan 2022 11:35:51 -0600 Subject: [PATCH 05/40] more tweaks --- library/OrderCloud.Catalyst/Integrations/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index fe3f883..c2ce0f5 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -4,7 +4,7 @@ Guidelines for adding a new integration to the Catalyst library. ## Basics -Creating an integration in this project means it will be published in this [Nuget code library](https://www.nuget.org/packages/ordercloud-dotnet-catalyst/). Each integration should expose functionality to interact with 1 external service and should not depend any other integrations. There is a tension between providing too little "wrapper" functionality (creating a generic API client) and too much "wrapper" (an opinionated solution that limits use cases). The key to this balance is the method signatures of the contract your integration exposes. +Creating an integration in this project means it will be published as part of a [Nuget code library](https://www.nuget.org/packages/ordercloud-dotnet-catalyst/). Each integration should expose functionality to interact with 1 external service and should not depend any other integrations. There is a tension between providing too little "wrapper" functionality (creating a generic API client) and too much "wrapper" (an opinionated solution that limits use cases). The key to this balance is the method signatures of the contract your integration exposes. ## Exposed Contracts @@ -14,7 +14,7 @@ All integrations should include two classes designed to be exposed and consumed public class MississippiOCIntegrationConfig : OCIntegrationConfig { public string ApiKey { get; set;} - ... ect + ... ect. } public class MississippiOCIntegrationCommand : OCIntegrationCommand, IRiver @@ -31,7 +31,7 @@ public class MississippiOCIntegrationCommand : OCIntegrationCommand, IRiver } - ... ect + ... ect. } ``` @@ -39,7 +39,7 @@ Your integration will likely contain other public classes but these two mandator ## Interfaces -A key goal of these integrations is interoperability. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like ITaxCalculator. Please check under [/Integrations/Interfaces](./Interfaces) to see if any apply to your integration's problem domain. If some do, make sure your OCIntegrationCommand implements those interfaces. If none do still create your integration but we aware we may look to standardize it in the future. Feel free open issues recommending changes or additions to the interfaces. +A key goal of these integrations is *interoperability*. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like [ITaxCalculator](./Interfaces/ITaxCalculator.cs). Please check under [/Integrations/Interfaces](./Interfaces) to see if any apply to your integration's problem domain. If some do, make sure your OCIntegrationCommand implements those interfaces. If none do, still create your integration but we aware we may look to standardize it in the future. Feel free open issues recommending changes or additions to the interfaces. ## Other Guidelines @@ -47,7 +47,7 @@ A key goal of these integrations is interoperability. In other words, if two ser - Under [/Integrations/Implementations](./Implementations) create a folder with your service name (e.g. "Mississippi") to contain your files. At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. - Handle error scenarios within your integration by throwing a CatalystBaseException. - Avoid adding a nuget package for your service's SDK. This will lead to bloat as many projects may use this library without using your service. Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. - - Write unit tests against the Command methods. Mock API reponses from your service using Flurl (https://flurl.dev/docs/testable-http/) or something simuliar. + - Write unit tests against the Command methods. Mock API reponses from your service using [Flurl test practices](https://flurl.dev/docs/testable-http/) or something simuliar. - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. - Aim to follow the code patterns of existing integrations. From f987d94447fd5c5b9ceb8ef2c58a680df0ed487c Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Wed, 26 Jan 2022 11:41:39 -0600 Subject: [PATCH 06/40] last tweaks --- library/OrderCloud.Catalyst/Integrations/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index c2ce0f5..2e6106e 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -26,7 +26,7 @@ public class MississippiOCIntegrationCommand : OCIntegrationCommand, IRiver _config = config; // used to auth to service } - public async Task GetRiverLength() + public async Task GetRiverLength() { } @@ -39,7 +39,7 @@ Your integration will likely contain other public classes but these two mandator ## Interfaces -A key goal of these integrations is *interoperability*. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like [ITaxCalculator](./Interfaces/ITaxCalculator.cs). Please check under [/Integrations/Interfaces](./Interfaces) to see if any apply to your integration's problem domain. If some do, make sure your OCIntegrationCommand implements those interfaces. If none do, still create your integration but we aware we may look to standardize it in the future. Feel free open issues recommending changes or additions to the interfaces. +A key goal of these integrations is *interoperability*. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like [ITaxCalculator](./Interfaces/ITaxCalculator.cs). Please check under [/Integrations/Interfaces](./Interfaces) to see if any apply to your integration's problem domain. If some do, make sure your OCIntegrationCommand implements those interfaces. If none do, still create your integration but be aware we may look to standardize it in the future. Feel free open issues recommending changes or additions to the interfaces. ## Other Guidelines @@ -47,7 +47,7 @@ A key goal of these integrations is *interoperability*. In other words, if two s - Under [/Integrations/Implementations](./Implementations) create a folder with your service name (e.g. "Mississippi") to contain your files. At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. - Handle error scenarios within your integration by throwing a CatalystBaseException. - Avoid adding a nuget package for your service's SDK. This will lead to bloat as many projects may use this library without using your service. Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. - - Write unit tests against the Command methods. Mock API reponses from your service using [Flurl test practices](https://flurl.dev/docs/testable-http/) or something simuliar. + - Write unit tests against the Command methods. Mock API reponses from your service using [Flurl test practices](https://flurl.dev/docs/testable-http/) or something similar. - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. - Aim to follow the code patterns of existing integrations. From 2eddce52c4a0efbc2c19d3622663a8f176a44466 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Wed, 26 Jan 2022 11:46:48 -0600 Subject: [PATCH 07/40] more detail about tests --- .../Integrations/README.md | 4 ++-- .../IntegrationTests/VertexTests.cs | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 tests/OrderCloud.Catalyst.Tests/IntegrationTests/VertexTests.cs diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 2e6106e..1006a3b 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -45,9 +45,9 @@ A key goal of these integrations is *interoperability*. In other words, if two s - Keep the number of properties and methods on your exposed contracts to the minimum required. Do a small amount well. - Under [/Integrations/Implementations](./Implementations) create a folder with your service name (e.g. "Mississippi") to contain your files. At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. - - Handle error scenarios within your integration by throwing a CatalystBaseException. + - Handle error scenarios like auth errors and bad requests within your integration by throwing a CatalystBaseException. - Avoid adding a nuget package for your service's SDK. This will lead to bloat as many projects may use this library without using your service. Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. - - Write unit tests against the Command methods. Mock API reponses from your service using [Flurl test practices](https://flurl.dev/docs/testable-http/) or something similar. + - Write unit tests against the Command methods in the OrderCloud.Catalyst.Tests project under `/IntegrationTests/[ServiceName]Tests.cs`. Mock API reponses from your service using [Flurl test practices](https://flurl.dev/docs/testable-http/) or something similar. - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. - Aim to follow the code patterns of existing integrations. diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/VertexTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/VertexTests.cs new file mode 100644 index 0000000..65cb207 --- /dev/null +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/VertexTests.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace OrderCloud.Catalyst.Tests +{ + [TestFixture] + public class VertexTests + { + [Test] + public async Task Test() + { + } + + } +} From 6fa6de29fa7cdb96c7dba6ba05e677fe501259d1 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Wed, 26 Jan 2022 11:47:54 -0600 Subject: [PATCH 08/40] wording --- library/OrderCloud.Catalyst/Integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 1006a3b..90698e5 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -47,7 +47,7 @@ A key goal of these integrations is *interoperability*. In other words, if two s - Under [/Integrations/Implementations](./Implementations) create a folder with your service name (e.g. "Mississippi") to contain your files. At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. - Handle error scenarios like auth errors and bad requests within your integration by throwing a CatalystBaseException. - Avoid adding a nuget package for your service's SDK. This will lead to bloat as many projects may use this library without using your service. Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. - - Write unit tests against the Command methods in the OrderCloud.Catalyst.Tests project under `/IntegrationTests/[ServiceName]Tests.cs`. Mock API reponses from your service using [Flurl test practices](https://flurl.dev/docs/testable-http/) or something similar. + - Write unit tests against your Command methods and put them in the OrderCloud.Catalyst.Tests project under `/IntegrationTests/[ServiceName]Tests.cs`. Mock API reponses from your service using [Flurl test practices](https://flurl.dev/docs/testable-http/) or something similar. - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. - Aim to follow the code patterns of existing integrations. From cc92cb67fa903f9baa4ddfcf5f502f37154b4d64 Mon Sep 17 00:00:00 2001 From: Oliver Heywood <38010640+oliverheywood451@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:49:21 -0600 Subject: [PATCH 09/40] Update README.md --- library/OrderCloud.Catalyst/Integrations/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 90698e5..94ac92f 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -4,7 +4,7 @@ Guidelines for adding a new integration to the Catalyst library. ## Basics -Creating an integration in this project means it will be published as part of a [Nuget code library](https://www.nuget.org/packages/ordercloud-dotnet-catalyst/). Each integration should expose functionality to interact with 1 external service and should not depend any other integrations. There is a tension between providing too little "wrapper" functionality (creating a generic API client) and too much "wrapper" (an opinionated solution that limits use cases). The key to this balance is the method signatures of the contract your integration exposes. +Creating an integration in this project means it will be published as part of a [Nuget code library](https://www.nuget.org/packages/ordercloud-dotnet-catalyst/). Each integration should expose functionality to interact with 1 external service and should not depend any other integrations. There is a natural tension between providing too little "wrapper" functionality (creating a generic API client) and too much "wrapper" (an opinionated solution that limits use cases). The key to this balance is the method signatures of the contract your integration exposes. ## Exposed Contracts @@ -53,4 +53,4 @@ A key goal of these integrations is *interoperability*. In other words, if two s ## Approval -A disclaimer, whether a pull request with a new integration is accepted and published will ultimately depend on the approval of the OrderCloud team. \ No newline at end of file +A disclaimer, whether a pull request with a new integration is accepted and published will ultimately depend on the approval of the OrderCloud team. From 3ea532dc535b04d8e246238e82de566844e5ecb9 Mon Sep 17 00:00:00 2001 From: Oliver Heywood <38010640+oliverheywood451@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:50:19 -0600 Subject: [PATCH 10/40] Update README.md --- library/OrderCloud.Catalyst/Integrations/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 94ac92f..00fa36b 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -16,7 +16,8 @@ public class MississippiOCIntegrationConfig : OCIntegrationConfig public string ApiKey { get; set;} ... ect. } - +``` +```c# public class MississippiOCIntegrationCommand : OCIntegrationCommand, IRiver { protected MississippiOCIntegrationConfig _config; From 405fb3422a5dbc06d34d6ac69105294f5d02ba29 Mon Sep 17 00:00:00 2001 From: Oliver Heywood <38010640+oliverheywood451@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:50:57 -0600 Subject: [PATCH 11/40] Update README.md --- library/OrderCloud.Catalyst/Integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 00fa36b..858b3f1 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -20,7 +20,7 @@ public class MississippiOCIntegrationConfig : OCIntegrationConfig ```c# public class MississippiOCIntegrationCommand : OCIntegrationCommand, IRiver { - protected MississippiOCIntegrationConfig _config; + protected readonly MississippiOCIntegrationConfig _config; public MississippiOCIntegrationCommand(MississippiOCIntegrationConfig config) { From 40d4e495b7d6b2ad4695360d9b62f123aed10e49 Mon Sep 17 00:00:00 2001 From: Oliver Heywood <38010640+oliverheywood451@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:52:01 -0600 Subject: [PATCH 12/40] Update README.md --- library/OrderCloud.Catalyst/Integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 858b3f1..7c77a57 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -42,7 +42,7 @@ Your integration will likely contain other public classes but these two mandator A key goal of these integrations is *interoperability*. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like [ITaxCalculator](./Interfaces/ITaxCalculator.cs). Please check under [/Integrations/Interfaces](./Interfaces) to see if any apply to your integration's problem domain. If some do, make sure your OCIntegrationCommand implements those interfaces. If none do, still create your integration but be aware we may look to standardize it in the future. Feel free open issues recommending changes or additions to the interfaces. -## Other Guidelines +## Guidelines - Keep the number of properties and methods on your exposed contracts to the minimum required. Do a small amount well. - Under [/Integrations/Implementations](./Implementations) create a folder with your service name (e.g. "Mississippi") to contain your files. At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. From 551254aa770715a07700ee802d9d385857fe7c2d Mon Sep 17 00:00:00 2001 From: Oliver Heywood <38010640+oliverheywood451@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:53:01 -0600 Subject: [PATCH 13/40] Update README.md --- library/OrderCloud.Catalyst/Integrations/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 7c77a57..4700fcc 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -40,7 +40,9 @@ Your integration will likely contain other public classes but these two mandator ## Interfaces -A key goal of these integrations is *interoperability*. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like [ITaxCalculator](./Interfaces/ITaxCalculator.cs). Please check under [/Integrations/Interfaces](./Interfaces) to see if any apply to your integration's problem domain. If some do, make sure your OCIntegrationCommand implements those interfaces. If none do, still create your integration but be aware we may look to standardize it in the future. Feel free open issues recommending changes or additions to the interfaces. +A key goal of these integrations is *interoperability*. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like [ITaxCalculator](./Interfaces/ITaxCalculator.cs). Please check under [/Integrations/Interfaces](./Interfaces) to see if any apply to your integration's problem domain. If some do, make sure your OCIntegrationCommand implements those interfaces. + +Feel free open issues recommending changes or additions to the interfaces. ## Guidelines From 0ad08c751c618631732f43a103f1fe8844354ff3 Mon Sep 17 00:00:00 2001 From: Oliver Heywood <38010640+oliverheywood451@users.noreply.github.com> Date: Wed, 26 Jan 2022 11:53:22 -0600 Subject: [PATCH 14/40] Update README.md --- library/OrderCloud.Catalyst/Integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 4700fcc..93ef136 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -40,7 +40,7 @@ Your integration will likely contain other public classes but these two mandator ## Interfaces -A key goal of these integrations is *interoperability*. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like [ITaxCalculator](./Interfaces/ITaxCalculator.cs). Please check under [/Integrations/Interfaces](./Interfaces) to see if any apply to your integration's problem domain. If some do, make sure your OCIntegrationCommand implements those interfaces. +A key goal is *interoperability*. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like [ITaxCalculator](./Interfaces/ITaxCalculator.cs). Please check under [/Integrations/Interfaces](./Interfaces) to see if any apply to your integration's problem domain. If some do, make sure your OCIntegrationCommand implements those interfaces. Feel free open issues recommending changes or additions to the interfaces. From ae2d96b3d7cb9feaccc4f4843d6159c120588e54 Mon Sep 17 00:00:00 2001 From: Oliver Heywood <38010640+oliverheywood451@users.noreply.github.com> Date: Wed, 26 Jan 2022 12:01:50 -0600 Subject: [PATCH 15/40] Update README.md --- library/OrderCloud.Catalyst/Integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 93ef136..1bd091e 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -4,7 +4,7 @@ Guidelines for adding a new integration to the Catalyst library. ## Basics -Creating an integration in this project means it will be published as part of a [Nuget code library](https://www.nuget.org/packages/ordercloud-dotnet-catalyst/). Each integration should expose functionality to interact with 1 external service and should not depend any other integrations. There is a natural tension between providing too little "wrapper" functionality (creating a generic API client) and too much "wrapper" (an opinionated solution that limits use cases). The key to this balance is the method signatures of the contract your integration exposes. +Creating an integration in this project means it will be published as part of a [Nuget code library](https://www.nuget.org/packages/ordercloud-dotnet-catalyst/). Each integration should expose functionality to interact with 1 external service and should not depend any other integrations. There is a natural tension between providing too little "wrapper" functionality (creating a generic API client) and too much "wrapper" (an opinionated solution that limits use cases). The key to this balance are the details of the contract your integration exposes. ## Exposed Contracts From 25a89dfa49aea9e3a9ffcdd506971b7c3d4bd612 Mon Sep 17 00:00:00 2001 From: Oliver Heywood <38010640+oliverheywood451@users.noreply.github.com> Date: Wed, 26 Jan 2022 12:05:40 -0600 Subject: [PATCH 16/40] Update README.md --- library/OrderCloud.Catalyst/Integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 1bd091e..3a42fa9 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -18,7 +18,7 @@ public class MississippiOCIntegrationConfig : OCIntegrationConfig } ``` ```c# -public class MississippiOCIntegrationCommand : OCIntegrationCommand, IRiver +public class MississippiOCIntegrationCommand : OCIntegrationCommand { protected readonly MississippiOCIntegrationConfig _config; From 0f144416a4f05c89e9e8f1f44c78cacc9fedec72 Mon Sep 17 00:00:00 2001 From: Crhistian Ramirez <16483662+crhistianramirez@users.noreply.github.com> Date: Wed, 26 Jan 2022 12:08:46 -0600 Subject: [PATCH 17/40] Typos on Integrations README --- library/OrderCloud.Catalyst/Integrations/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 3a42fa9..50999e1 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -4,7 +4,7 @@ Guidelines for adding a new integration to the Catalyst library. ## Basics -Creating an integration in this project means it will be published as part of a [Nuget code library](https://www.nuget.org/packages/ordercloud-dotnet-catalyst/). Each integration should expose functionality to interact with 1 external service and should not depend any other integrations. There is a natural tension between providing too little "wrapper" functionality (creating a generic API client) and too much "wrapper" (an opinionated solution that limits use cases). The key to this balance are the details of the contract your integration exposes. +Creating an integration in this project means it will be published as part of a [Nuget code library](https://www.nuget.org/packages/ordercloud-dotnet-catalyst/). Each integration should expose functionality to interact with 1 external service and should not depend on any other integrations. There is a natural tension between providing too little "wrapper" functionality (creating a generic API client) and too much "wrapper" (an opinionated solution that limits use cases). The key to this balance are the details of the contract your integration exposes. ## Exposed Contracts @@ -14,7 +14,7 @@ All integrations should include two classes designed to be exposed and consumed public class MississippiOCIntegrationConfig : OCIntegrationConfig { public string ApiKey { get; set;} - ... ect. + ... etc. } ``` ```c# From 81db3b9810fd253c17f755eff2123b8b1f3edd3d Mon Sep 17 00:00:00 2001 From: Oliver Heywood <38010640+oliverheywood451@users.noreply.github.com> Date: Wed, 26 Jan 2022 12:11:31 -0600 Subject: [PATCH 18/40] Update README.md --- library/OrderCloud.Catalyst/Integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 50999e1..258d4f8 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -32,7 +32,7 @@ public class MississippiOCIntegrationCommand : OCIntegrationCommand } - ... ect. + ... etc. } ``` From 432abdf10e3afce1ba259ceff501a64b044d93b8 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Wed, 26 Jan 2022 13:54:54 -0600 Subject: [PATCH 19/40] remove duplicates --- .../Integrations/OCIntegrationCommand.cs | 8 +------- .../Integrations/OCIntegrationConfig.cs | 7 +------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs b/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs index 668cd75..840aaf9 100644 --- a/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs +++ b/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs @@ -4,17 +4,11 @@ namespace OrderCloud.Catalyst { - /// - /// A base class that all Integration Config classes should extend. Contains environment variables needed for that integration. - /// - public class OCIntegrationConfig - { - } - /// /// A base class that all Integration Command classes should extend. Exposes methods that are the behaviors of the integration. /// public class OCIntegrationCommand { + } } diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs index 668cd75..f3385b1 100644 --- a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs +++ b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs @@ -11,10 +11,5 @@ public class OCIntegrationConfig { } - /// - /// A base class that all Integration Command classes should extend. Exposes methods that are the behaviors of the integration. - /// - public class OCIntegrationCommand - { - } + } From 266bde97e1929cb3b7ebd250650403002ab94e24 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Mon, 31 Jan 2022 15:30:17 -0600 Subject: [PATCH 20/40] error handling is solid. now need to do tests --- .../Exceptions/ErrorResponseException.cs.cs | 21 ++++++ .../Exceptions/MissingConfigException.cs | 25 +++++++ .../Exceptions/NoResponseException.cs.cs | 20 +++++ .../UnauthorizedResponseException.cs | 18 +++++ .../Vertex/Mappers/VertexRequestMapper.cs | 4 +- .../Models/VertexCalculateTaxRequest.cs | 4 +- .../Implementations/Vertex/VertexClient.cs | 74 ++++++++++++------- .../Vertex/VertexOCIntegrationCommand.cs | 9 +-- .../Vertex/VertexOCIntegrationConfig.cs | 6 ++ .../Integrations/OCIntegrationCommand.cs | 7 +- .../Integrations/OCIntegrationConfig.cs | 22 +++++- .../IntegrationTests/VertexTests.cs | 1 - 12 files changed, 171 insertions(+), 40 deletions(-) create mode 100644 library/OrderCloud.Catalyst/Integrations/Exceptions/ErrorResponseException.cs.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Exceptions/MissingConfigException.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Exceptions/NoResponseException.cs.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Exceptions/UnauthorizedResponseException.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/ErrorResponseException.cs.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/ErrorResponseException.cs.cs new file mode 100644 index 0000000..3ffc833 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/ErrorResponseException.cs.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class ErrorResponseException : CatalystBaseException + { + public ErrorResponseException(OCIntegrationConfig config, string requestUrl, object responseBody) : base( + "IntegrationErrorResponse", + $"Request to 3rd party service \"{config.ServiceName}\" resulted in an error. See body for details.", + new + { + config.ServiceName, + RequestUrl = requestUrl, + ResponseBody = responseBody + } + , 400) + { } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/MissingConfigException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/MissingConfigException.cs new file mode 100644 index 0000000..811fbd0 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/MissingConfigException.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class MissingConfigException : CatalystBaseException + { + public MissingConfigException(OCIntegrationConfig config, List missingFields) : base( + "IntegrationMissingConfig", + $"Configuration field(s) for 3rd party service \"{config.ServiceName}\" are null or empty. Check fields on class {config.GetType().Name}.", + new + { + config.ServiceName, + MissingFieldNames = missingFields + }, + 400) { } + } + + [AttributeUsage(AttributeTargets.Property)] + public class RequiredIntegrationFieldAttribute : Attribute + { + + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/NoResponseException.cs.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/NoResponseException.cs.cs new file mode 100644 index 0000000..56deb89 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/NoResponseException.cs.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class NoResponseException : CatalystBaseException + { + public NoResponseException(OCIntegrationConfig config, string requestUrl) : base( + "IntegrationNoResponse", + $"Request to 3rd party service \"{config.ServiceName}\" returned no response.", + new + { + config.ServiceName, + RequestUrl = requestUrl, + } + , 400) + { } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/UnauthorizedResponseException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/UnauthorizedResponseException.cs new file mode 100644 index 0000000..1d8125f --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/UnauthorizedResponseException.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class UnauthorizedResponseException : CatalystBaseException + { + public UnauthorizedResponseException(OCIntegrationConfig config, string requestUrl) : base( + "IntegrationAuthorizationFailed", + $"Authentication to 3rd party service \"{config.ServiceName}\" failed. Check your config credentials.", + new { + config.ServiceName, + RequestUrl = requestUrl, + }, + 400) {} + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs index e62915c..7e1f678 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs @@ -21,8 +21,8 @@ public static VertexCalculateTaxRequest ToVertexCalculateTaxRequest(this OrderWo return new VertexCalculateTaxRequest() { postingDate = DateTime.Now.ToString("yyyy-MM-dd"), - saleMessageType = type, - transactionType = VertexTransactionType.SALE, + saleMessageType = type.ToString(), + transactionType = VertexTransactionType.SALE.ToString(), transactionId = order.Order.ID, seller = new VertexSeller() { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxRequest.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxRequest.cs index 0f323e8..9f5ecd3 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxRequest.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxRequest.cs @@ -6,14 +6,14 @@ namespace OrderCloud.Catalyst { public class VertexCalculateTaxRequest { - public VertexSaleMessageType saleMessageType { get; set; } + public string saleMessageType { get; set; } public VertexSeller seller { get; set; } public VertexCustomer customer { get; set; } public VertexDiscount discount { get; set; } public List lineItems { get; set; } = new List(); public string postingDate { get; set; } public string transactionId { get; set; } - public VertexTransactionType transactionType { get; set; } + public string transactionType { get; set; } } public class VertexSitusOverride diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs index bbdfe5c..8951aa3 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; -using OrderCloud.Catalyst; namespace OrderCloud.Catalyst { @@ -12,6 +11,7 @@ public class VertexClient { protected const string ApiUrl = "https://restconnect.vertexsmb.com"; protected const string AuthUrl = "https://auth.vertexsmb.com"; + protected DateTimeOffset? CurrentTokenExpires = null; protected readonly VertexOCIntegrationConfig _config; protected VertexTokenResponse _token; @@ -22,37 +22,40 @@ public VertexClient(VertexOCIntegrationConfig config) public async Task CalculateTax(VertexCalculateTaxRequest request) { - return await MakeRequest(() => - $"{ApiUrl}/vertex-restapi/v1/sale" - .WithOAuthBearerToken(_token.access_token) - .AllowAnyHttpStatus() + var token = await GetToken(_config); + var url = $"{ApiUrl}/vertex-restapi/v1/sale"; + try + { + var response = await url + .WithOAuthBearerToken(token.access_token) .PostJsonAsync(request) - ); - } - - protected async Task MakeRequest(Func> request) - { - if (_token?.access_token == null) + .ReceiveJson>(); + return response.data; + } catch (FlurlHttpTimeoutException ex) // simulate with this https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error { - _token = await GetToken(_config); + // candidate for retry here? + throw new NoResponseException(_config, url); } - var response = await (await request()).GetJsonAsync>(); - if (response.errors.Exists(e => e.detail == "invalid access token")) + catch (FlurlHttpException ex) { - // refresh the token - _token = await GetToken(_config); - // try the request again - response = await (await request()).GetJsonAsync>(); + if (ex.Call.Response == null || ex.Call.Response.StatusCode > 500) // simulate by putting laptop on airplane mode + { + // candidate for retry here? + throw new NoResponseException(_config, url); + } + var body = await ex.Call.Response.GetJsonAsync>(); + throw new ErrorResponseException(_config, url, body.errors); } - - // Catch and throw any api errors - Require.That(response.errors.Count == 0, new VertexException(response.errors)); - - return response.data; } + protected async Task GetToken(VertexOCIntegrationConfig config) { + if (_token?.access_token != null && CurrentTokenExpires != null && CurrentTokenExpires > DateTimeOffset.Now) + { + return _token; + } + var body = new { scope = "calc-rest-api", @@ -62,9 +65,28 @@ protected async Task GetToken(VertexOCIntegrationConfig con username = config.Username, password = config.Password }; - var response = await $"{AuthUrl}/identity/connect/token".PostUrlEncodedAsync(body); - var token = await response.GetJsonAsync(); - return token; + var url = $"{AuthUrl}/identity/connect/token"; + try + { + var response = await url.PostUrlEncodedAsync(body); + var token = await response.GetJsonAsync(); + _token = token; + CurrentTokenExpires = DateTimeOffset.Now.AddSeconds(token.expires_in); + return token; + } catch (FlurlHttpTimeoutException ex) // simulate with this https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error + { + // candidate for retry here? + throw new NoResponseException(_config, url); + } + catch (FlurlHttpException ex) + { + if (ex.Call.Response == null || ex.Call.Response.StatusCode > 500) // simulate by putting laptop on airplane mode + { + // candidate for retry here? + throw new NoResponseException(_config, url); + } + throw new UnauthorizedResponseException(_config, url); + } } } } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationCommand.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationCommand.cs index 7b09670..f3899aa 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationCommand.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationCommand.cs @@ -1,18 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; using System.Threading.Tasks; using OrderCloud.SDK; - namespace OrderCloud.Catalyst { - public class VertexOCIntegrationCommand : ITaxCalculator + public class VertexOCIntegrationCommand : OCIntegrationCommand, ITaxCalculator { protected readonly VertexClient _client; protected readonly VertexOCIntegrationConfig _config; - public VertexOCIntegrationCommand(VertexOCIntegrationConfig config) + public VertexOCIntegrationCommand(VertexOCIntegrationConfig config) : base(config) { _config = config; _client = new VertexClient(config); diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationConfig.cs index a2a76c1..d7bbfb9 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationConfig.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationConfig.cs @@ -4,10 +4,16 @@ namespace OrderCloud.Catalyst { public class VertexOCIntegrationConfig : OCIntegrationConfig { + public override string ServiceName { get; } = "Vertex"; + [RequiredIntegrationField] public string CompanyName { get; set; } + [RequiredIntegrationField] public string ClientID { get; set; } + [RequiredIntegrationField] public string ClientSecret { get; set; } + [RequiredIntegrationField] public string Username { get; set; } + [RequiredIntegrationField] public string Password { get; set; } } } diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs b/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs index 840aaf9..a76024c 100644 --- a/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs +++ b/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs @@ -7,8 +7,11 @@ namespace OrderCloud.Catalyst /// /// A base class that all Integration Command classes should extend. Exposes methods that are the behaviors of the integration. /// - public class OCIntegrationCommand + public abstract class OCIntegrationCommand { - + public OCIntegrationCommand(OCIntegrationConfig config) + { + config.ValidateRequiredFields(); + } } } diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs index f3385b1..b6b7259 100644 --- a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs +++ b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace OrderCloud.Catalyst @@ -7,8 +8,27 @@ namespace OrderCloud.Catalyst /// /// A base class that all Integration Config classes should extend. Contains environment variables needed for that integration. /// - public class OCIntegrationConfig + public abstract class OCIntegrationConfig { + public abstract string ServiceName { get; } + + public void ValidateRequiredFields() + { + var props = GetType() + .GetProperties(); + var missing = props.Where(prop => + { + var value = (string)prop.GetValue(this); + var isRequired = Attribute.IsDefined(prop, typeof(RequiredIntegrationFieldAttribute)); + return isRequired && (value == null || value == ""); + }); + + if (missing.Any()) + { + var names = missing.Select(p => p.Name).ToList(); + throw new MissingConfigException(this, names); + } + } } diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/VertexTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/VertexTests.cs index 65cb207..adb1fa6 100644 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/VertexTests.cs +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/VertexTests.cs @@ -14,6 +14,5 @@ public class VertexTests public async Task Test() { } - } } From 6263c513044dd89f18cfa82437331cb0012830d3 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Mon, 31 Jan 2022 15:33:14 -0600 Subject: [PATCH 21/40] couple readme updates --- library/OrderCloud.Catalyst/Integrations/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index 258d4f8..c3f5bd0 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -13,7 +13,11 @@ All integrations should include two classes designed to be exposed and consumed ```c# public class MississippiOCIntegrationConfig : OCIntegrationConfig { + public override string ServiceName { get; } = "Mississippi"; + + [RequiredIntegrationField] public string ApiKey { get; set;} + ... etc. } ``` @@ -22,7 +26,7 @@ public class MississippiOCIntegrationCommand : OCIntegrationCommand { protected readonly MississippiOCIntegrationConfig _config; - public MississippiOCIntegrationCommand(MississippiOCIntegrationConfig config) + public MississippiOCIntegrationCommand(MississippiOCIntegrationConfig config) : base(config) { _config = config; // used to auth to service } @@ -48,7 +52,7 @@ Feel free open issues recommending changes or additions to the interfaces. - Keep the number of properties and methods on your exposed contracts to the minimum required. Do a small amount well. - Under [/Integrations/Implementations](./Implementations) create a folder with your service name (e.g. "Mississippi") to contain your files. At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. - - Handle error scenarios like auth errors and bad requests within your integration by throwing a CatalystBaseException. + - Handle error scenarios like auth errors and bad requests within your integration by throwing one of the exceptions in [/Integrations/Exceptions](./Exceptions). - Avoid adding a nuget package for your service's SDK. This will lead to bloat as many projects may use this library without using your service. Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. - Write unit tests against your Command methods and put them in the OrderCloud.Catalyst.Tests project under `/IntegrationTests/[ServiceName]Tests.cs`. Mock API reponses from your service using [Flurl test practices](https://flurl.dev/docs/testable-http/) or something similar. - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. From 553d5a1b79edf380f6d97cd461ba076fb4916df6 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Tue, 1 Feb 2022 10:50:25 -0600 Subject: [PATCH 22/40] solid progress on the testing framework --- .../Exceptions/ErrorResponseException.cs.cs | 21 ---- .../IntegrationAuthFailedException.cs | 25 +++++ .../IntegrationErrorResponseException.cs.cs | 28 ++++++ .../IntegrationMissingConfigsException.cs | 31 ++++++ .../IntegrationNoResponseException.cs.cs | 26 +++++ .../Exceptions/MissingConfigException.cs | 25 ----- .../Exceptions/NoResponseException.cs.cs | 20 ---- .../UnauthorizedResponseException.cs | 18 ---- .../Vertex/Mappers/VertexRequestMapper.cs | 6 +- .../Vertex/Mappers/VertexResponseMapper.cs | 6 +- .../Implementations/Vertex/README.md | 34 +++---- .../Implementations/Vertex/VertexClient.cs | 12 +-- .../Integrations/OCIntegrationConfig.cs | 2 +- .../IntegrationTests/OrderWorksheetBuilder.cs | 32 +++++++ .../IntegrationTests/Vertex/VertexTests.cs | 96 +++++++++++++++++++ .../IntegrationTests/VertexTests.cs | 18 ---- 16 files changed, 265 insertions(+), 135 deletions(-) delete mode 100644 library/OrderCloud.Catalyst/Integrations/Exceptions/ErrorResponseException.cs.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs.cs delete mode 100644 library/OrderCloud.Catalyst/Integrations/Exceptions/MissingConfigException.cs delete mode 100644 library/OrderCloud.Catalyst/Integrations/Exceptions/NoResponseException.cs.cs delete mode 100644 library/OrderCloud.Catalyst/Integrations/Exceptions/UnauthorizedResponseException.cs create mode 100644 tests/OrderCloud.Catalyst.Tests/IntegrationTests/OrderWorksheetBuilder.cs create mode 100644 tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs delete mode 100644 tests/OrderCloud.Catalyst.Tests/IntegrationTests/VertexTests.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/ErrorResponseException.cs.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/ErrorResponseException.cs.cs deleted file mode 100644 index 3ffc833..0000000 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/ErrorResponseException.cs.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace OrderCloud.Catalyst -{ - public class ErrorResponseException : CatalystBaseException - { - public ErrorResponseException(OCIntegrationConfig config, string requestUrl, object responseBody) : base( - "IntegrationErrorResponse", - $"Request to 3rd party service \"{config.ServiceName}\" resulted in an error. See body for details.", - new - { - config.ServiceName, - RequestUrl = requestUrl, - ResponseBody = responseBody - } - , 400) - { } - } -} diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs new file mode 100644 index 0000000..a2ee365 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class IntegrationAuthFailedException : CatalystBaseException + { + public IntegrationAuthFailedException(OCIntegrationConfig config, string requestUrl) : base( + "IntegrationAuthorizationFailed", + $"Authentication to 3rd party service \"{config.ServiceName}\" failed. Check your config credentials.", + new IntegrationAuthFailedError() + { + ServiceName = config.ServiceName, + RequestUrl = requestUrl, + }, + 400) {} + } + + public class IntegrationAuthFailedError + { + public string ServiceName { get; set; } + public string RequestUrl { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs.cs new file mode 100644 index 0000000..fc37af3 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class IntegrationErrorResponseException : CatalystBaseException + { + public IntegrationErrorResponseException(OCIntegrationConfig config, string requestUrl, object responseBody) : base( + "IntegrationErrorResponse", + $"Request to 3rd party service \"{config.ServiceName}\" resulted in an error. See body for details.", + new IntegrationErrorResponseError() + { + ServiceName = config.ServiceName, + RequestUrl = requestUrl, + ResponseBody = responseBody + } + , 400) + { } + } + + public class IntegrationErrorResponseError + { + public string ServiceName { get; set; } + public string RequestUrl { get; set; } + public object ResponseBody { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs new file mode 100644 index 0000000..80a4626 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class IntegrationMissingConfigsException : CatalystBaseException + { + public IntegrationMissingConfigsException(OCIntegrationConfig config, List missingFields) : base( + "IntegrationMissingConfigs", + $"Configuration field(s) for 3rd party service \"{config.ServiceName}\" are null or empty. Check fields on class {config.GetType().Name}.", + new IntegrationMissingConfigs() + { + ServiceName = config.ServiceName, + MissingFieldNames = missingFields + }, + 400) { } + } + + public class IntegrationMissingConfigs + { + public string ServiceName { get; set; } + public List MissingFieldNames { get; set; } = new List(); + } + + [AttributeUsage(AttributeTargets.Property)] + public class RequiredIntegrationFieldAttribute : Attribute + { + + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs.cs new file mode 100644 index 0000000..cbd88e2 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class IntegrationNoResponseException : CatalystBaseException + { + public IntegrationNoResponseException(OCIntegrationConfig config, string requestUrl) : base( + "IntegrationNoResponse", + $"Request to 3rd party service \"{config.ServiceName}\" returned no response.", + new IntegrationNoResponseError() + { + ServiceName = config.ServiceName, + RequestUrl = requestUrl, + } + , 400) + { } + } + + public class IntegrationNoResponseError + { + public string ServiceName { get; set; } + public string RequestUrl { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/MissingConfigException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/MissingConfigException.cs deleted file mode 100644 index 811fbd0..0000000 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/MissingConfigException.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace OrderCloud.Catalyst -{ - public class MissingConfigException : CatalystBaseException - { - public MissingConfigException(OCIntegrationConfig config, List missingFields) : base( - "IntegrationMissingConfig", - $"Configuration field(s) for 3rd party service \"{config.ServiceName}\" are null or empty. Check fields on class {config.GetType().Name}.", - new - { - config.ServiceName, - MissingFieldNames = missingFields - }, - 400) { } - } - - [AttributeUsage(AttributeTargets.Property)] - public class RequiredIntegrationFieldAttribute : Attribute - { - - } -} diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/NoResponseException.cs.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/NoResponseException.cs.cs deleted file mode 100644 index 56deb89..0000000 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/NoResponseException.cs.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace OrderCloud.Catalyst -{ - public class NoResponseException : CatalystBaseException - { - public NoResponseException(OCIntegrationConfig config, string requestUrl) : base( - "IntegrationNoResponse", - $"Request to 3rd party service \"{config.ServiceName}\" returned no response.", - new - { - config.ServiceName, - RequestUrl = requestUrl, - } - , 400) - { } - } -} diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/UnauthorizedResponseException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/UnauthorizedResponseException.cs deleted file mode 100644 index 1d8125f..0000000 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/UnauthorizedResponseException.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace OrderCloud.Catalyst -{ - public class UnauthorizedResponseException : CatalystBaseException - { - public UnauthorizedResponseException(OCIntegrationConfig config, string requestUrl) : base( - "IntegrationAuthorizationFailed", - $"Authentication to 3rd party service \"{config.ServiceName}\" failed. Check your config credentials.", - new { - config.ServiceName, - RequestUrl = requestUrl, - }, - 400) {} - } -} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs index 7e1f678..65183c9 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs @@ -14,7 +14,8 @@ public static VertexCalculateTaxRequest ToVertexCalculateTaxRequest(this OrderWo var itemLines = order.LineItems.Select(li => ToVertexLineItem(li)); var shippingLines = order.ShipEstimateResponse.ShipEstimates.Select(se => { - var firstLi = order.LineItems.First(li => li.ID == se.ShipEstimateItems.First().LineItemID); + var firstLi = order.LineItems.FirstOrDefault(li => li.ID == se.ShipEstimateItems.First().LineItemID); + Require.That(firstLi != null, new CatalystBaseException("InvalidOrderWorksheet", $"Invalid OrderWorksheet. Based on ShipEstimateItems, expected to find a LineItem with ID {se.ShipEstimateItems.First().LineItemID}", null, 400)); return ToVertexLineItem(se, firstLi.ShippingAddress); }); @@ -65,7 +66,8 @@ public static VertexLineItem ToVertexLineItem(LineItem lineItem) public static VertexLineItem ToVertexLineItem(ShipEstimate shipEstimate, Address shipTo) { - var selectedMethod = shipEstimate.ShipMethods.First(m => m.ID == shipEstimate.SelectedShipMethodID); + var selectedMethod = shipEstimate.ShipMethods.FirstOrDefault(m => m.ID == shipEstimate.SelectedShipMethodID); + Require.That(selectedMethod != null, new CatalystBaseException("InvalidOrderWorksheet", $"Invalid OrderWorksheet. Based on SelectedShipMethodID, expected to find a ShipMethod with ID {shipEstimate.SelectedShipMethodID}", null, 400)); return new VertexLineItem() { customer = new VertexCustomer() diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs index 616a147..8d47eec 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs @@ -7,10 +7,12 @@ namespace OrderCloud.Catalyst { public static class VertexResponseMapper { + public static bool IsShippingLineItem(this VertexResponseLineItem li) => li.product.productClass == "shipping_code"; + public static OrderTaxCalculation ToOrderTaxCalculation(this VertexCalculateTaxResponse response) { - var shippingLines = response.lineItems?.Where(line => line.product.productClass == "shipping_code") ?? new List(); - var itemLines = response.lineItems?.Where(line => line.product.productClass != "shipping_code") ?? new List(); + var shippingLines = response.lineItems?.Where(li => li.IsShippingLineItem()) ?? new List(); + var itemLines = response.lineItems?.Where(li => !li.IsShippingLineItem()) ?? new List(); return new OrderTaxCalculation() { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md index 3b176fa..d95452b 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md @@ -1,11 +1,7 @@ -# Vertex Integration For OrderCloud Headstart +# Vertex Integration ## Scope of this integration -This integration calculates sales tax for an Order using the vertex cloud API. It is part of the open source Headstart project, which provides a complete, opinionated OrderCloud solution. It conforms to the [ITaxCalculator](../ordercloud.integrations.library/interfaces/ITaxCalculator.cs) interface. - -Use Cases: -- Sales Tax Estimate -- Finalized Order Forwarding +This .NET integration calculates sales tax for an Order using the vertex cloud API. It can be used during checkout to provide a tax cost to the buyer or after submit to create a vertex transaction for filling. ## Vertex Basics [Vertex](https://www.vertexinc.com/) is a cloud or on premise **sales and use tax solution**. Vertex Cloud integrates with leading e-commerce platforms and mid-market ERP systems. Customers can use Vertex Cloud to manage complex sales and use tax across multiple jurisdictions. Vertex Cloud provides tax calculations and signature-ready PDF returns in one comprehensive solution. @@ -24,19 +20,13 @@ A taxable transaction is committed to vertex asynchronously shortly following or **OrderCloud Side -** This integration should be triggered by the **`PostOrderSubmit`** Checkout Integration Event. Learn more about [checkout integration events](https://ordercloud.io/knowledge-base/order-checkout-integration); -## Steps to use -- Set up the headstart application. This is process is throughly documented [here](https://github.com/ordercloud-api/headstart#initial-setup). -- Sign up for vertex and login to the [Vertex Portal](https://portal.vertexsmb.com/Home) to get the credentials required in the next step. -- Set environment variables required for Vertex authentication. See the vertex [authentication guide](https://developer.vertexcloud.com/access-token/). If you follow the headstart set up process env vars are stored in an Azure Config. -``` -VertexSettings:CompanyName -VertexSettings:ClientID -VertexSettings:ClientSecret -VertexSettings:Username -VertexSettings:Password -``` -- Set an environment variable to indicate you want to use Vertex for tax calculation. -``` -EnvironmentSettings:TaxProvider=Vertex -``` -- Redeploy your middleware and on the storefront, go through checkout pausing before entering payment. You will see tax calculated by Vertex! +## Set up steps + +- You should set up a .NET middleware project using the Catalyst library and starter project. [See guide](https://ordercloud.io/knowledge-base/start-dotnet-middleware-from-scratch). +- Through the OrderCloud API configure an Order Chekout IntegrationEvent object to point to your new middleware. [See guide](https://ordercloud.io/knowledge-base/order-checkout-integration) +- Create a vertex account online and retrieve all the configuration variables required in [VertexOCIntegrationConfig.cs](./VertexOCIntegrationConfig.cs); +- Within your .NET code project, create an instance of [VertexOCIntegrationCommand.cs](./VertexOCIntegrationCommand.cs). Use the method `CalculateEstimateAsync` within the **`OrderCalculate`** Checkout Integration Event. Use the method `CommitTransactionAsync` within the **`PostOrderSubmit`** Checkout Integration Event. + +## Interfaces + +- It conforms to the [ITaxCalculator](../../Interfaces/ITaxCalculator.cs) interface. diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs index 8951aa3..47096e4 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs @@ -34,17 +34,17 @@ public async Task CalculateTax(VertexCalculateTaxReq } catch (FlurlHttpTimeoutException ex) // simulate with this https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error { // candidate for retry here? - throw new NoResponseException(_config, url); + throw new IntegrationNoResponseException(_config, url); } catch (FlurlHttpException ex) { if (ex.Call.Response == null || ex.Call.Response.StatusCode > 500) // simulate by putting laptop on airplane mode { // candidate for retry here? - throw new NoResponseException(_config, url); + throw new IntegrationNoResponseException(_config, url); } var body = await ex.Call.Response.GetJsonAsync>(); - throw new ErrorResponseException(_config, url, body.errors); + throw new IntegrationErrorResponseException(_config, url, body.errors); } } @@ -76,16 +76,16 @@ protected async Task GetToken(VertexOCIntegrationConfig con } catch (FlurlHttpTimeoutException ex) // simulate with this https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error { // candidate for retry here? - throw new NoResponseException(_config, url); + throw new IntegrationNoResponseException(_config, url); } catch (FlurlHttpException ex) { if (ex.Call.Response == null || ex.Call.Response.StatusCode > 500) // simulate by putting laptop on airplane mode { // candidate for retry here? - throw new NoResponseException(_config, url); + throw new IntegrationNoResponseException(_config, url); } - throw new UnauthorizedResponseException(_config, url); + throw new IntegrationAuthFailedException(_config, url); } } } diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs index b6b7259..80652a3 100644 --- a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs +++ b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs @@ -26,7 +26,7 @@ public void ValidateRequiredFields() if (missing.Any()) { var names = missing.Select(p => p.Name).ToList(); - throw new MissingConfigException(this, names); + throw new IntegrationMissingConfigsException(this, names); } } } diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/OrderWorksheetBuilder.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/OrderWorksheetBuilder.cs new file mode 100644 index 0000000..98b38da --- /dev/null +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/OrderWorksheetBuilder.cs @@ -0,0 +1,32 @@ +using AutoFixture; +using OrderCloud.SDK; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OrderCloud.Catalyst.Tests +{ + public class OrderWorksheetBuilder + { + private static Fixture _fixture = new Fixture(); + + public OrderWorksheet Build() + { + var workSheet = _fixture.Create(); + + // The object fixture auto-generates is invalid because some ID references don't exist. + var lineItemID = workSheet.LineItems.First().ID; + foreach (var shipEstimate in workSheet.ShipEstimateResponse.ShipEstimates) + { + shipEstimate.SelectedShipMethodID = shipEstimate.ShipMethods.First().ID; + foreach (var item in shipEstimate.ShipEstimateItems) + { + item.LineItemID = lineItemID; + } + } + + return workSheet; + } + } +} diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs new file mode 100644 index 0000000..1c2b79e --- /dev/null +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoFixture; +using Flurl.Http.Testing; +using NUnit.Framework; +using OrderCloud.SDK; + +namespace OrderCloud.Catalyst.Tests +{ + [TestFixture] + public class VertexTests + { + private static Fixture _fixture = new Fixture(); + private HttpTest _httpTest; + private static VertexOCIntegrationConfig _config = _fixture.Create(); + private VertexOCIntegrationCommand _command = new VertexOCIntegrationCommand(_config); + private OrderWorksheetBuilder _worksheetBuilder = new OrderWorksheetBuilder(); + + [SetUp] + public void CreateHttpTest() + { + _httpTest = new HttpTest(); + } + + [TearDown] + public void DisposeHttpTest() + { + _httpTest.Dispose(); + } + + [Test] + public void ShouldThrowErrorIfMissingRequiredConfigs() + { + // Arrange + var config = new VertexOCIntegrationConfig(); + // Act + var ex = Assert.Throws(() => new VertexOCIntegrationCommand(config)); + // Assert + var data = (IntegrationMissingConfigs) ex.Errors[0].Data; + Assert.AreEqual(data.ServiceName, "Vertex"); + Assert.AreEqual(data.MissingFieldNames, new List { "CompanyName", "ClientID", "ClientSecret", "Username", "Password" }); + } + + [Test] + public async Task ResponseShouldBeMappedCorrectly() + { + // Arrange + var response = _fixture.Create>(); + _httpTest.RespondWithJson(response); + // Act + var result = await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }); + // Assert + Assert.AreEqual(response.data.transactionId, result.OrderID); + Assert.AreEqual(response.data.transactionId, result.ExternalTransactionID); + Assert.AreEqual(response.data.totalTax, result.TotalTax); + + var lineItems = response.data.lineItems.Where(li => !li.IsShippingLineItem()).ToList(); + + for (var i = 0; i < result.LineItems.Count; i++) + { + var responseLineItem = lineItems[i]; + var resultLineItem = result.LineItems[i]; + Assert.AreEqual(responseLineItem.lineItemId, resultLineItem.LineItemID); + Assert.AreEqual(responseLineItem.totalTax, resultLineItem.LineItemTotalTax); + for (var j = 0; j < lineItems[i].taxes.Count; j++) + { + var responseTax = responseLineItem.taxes[j]; + var resultTax = resultLineItem.LineItemLevelTaxes[j]; + Assert.AreEqual(responseTax.calculatedTax, resultTax.Tax); + Assert.AreEqual(responseTax.taxable, resultTax.Taxable); + Assert.AreEqual(0, resultTax.Exempt); // should maybe be something else + Assert.AreEqual(responseTax.impositionType.value, resultTax.TaxDescription); + Assert.AreEqual(responseTax.jurisdiction.jurisdictionLevel.ToString(), resultTax.JurisdictionLevel); + Assert.AreEqual(responseTax.jurisdiction.value, resultTax.JurisdictionValue); + Assert.AreEqual(null, resultTax.ShipEstimateID); + } + } + + var shippingLines = response.data.lineItems.Where(li => li.IsShippingLineItem()).SelectMany(li => li.taxes).ToList(); + + for (var i = 0; i < result.OrderLevelTaxes.Count; i++) + { + var responseShipTax = shippingLines[i]; + var resultTax = result.OrderLevelTaxes[i]; + Assert.AreEqual(responseShipTax.calculatedTax, resultTax.Tax); + Assert.AreEqual(responseShipTax.taxable, resultTax.Taxable); + Assert.AreEqual(0, resultTax.Exempt); // should maybe be something else + Assert.AreEqual(responseShipTax.impositionType.value, resultTax.TaxDescription); + Assert.AreEqual(responseShipTax.jurisdiction.jurisdictionLevel.ToString(), resultTax.JurisdictionLevel); + Assert.AreEqual(responseShipTax.jurisdiction.value, resultTax.JurisdictionValue); + } + } + } +} diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/VertexTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/VertexTests.cs deleted file mode 100644 index adb1fa6..0000000 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/VertexTests.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using NUnit.Framework; - -namespace OrderCloud.Catalyst.Tests -{ - [TestFixture] - public class VertexTests - { - [Test] - public async Task Test() - { - } - } -} From 03c56a6d8db2744449c9562f099c26c4474e8c48 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Tue, 1 Feb 2022 13:21:19 -0600 Subject: [PATCH 23/40] more tests --- .../Implementations/Vertex/README.md | 5 ++ .../Integrations/README.md | 4 +- .../Controllers/IntegrationController.cs | 72 +++++++++++++++++++ .../IntegrationTests/Vertex/VertexTests.cs | 54 +++++++++++++- 4 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 tests/OrderCloud.Catalyst.TestApi/Controllers/IntegrationController.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md index d95452b..4fa9144 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md @@ -25,6 +25,11 @@ A taxable transaction is committed to vertex asynchronously shortly following or - You should set up a .NET middleware project using the Catalyst library and starter project. [See guide](https://ordercloud.io/knowledge-base/start-dotnet-middleware-from-scratch). - Through the OrderCloud API configure an Order Chekout IntegrationEvent object to point to your new middleware. [See guide](https://ordercloud.io/knowledge-base/order-checkout-integration) - Create a vertex account online and retrieve all the configuration variables required in [VertexOCIntegrationConfig.cs](./VertexOCIntegrationConfig.cs); + - CompanyName + - ClientID + - ClientSecret + - Username + - Password - Within your .NET code project, create an instance of [VertexOCIntegrationCommand.cs](./VertexOCIntegrationCommand.cs). Use the method `CalculateEstimateAsync` within the **`OrderCalculate`** Checkout Integration Event. Use the method `CommitTransactionAsync` within the **`PostOrderSubmit`** Checkout Integration Event. ## Interfaces diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/README.md index c3f5bd0..c2e1c07 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/README.md @@ -52,9 +52,9 @@ Feel free open issues recommending changes or additions to the interfaces. - Keep the number of properties and methods on your exposed contracts to the minimum required. Do a small amount well. - Under [/Integrations/Implementations](./Implementations) create a folder with your service name (e.g. "Mississippi") to contain your files. At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. - - Handle error scenarios like auth errors and bad requests within your integration by throwing one of the exceptions in [/Integrations/Exceptions](./Exceptions). + - Handle error scenarios within your integration by throwing one of the exceptions in [/Integrations/Exceptions](./Exceptions). Key scenarios to handle are missing configs, invalid authentication, error response, and no response. - Avoid adding a nuget package for your service's SDK. This will lead to bloat as many projects may use this library without using your service. Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. - - Write unit tests against your Command methods and put them in the OrderCloud.Catalyst.Tests project under `/IntegrationTests/[ServiceName]Tests.cs`. Mock API reponses from your service using [Flurl test practices](https://flurl.dev/docs/testable-http/) or something similar. + - Write unit tests against your Command methods and put them in the OrderCloud.Catalyst.Tests project under a new folder like so `/IntegrationTests/[ServiceName]/[ServiceName]Tests.cs`. Mock API reponses from your service using [Flurl test practices](https://flurl.dev/docs/testable-http/) or something similar. - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. - Aim to follow the code patterns of existing integrations. diff --git a/tests/OrderCloud.Catalyst.TestApi/Controllers/IntegrationController.cs b/tests/OrderCloud.Catalyst.TestApi/Controllers/IntegrationController.cs new file mode 100644 index 0000000..bdaa794 --- /dev/null +++ b/tests/OrderCloud.Catalyst.TestApi/Controllers/IntegrationController.cs @@ -0,0 +1,72 @@ +using Microsoft.AspNetCore.Mvc; +using OrderCloud.SDK; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace OrderCloud.Catalyst.TestApi.Controllers +{ + [Route("int")] + public class IntegrationController : CatalystController + { + public IntegrationController() + { + } + + [HttpGet("vertex")] + public async Task GetTaxRates() + { + var command = new VertexOCIntegrationCommand(new VertexOCIntegrationConfig() + { + CompanyName = "OrderCloudTest", + ClientID = "REST-API-SiteCoreUSA", + ClientSecret = "dbbd2e08e5c3bef5f921c0110f17c950", + Username = "e44b802c-d160-4e9f-8f72-614bb08d4d86", + Password = "sD$9/n2A", + }); + + var worksheet = new OrderWorksheet() + { + Order = new Order() + { + ID = "testId", + FromUserID = "testUserID", + FromUser = new User() + { + Email = "oheywood@test.com", + }, + }, + LineItems = new List + { + new LineItem() + { + ProductID = "testProductID", + Quantity = 10, + UnitPrice = 20, + ID = "testLineItemID", + LineTotal = 200, + Product = new LineItemProduct() + { + Name= "productName" + }, + ShippingAddress = new Address() + { + //Street1 = "2418 e menlo blvd", + //State = "wi", + //Zip = "53211", + //City = "shorewood", + //Country = "us" + } + } + }, + ShipEstimateResponse = new ShipEstimateResponse() + { + ShipEstimates = new List { } + } + }; + + return await command.CalculateEstimateAsync(worksheet, new List { }); + } + } +} diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs index 1c2b79e..4b27957 100644 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs @@ -44,7 +44,59 @@ public void ShouldThrowErrorIfMissingRequiredConfigs() } [Test] - public async Task ResponseShouldBeMappedCorrectly() + public void ShouldThrowErrorIfAuthorizationFails() + { + // Arrange + _httpTest.RespondWith("{\"error\":\"invalid_client\"}", 400); // real vertex response + + var ex = Assert.ThrowsAsync(async () => + // Act + await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }) + ); + + var data = (IntegrationAuthFailedError) ex.Errors[0].Data; + Assert.AreEqual(data.ServiceName, "Vertex"); + Assert.AreEqual(data.RequestUrl, "https://auth.vertexsmb.com/identity/connect/token"); + } + + [Test] + public void ShouldThrowErrorIfNoResponse() + { + // Arrange + _httpTest.SimulateTimeout(); + + var ex = Assert.ThrowsAsync(async () => + // Act + await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }) + ); + + var data = (IntegrationNoResponseError) ex.Errors[0].Data; + Assert.AreEqual(data.ServiceName, "Vertex"); + } + + [Test] + public void ShouldThrowVertexErrorForBadRequest() + { + // Arrange + _httpTest + // real vertex response + .RespondWith("{\"access_token\":\"fakee1cf7e06bc50f1a884dd0fffake\",\"expires_in\":1200,\"token_type\":\"Bearer\"}", 200) + // real vertex response + .RespondWith("{\"meta\":{\"app\":\"Vertex REST API v2.0.0\",\"timeReceived\":\"2022-02-01T19: 04:12.751Z\",\"timeElapsed(ms)\":31},\"errors\":[{\"status\":\"400\",\"code\":\"VertexApplicationException\",\"title\":\"Bad Request\",\"detail\":\"The LocationRole being added is invalid.This might be due to an invalid location or an invalid address field.Make sure that the locationRole is valid, and try again.\n\"}]}", 400); + + var ex = Assert.ThrowsAsync(async () => + // Act + await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }) + ); + + var data = (IntegrationErrorResponseError)ex.Errors[0].Data; + Assert.AreEqual(data.ServiceName, "Vertex"); + Assert.AreEqual(data.RequestUrl, "https://restconnect.vertexsmb.com/vertex-restapi/v1/sale"); + Assert.AreEqual(((List) data.ResponseBody)[0].detail, "The LocationRole being added is invalid.This might be due to an invalid location or an invalid address field.Make sure that the locationRole is valid, and try again.\n"); + } + + [Test] + public async Task SuccessResponseShouldBeMappedCorrectly() { // Arrange var response = _fixture.Create>(); From ae116941ff93899ecb3d854593d6d5dae98a63ad Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Tue, 1 Feb 2022 13:40:04 -0600 Subject: [PATCH 24/40] readme updates --- README.md | 6 ++++++ .../Integrations/Implementations/README.md | 5 +++++ 2 files changed, 11 insertions(+) create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/README.md diff --git a/README.md b/README.md index aef9f2e..21bdf80 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ If you're building solutions for OrderCloud using .NET and find a particular tas ## Features +### [Commerce Integration List](./library/OrderCLoud.Catalyst/Integrations/Implementations/README.md) + +Interact with popular 3rd party APIs that provide functionality useful for commerce. Integrations within a category are made interoperable with an interface. + +[Guide to adding an Integration](./library/OrderCLoud.Catalyst/Integrations/README.md) + ### [User Authentication](https://github.com/ordercloud-api/ordercloud-dotnet-catalyst/tree/dev/library/OrderCloud.Catalyst/Auth/UserAuth) Use Ordercloud's authentication scheme in your own APIs. diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/README.md b/library/OrderCloud.Catalyst/Integrations/Implementations/README.md new file mode 100644 index 0000000..076d4d2 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/README.md @@ -0,0 +1,5 @@ +# List of Integrations + +| Name | Website | Integration Guide | Contributed By | Categories | +| ------------- | ------------- | ------------- | ------------- | ------------- | +| Vertex | https://www.vertexinc.com/ | [./Vertex/README.md](./Vertex/README.md) | OrderCloud Team | Tax From df73caa09f8b05abcb92d0a7077caa29a714bb9f Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Tue, 1 Feb 2022 13:40:38 -0600 Subject: [PATCH 25/40] dont actually want this --- .../Controllers/IntegrationController.cs | 72 ------------------- 1 file changed, 72 deletions(-) delete mode 100644 tests/OrderCloud.Catalyst.TestApi/Controllers/IntegrationController.cs diff --git a/tests/OrderCloud.Catalyst.TestApi/Controllers/IntegrationController.cs b/tests/OrderCloud.Catalyst.TestApi/Controllers/IntegrationController.cs deleted file mode 100644 index bdaa794..0000000 --- a/tests/OrderCloud.Catalyst.TestApi/Controllers/IntegrationController.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using OrderCloud.SDK; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace OrderCloud.Catalyst.TestApi.Controllers -{ - [Route("int")] - public class IntegrationController : CatalystController - { - public IntegrationController() - { - } - - [HttpGet("vertex")] - public async Task GetTaxRates() - { - var command = new VertexOCIntegrationCommand(new VertexOCIntegrationConfig() - { - CompanyName = "OrderCloudTest", - ClientID = "REST-API-SiteCoreUSA", - ClientSecret = "dbbd2e08e5c3bef5f921c0110f17c950", - Username = "e44b802c-d160-4e9f-8f72-614bb08d4d86", - Password = "sD$9/n2A", - }); - - var worksheet = new OrderWorksheet() - { - Order = new Order() - { - ID = "testId", - FromUserID = "testUserID", - FromUser = new User() - { - Email = "oheywood@test.com", - }, - }, - LineItems = new List - { - new LineItem() - { - ProductID = "testProductID", - Quantity = 10, - UnitPrice = 20, - ID = "testLineItemID", - LineTotal = 200, - Product = new LineItemProduct() - { - Name= "productName" - }, - ShippingAddress = new Address() - { - //Street1 = "2418 e menlo blvd", - //State = "wi", - //Zip = "53211", - //City = "shorewood", - //Country = "us" - } - } - }, - ShipEstimateResponse = new ShipEstimateResponse() - { - ShipEstimates = new List { } - } - }; - - return await command.CalculateEstimateAsync(worksheet, new List { }); - } - } -} From 995b14c5a7a780c2833ba1b1c395c9c137002aed Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Mon, 7 Feb 2022 09:23:15 -0600 Subject: [PATCH 26/40] switch exception type from int to HttpStatusCode --- .../Errors/CatalystBaseException.cs | 44 ++++++++----------- .../OrderCloud.Catalyst/Errors/ErrorCode.cs | 7 +-- .../OrderCloud.Catalyst/Errors/Exceptions.cs | 22 +++++----- .../Errors/GlobalExceptionHandler.cs | 6 +-- .../IntegrationAuthFailedException.cs | 5 ++- .../IntegrationErrorResponseException.cs.cs | 3 +- .../IntegrationMissingConfigsException.cs | 3 +- .../IntegrationNoResponseException.cs.cs | 3 +- .../Vertex/Mappers/VertexRequestMapper.cs | 5 ++- .../Vertex/Models/VertexException.cs | 5 +-- 10 files changed, 51 insertions(+), 52 deletions(-) diff --git a/library/OrderCloud.Catalyst/Errors/CatalystBaseException.cs b/library/OrderCloud.Catalyst/Errors/CatalystBaseException.cs index f77e770..f7a366c 100644 --- a/library/OrderCloud.Catalyst/Errors/CatalystBaseException.cs +++ b/library/OrderCloud.Catalyst/Errors/CatalystBaseException.cs @@ -9,31 +9,22 @@ namespace OrderCloud.Catalyst public class CatalystBaseException : Exception { public override string Message => Errors?.FirstOrDefault()?.Message ?? ""; - public int HttpStatus { get; set; } + public HttpStatusCode HttpStatus { get; set; } public IList Errors { get; } - public CatalystBaseException(ApiError apiError, int httpStatus = 400) : base(apiError.Message) - { - HttpStatus = httpStatus; - Errors = new[] { - new ApiError - { - ErrorCode = apiError.ErrorCode, - Message = apiError.Message, - Data = apiError.Data - } - }; - } + public CatalystBaseException(ApiError apiError, HttpStatusCode httpStatus = HttpStatusCode.BadRequest) + : this(apiError.ErrorCode, apiError.Message, apiError.Data, httpStatus) { } - public CatalystBaseException(IList errors, int httpStatus = 400) + + public CatalystBaseException(IList errors, HttpStatusCode httpStatus = HttpStatusCode.BadRequest) { HttpStatus = httpStatus; Require.That(!errors.IsNullOrEmpty(), new Exception("errors collection must contain at least one item.")); Errors = errors; } - public CatalystBaseException(string errorCode, string message, object data = null, int httpStatus = 400) + public CatalystBaseException(string errorCode, string message, object data = null, HttpStatusCode httpStatus = HttpStatusCode.BadRequest) { HttpStatus = httpStatus; Errors = new[] { @@ -46,15 +37,18 @@ public CatalystBaseException(string errorCode, string message, object data = nu } public CatalystBaseException(ErrorCode errorCode, object data = null) - { - HttpStatus = errorCode.HttpStatus; - Errors = new[] { - new ApiError { - ErrorCode = errorCode.Code, - Message = errorCode.DefaultMessage, - Data = data - } - }; - } + : this(errorCode.Code, errorCode.DefaultMessage, data, errorCode.HttpStatus) { } + + + + // Keeping these depreacated constructors that take an Int for backwards compatibility. + //public CatalystBaseException(ApiError apiError, int httpStatus) : this(apiError, (HttpStatusCode)httpStatus) { } + + //public CatalystBaseException(IList errors, int httpStatus): this(errors, (HttpStatusCode)httpStatus) { } + + //public CatalystBaseException(string errorCode, string message, object data, int httpStatus) + // : this(errorCode, message, data, (HttpStatusCode)httpStatus) { } + + } } diff --git a/library/OrderCloud.Catalyst/Errors/ErrorCode.cs b/library/OrderCloud.Catalyst/Errors/ErrorCode.cs index 81e49f4..5d01d50 100644 --- a/library/OrderCloud.Catalyst/Errors/ErrorCode.cs +++ b/library/OrderCloud.Catalyst/Errors/ErrorCode.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; +using System.Net; using System.Text; namespace OrderCloud.Catalyst { public class ErrorCode { - public ErrorCode(string code, string defaultMessage, int httpStatus = 400) + public ErrorCode(string code, string defaultMessage, HttpStatusCode httpStatus = HttpStatusCode.BadRequest) { Code = code; DefaultMessage = defaultMessage; @@ -14,12 +15,12 @@ public ErrorCode(string code, string defaultMessage, int httpStatus = 400) } public string Code { get; set; } - public int HttpStatus { get; set; } + public HttpStatusCode HttpStatus { get; set; } public string DefaultMessage { get; set; } } public class ErrorCode : ErrorCode { - public ErrorCode(string code, int httpStatus, string defaultMessage) : base(code, defaultMessage, httpStatus) { } + public ErrorCode(string code, HttpStatusCode httpStatus, string defaultMessage) : base(code, defaultMessage, httpStatus) { } } } diff --git a/library/OrderCloud.Catalyst/Errors/Exceptions.cs b/library/OrderCloud.Catalyst/Errors/Exceptions.cs index 573fd66..794a0a0 100644 --- a/library/OrderCloud.Catalyst/Errors/Exceptions.cs +++ b/library/OrderCloud.Catalyst/Errors/Exceptions.cs @@ -9,53 +9,53 @@ namespace OrderCloud.Catalyst { public class UnAuthorizedException : CatalystBaseException { - public UnAuthorizedException() : base("InvalidToken", "Access token is invalid or expired.", null, 401) { } + public UnAuthorizedException() : base("InvalidToken", "Access token is invalid or expired.", null, HttpStatusCode.Unauthorized) { } } public class WebhookUnauthorizedException : CatalystBaseException { - public WebhookUnauthorizedException() : base("Unauthorized", "X-oc-hash header does not match. Endpoint can only be hit from a valid OrderCloud webhook.", null, 401) { } + public WebhookUnauthorizedException() : base("Unauthorized", "X-oc-hash header does not match. Endpoint can only be hit from a valid OrderCloud webhook.", null, HttpStatusCode.Unauthorized) { } } public class InsufficientRolesException : CatalystBaseException { - public InsufficientRolesException(InsufficientRolesError data) : base("InsufficientRoles", "User does not have role(s) required to perform this action.", data, 403) { } + public InsufficientRolesException(InsufficientRolesError data) : base("InsufficientRoles", "User does not have role(s) required to perform this action.", data, HttpStatusCode.Forbidden) { } } public class InvalidUserTypeException : CatalystBaseException { - public InvalidUserTypeException(InvalidUserTypeError data) : base("InvalidUserType", $"Users of type {data.ThisUserType} do not have permission to perform this action.", data, 403) { } + public InvalidUserTypeException(InvalidUserTypeError data) : base("InvalidUserType", $"Users of type {data.ThisUserType} do not have permission to perform this action.", data, HttpStatusCode.Forbidden) { } } public class RequiredFieldException : CatalystBaseException { - public RequiredFieldException(string fieldName) : base("RequiredField", $"Field {fieldName} is required", null, 400) { } + public RequiredFieldException(string fieldName) : base("RequiredField", $"Field {fieldName} is required", null, HttpStatusCode.BadRequest) { } } public class NotFoundException : CatalystBaseException { - public NotFoundException() : base("NotFound", $"Not found.", null, 404) { } + public NotFoundException() : base("NotFound", $"Not found.", null, HttpStatusCode.NotFound) { } - public NotFoundException(string thingName, string thingID) : base("NotFound", "Not Found.", new { ObjectType = thingName, ObjectID = thingID }, 404) { } + public NotFoundException(string thingName, string thingID) : base("NotFound", "Not Found.", new { ObjectType = thingName, ObjectID = thingID }, HttpStatusCode.NotFound) { } } public class InvalidPropertyException : CatalystBaseException { - public InvalidPropertyException(Type type, string name) : base("InvalidProperty", $"{type.Name}.{name}", null, 400) { } + public InvalidPropertyException(Type type, string name) : base("InvalidProperty", $"{type.Name}.{name}", null, HttpStatusCode.BadRequest) { } } public class UserErrorException : CatalystBaseException { - public UserErrorException(string message) : base("InvalidRequest", message, null, 400) { } + public UserErrorException(string message) : base("InvalidRequest", message, null, HttpStatusCode.BadRequest) { } } public class UserContextException : CatalystBaseException { - public UserContextException(string message) : base("UserContextError", message, null, 400) { } + public UserContextException(string message) : base("UserContextError", message, null, HttpStatusCode.BadRequest) { } } public class WrongEnvironmentException : CatalystBaseException { - public WrongEnvironmentException(WrongEnvironmentError data) : base("InvalidToken", $"Environment mismatch. Token gives access to {data.TokenIssuerEnvironment} while this API expects {data.ExpectedEnvironment}", data, 401) { } + public WrongEnvironmentException(WrongEnvironmentError data) : base("InvalidToken", $"Environment mismatch. Token gives access to {data.TokenIssuerEnvironment} while this API expects {data.ExpectedEnvironment}", data, HttpStatusCode.Unauthorized) { } } public class WrongEnvironmentError diff --git a/library/OrderCloud.Catalyst/Errors/GlobalExceptionHandler.cs b/library/OrderCloud.Catalyst/Errors/GlobalExceptionHandler.cs index d299b6c..c29a093 100644 --- a/library/OrderCloud.Catalyst/Errors/GlobalExceptionHandler.cs +++ b/library/OrderCloud.Catalyst/Errors/GlobalExceptionHandler.cs @@ -35,7 +35,7 @@ public static IApplicationBuilder UseCatalystExceptionHandler(this IApplicationB private static Task HandleExceptionAsync(HttpContext context, Exception ex) { IList body; - int status = (int) HttpStatusCode.InternalServerError; // 500 if unexpected + var status = HttpStatusCode.InternalServerError; // 500 if unexpected switch (ex) { @@ -57,7 +57,7 @@ private static Task HandleExceptionAsync(HttpContext context, Exception ex) } else // forward status code and errors from OrderCloud API { - status = (int) ocException.HttpStatus; + status = ocException.HttpStatus ?? HttpStatusCode.BadRequest; body = ocException.Errors; } break; @@ -73,7 +73,7 @@ private static Task HandleExceptionAsync(HttpContext context, Exception ex) break; } - context.Response.StatusCode = status; + context.Response.StatusCode = (int) status; context.Response.ContentType = "application/json"; return context.Response.WriteAsync(JsonConvert.SerializeObject(new ErrorList(body))); } diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs index a2ee365..4e31b8f 100644 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using System.Text; namespace OrderCloud.Catalyst @@ -13,8 +14,8 @@ public IntegrationAuthFailedException(OCIntegrationConfig config, string request { ServiceName = config.ServiceName, RequestUrl = requestUrl, - }, - 400) {} + }, + HttpStatusCode.BadRequest) {} } public class IntegrationAuthFailedError diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs.cs index fc37af3..566dd0d 100644 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs.cs +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using System.Text; namespace OrderCloud.Catalyst @@ -15,7 +16,7 @@ public IntegrationErrorResponseException(OCIntegrationConfig config, string requ RequestUrl = requestUrl, ResponseBody = responseBody } - , 400) + , HttpStatusCode.BadRequest) { } } diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs index 80a4626..add15f7 100644 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using System.Text; namespace OrderCloud.Catalyst @@ -14,7 +15,7 @@ public IntegrationMissingConfigsException(OCIntegrationConfig config, List { var firstLi = order.LineItems.FirstOrDefault(li => li.ID == se.ShipEstimateItems.First().LineItemID); - Require.That(firstLi != null, new CatalystBaseException("InvalidOrderWorksheet", $"Invalid OrderWorksheet. Based on ShipEstimateItems, expected to find a LineItem with ID {se.ShipEstimateItems.First().LineItemID}", null, 400)); + Require.That(firstLi != null, new CatalystBaseException("InvalidOrderWorksheet", $"Invalid OrderWorksheet. Based on ShipEstimateItems, expected to find a LineItem with ID {se.ShipEstimateItems.First().LineItemID}", null, HttpStatusCode.BadRequest)); return ToVertexLineItem(se, firstLi.ShippingAddress); }); @@ -67,7 +68,7 @@ public static VertexLineItem ToVertexLineItem(LineItem lineItem) public static VertexLineItem ToVertexLineItem(ShipEstimate shipEstimate, Address shipTo) { var selectedMethod = shipEstimate.ShipMethods.FirstOrDefault(m => m.ID == shipEstimate.SelectedShipMethodID); - Require.That(selectedMethod != null, new CatalystBaseException("InvalidOrderWorksheet", $"Invalid OrderWorksheet. Based on SelectedShipMethodID, expected to find a ShipMethod with ID {shipEstimate.SelectedShipMethodID}", null, 400)); + Require.That(selectedMethod != null, new CatalystBaseException("InvalidOrderWorksheet", $"Invalid OrderWorksheet. Based on SelectedShipMethodID, expected to find a ShipMethod with ID {shipEstimate.SelectedShipMethodID}", null, HttpStatusCode.BadRequest)); return new VertexLineItem() { customer = new VertexCustomer() diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexException.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexException.cs index c2a3cb4..9039575 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexException.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexException.cs @@ -12,8 +12,7 @@ public VertexException(List errors) : base( "VertexTaxCalculationError", "The vertex api returned an error: https://restconnect.vertexsmb.com/vertex-restapi/v1/sale", - errors, - int.Parse(errors.First().status - )) { } + errors + ) { } } } From 184863293d6f266a1b6d64fdb4db39562af67799 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Mon, 7 Feb 2022 09:39:38 -0600 Subject: [PATCH 27/40] change double to decimal --- ...on.cs.cs => IntegrationErrorResponseException.cs} | 0 ...ption.cs.cs => IntegrationNoResponseException.cs} | 0 .../Vertex/Mappers/VertexRequestMapper.cs | 8 ++++---- .../Vertex/Models/VertexCalculateTaxResponse.cs | 6 +++--- .../Implementations/Vertex/Models/VertexDiscount.cs | 2 +- .../Vertex/Models/VertexJurisdiction.cs | 2 +- .../Vertex/Models/VertexResponseLineItem.cs | 12 ++++++------ 7 files changed, 15 insertions(+), 15 deletions(-) rename library/OrderCloud.Catalyst/Integrations/Exceptions/{IntegrationErrorResponseException.cs.cs => IntegrationErrorResponseException.cs} (100%) rename library/OrderCloud.Catalyst/Integrations/Exceptions/{IntegrationNoResponseException.cs.cs => IntegrationNoResponseException.cs} (100%) diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs.cs rename to library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs.cs rename to library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs index e143d15..a919fab 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs @@ -22,7 +22,7 @@ public static VertexCalculateTaxRequest ToVertexCalculateTaxRequest(this OrderWo return new VertexCalculateTaxRequest() { - postingDate = DateTime.Now.ToString("yyyy-MM-dd"), + postingDate = DateTime.UtcNow.ToString("yyyy-MM-dd"), saleMessageType = type.ToString(), transactionType = VertexTransactionType.SALE.ToString(), transactionId = order.Order.ID, @@ -59,9 +59,9 @@ public static VertexLineItem ToVertexLineItem(LineItem lineItem) { value = lineItem.Quantity }, - unitPrice = (double) lineItem.UnitPrice, + unitPrice = lineItem.UnitPrice ?? 0, lineItemId = lineItem.ID, - extendedPrice = (double) lineItem.LineTotal // this takes precedence over quanitity and unit price in determining tax cost + extendedPrice = lineItem.LineTotal // this takes precedence over quanitity and unit price in determining tax cost }; } @@ -84,7 +84,7 @@ public static VertexLineItem ToVertexLineItem(ShipEstimate shipEstimate, Address { value = 1 }, - unitPrice = (double) selectedMethod.Cost, + unitPrice = selectedMethod.Cost, lineItemId = shipEstimate.ID, }; } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxResponse.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxResponse.cs index 48b64b2..3429fb0 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxResponse.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxResponse.cs @@ -29,9 +29,9 @@ public class VertexResponseError public class VertexCalculateTaxResponse : VertexCalculateTaxRequest { - public double subTotal { get; set; } - public double total { get; set; } - public double totalTax { get; set; } + public decimal subTotal { get; set; } + public decimal total { get; set; } + public decimal totalTax { get; set; } public new List lineItems { get; set; } = new List(); } } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexDiscount.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexDiscount.cs index ccd029d..77cfa49 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexDiscount.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexDiscount.cs @@ -6,7 +6,7 @@ namespace OrderCloud.Catalyst { public class VertexDiscount { - public double discountValue { get; set; } + public decimal discountValue { get; set; } public VertexDiscountType discountType { get; set; } public string userDefinedDiscountCode { get; set; } } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexJurisdiction.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexJurisdiction.cs index f0ff1cd..4ea12a3 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexJurisdiction.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexJurisdiction.cs @@ -42,7 +42,7 @@ public enum VertexJurisdictionLevel public class VertexRateOverride { - public double value { get; set; } + public decimal value { get; set; } } public class VertexDeductionOverride diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexResponseLineItem.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexResponseLineItem.cs index f3a858d..097a785 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexResponseLineItem.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexResponseLineItem.cs @@ -6,7 +6,7 @@ namespace OrderCloud.Catalyst { public class VertexResponseLineItem : VertexLineItem { - public double totalTax { get; set; } + public decimal totalTax { get; set; } public List taxes { get; set; } = new List(); } @@ -19,23 +19,23 @@ public class VertexTax /// /// Amount of tax calculated by the calculation engine. /// - public double calculatedTax { get; set; } + public decimal calculatedTax { get; set; } /// /// For Buyer Input tax and Seller Import tax, this rate is calculated based on the Extended Price and Tax Amount (Import or Input) passed in the Request message. If you total the Extended Price and Tax Amounts before passing them in, this rate is an average. /// - public double effectiveRate { get; set; } + public decimal effectiveRate { get; set; } /// /// Amount of the line item not subject to tax due to exempt status. /// - public double exempt { get; set; } + public decimal exempt { get; set; } /// /// Amount of the line item not subject to tax due to nontaxable status. /// - public double nonTaxable { get; set; } + public decimal nonTaxable { get; set; } /// /// Amount of the line item subject to tax. /// - public double taxable { get; set; } + public decimal taxable { get; set; } /// /// The name of the imposition to which the relevant tax rule belongs. This is assigned either by Vertex or by the user when setting up a user-defined imposition in the Vertex Central user interface. /// From d21476684ac2f96bbd103b3596403841f10923f9 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Mon, 7 Feb 2022 09:44:07 -0600 Subject: [PATCH 28/40] putting shipping code into a const --- .../Implementations/Vertex/Mappers/VertexRequestMapper.cs | 4 +++- .../Implementations/Vertex/Mappers/VertexResponseMapper.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs index a919fab..de74850 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs @@ -10,6 +10,8 @@ namespace OrderCloud.Catalyst { public static class VertexRequestMapper { + public const string ShippingLineCode = "shipping_code"; + public static VertexCalculateTaxRequest ToVertexCalculateTaxRequest(this OrderWorksheet order, List promosOnOrder, string companyCode, VertexSaleMessageType type) { var itemLines = order.LineItems.Select(li => ToVertexLineItem(li)); @@ -77,7 +79,7 @@ public static VertexLineItem ToVertexLineItem(ShipEstimate shipEstimate, Address }, product = new VertexProduct() { - productClass = "shipping_code", + productClass = ShippingLineCode, value = selectedMethod.Name }, quantity = new VertexMeasure() diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs index 8d47eec..a514854 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs @@ -7,7 +7,7 @@ namespace OrderCloud.Catalyst { public static class VertexResponseMapper { - public static bool IsShippingLineItem(this VertexResponseLineItem li) => li.product.productClass == "shipping_code"; + public static bool IsShippingLineItem(this VertexResponseLineItem li) => li.product.productClass == VertexRequestMapper.ShippingLineCode; public static OrderTaxCalculation ToOrderTaxCalculation(this VertexCalculateTaxResponse response) { From 15e75098a26d585674ae8a614f7be74ccf332c1d Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Mon, 7 Feb 2022 10:35:00 -0600 Subject: [PATCH 29/40] update readme --- .../{README.md => CONTRIBUTING.md} | 29 ++++++++++++++----- .../Integrations/Implementations/README.md | 2 +- .../Integrations/OCIntegrationConfig.cs | 2 +- 3 files changed, 23 insertions(+), 10 deletions(-) rename library/OrderCloud.Catalyst/Integrations/{README.md => CONTRIBUTING.md} (66%) diff --git a/library/OrderCloud.Catalyst/Integrations/README.md b/library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md similarity index 66% rename from library/OrderCloud.Catalyst/Integrations/README.md rename to library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md index c2e1c07..56af291 100644 --- a/library/OrderCloud.Catalyst/Integrations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing Guide For Integrations -Guidelines for adding a new integration to the Catalyst library. +Thank you for investing your time in contributing! These are guidelines for adding a new integration to the Catalyst library. See a list of [existing integrations](./Implementations/README.md). ## Basics @@ -50,14 +50,27 @@ Feel free open issues recommending changes or additions to the interfaces. ## Guidelines - - Keep the number of properties and methods on your exposed contracts to the minimum required. Do a small amount well. - - Under [/Integrations/Implementations](./Implementations) create a folder with your service name (e.g. "Mississippi") to contain your files. At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. - - Handle error scenarios within your integration by throwing one of the exceptions in [/Integrations/Exceptions](./Exceptions). Key scenarios to handle are missing configs, invalid authentication, error response, and no response. - - Avoid adding a nuget package for your service's SDK. This will lead to bloat as many projects may use this library without using your service. Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. - - Write unit tests against your Command methods and put them in the OrderCloud.Catalyst.Tests project under a new folder like so `/IntegrationTests/[ServiceName]/[ServiceName]Tests.cs`. Mock API reponses from your service using [Flurl test practices](https://flurl.dev/docs/testable-http/) or something similar. - - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. - - Aim to follow the code patterns of existing integrations. + - General + - Keep the number of properties and methods on your exposed contracts to the minimum required. Do a small amount well. + - Aim to follow the code patterns of existing integrations. + - Folders and files + - Under [/Integrations/Implementations](./Implementations) create a folder with your service name (e.g. "Mississippi") to contain your files. + - At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. + - Errors + - Handle error scenarios within your integration by throwing one of the exceptions in [/Integrations/Exceptions](./Exceptions). + - Every integration should handle cases like missing configs, invalid authentication, error response, and no response. + - Tests + - Write unit tests against your Command methods and put them in the OrderCloud.Catalyst.Tests project under a new folder like so `/IntegrationTests/[ServiceName]/[ServiceName]Tests.cs`. + - Mock API reponses from your service using [Flurl test practices](https://flurl.dev/docs/testable-http/) or something similar. + - Test error scenarios as well. + - See [VertexTests](../../../tests.OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs). + - Code Style + - Avoid adding a nuget package for your service's SDK. This will lead to bloat as many projects may use this library without using your service. Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. + - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. + - Use DateTime.Utc explicitly to keep the project time-zone agnostic. + - Always use the `decimal` type for anything money related. ## Approval A disclaimer, whether a pull request with a new integration is accepted and published will ultimately depend on the approval of the OrderCloud team. + diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/README.md b/library/OrderCloud.Catalyst/Integrations/Implementations/README.md index 076d4d2..9037a61 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/README.md @@ -1,5 +1,5 @@ # List of Integrations -| Name | Website | Integration Guide | Contributed By | Categories | +| Name | Website | Integration Guide | Contributed By | Categories | | ------------- | ------------- | ------------- | ------------- | ------------- | | Vertex | https://www.vertexinc.com/ | [./Vertex/README.md](./Vertex/README.md) | OrderCloud Team | Tax diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs index 80652a3..983dc72 100644 --- a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs +++ b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs @@ -20,7 +20,7 @@ public void ValidateRequiredFields() { var value = (string)prop.GetValue(this); var isRequired = Attribute.IsDefined(prop, typeof(RequiredIntegrationFieldAttribute)); - return isRequired && (value == null || value == ""); + return isRequired && value.IsNullOrEmpty(); }); if (missing.Any()) From 56023ea2c372becd5da604310c1d72a113c89821 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Mon, 7 Feb 2022 14:05:27 -0600 Subject: [PATCH 30/40] taxjar is working. still needs tests and error handling. --- README.md | 2 +- .../TaxJar/Mapper/TaxJarCategoryMapper.cs | 37 ++++++ .../TaxJar/Mapper/TaxJarRequestMapper.cs | 94 +++++++++++++++ .../TaxJar/Mapper/TaxJarResponseMapper.cs | 107 ++++++++++++++++++ .../TaxJar/Models/TaxJarCalculateResponse.cs | 79 +++++++++++++ .../TaxJar/Models/TaxJarCategory.cs | 18 +++ .../TaxJar/Models/TaxJarOrder.cs | 43 +++++++ .../Implementations/TaxJar/TaxJarClient.cs | 45 ++++++++ .../TaxJar/TaxJarOCIntegrationCommand.cs | 59 ++++++++++ .../TaxJar/TaxJarOCIntegrationConfig.cs | 13 +++ .../Vertex/Models/VertexLineItem.cs | 6 +- .../Interfaces/ITaxCodeProvider.cs | 42 +++++++ .../Integrations/OCIntegrationConfig.cs | 2 - 13 files changed, 541 insertions(+), 6 deletions(-) create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarCategoryMapper.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarResponseMapper.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCalculateResponse.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCategory.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarOrder.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationCommand.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationConfig.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCodeProvider.cs diff --git a/README.md b/README.md index 21bdf80..f793baa 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ If you're building solutions for OrderCloud using .NET and find a particular tas Interact with popular 3rd party APIs that provide functionality useful for commerce. Integrations within a category are made interoperable with an interface. -[Guide to adding an Integration](./library/OrderCLoud.Catalyst/Integrations/README.md) +[Guide to adding an Integration](./library/OrderCLoud.Catalyst/Integrations/CONTRIBUTING.md) ### [User Authentication](https://github.com/ordercloud-api/ordercloud-dotnet-catalyst/tree/dev/library/OrderCloud.Catalyst/Auth/UserAuth) diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarCategoryMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarCategoryMapper.cs new file mode 100644 index 0000000..29d7e71 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarCategoryMapper.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OrderCloud.Catalyst +{ + public static class TaxJarCategoryMapper + { + public static TaxCategorizationResponse ToTaxCategorization(TaxJarCategories list, string filterTerm) + { + var categories = list.categories + .Where(c => MatchesSearch(c, filterTerm)) + .Select(ToTaxCategorization) + .ToList(); + + return new TaxCategorizationResponse() { Categories = categories }; + } + + public static bool MatchesSearch(TaxJarCategory category, string filterTerm) + { + if (filterTerm.IsNullOrEmpty()) { return true; } + var filter = filterTerm.ToLower(); + return category.name.ToLower().Contains(filter) || category.description.ToLower().Contains(filter); + } + + public static TaxCategorization ToTaxCategorization(TaxJarCategory category) + { + return new TaxCategorization() + { + Code = category.product_tax_code, + Description = category.name, + LongDescription = category.description + }; + } + + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs new file mode 100644 index 0000000..793efe8 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs @@ -0,0 +1,94 @@ +using OrderCloud.SDK; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public static class TaxJarRequestMapper + { + public const string ShippingLineCode = "shipping_code"; + private static string CreateLineTransactionID(string orderID, string lineItemID) => $"OrderID:|{orderID}|LineItemsID:|{lineItemID}"; + private static string CreateShipTransactionID(string orderID, string shipEstimateID) => $"OrderID:|{orderID}|ShippingEstimateID:|{shipEstimateID}"; + + /// + /// Returns a list of TaxJarOrders because each order has a single to and from address. They therefor coorespond to OrderCloud LineItems. + /// + public static List ToOrders(OrderWorksheet order) + { + var itemLines = order.LineItems.Select(li => ToTaxJarLineOrder(li, order.Order.ID)); + var shippingLines = order.ShipEstimateResponse.ShipEstimates.Select(se => + { + var firstLineItem = order.LineItems.First(li => li.ID == se.ShipEstimateItems.First().LineItemID); + return ToTaxJarShipOrder(se, firstLineItem, order.Order.ID); + }); + return itemLines.Concat(shippingLines).ToList(); + } + + private static TaxJarOrder ToTaxJarShipOrder(ShipEstimate shipEstimate, LineItem lineItem, string orderID) + { + var selectedShipMethod = shipEstimate.ShipMethods.First(x => x.ID == shipEstimate.SelectedShipMethodID); + return new TaxJarOrder() + { + transaction_id = CreateShipTransactionID(orderID, shipEstimate.ID), + shipping = 0, // will create separate lines for shipping + + from_city = lineItem.ShipFromAddress.City, + from_zip = lineItem.ShipFromAddress.Zip, + from_state = lineItem.ShipFromAddress.State, + from_country = lineItem.ShipFromAddress.Country, + from_street = lineItem.ShipFromAddress.Street1, + + to_city = lineItem.ShippingAddress.City, + to_zip = lineItem.ShippingAddress.Zip, + to_state = lineItem.ShippingAddress.State, + to_country = lineItem.ShippingAddress.Country, + to_street = lineItem.ShippingAddress.Street1, + + line_items = new List { + new TaxJarLineItem() + { + id = shipEstimate.ID, + quantity = 1, + unit_price = selectedShipMethod.Cost, + description = selectedShipMethod.Name, + product_identifier = ShippingLineCode, + } + } + }; + } + + private static TaxJarOrder ToTaxJarLineOrder(LineItem lineItem, string orderID) + { + return new TaxJarOrder() + { + transaction_id = CreateLineTransactionID(orderID, lineItem.ID), + shipping = 0, // will create separate lines for shipping + + from_city = lineItem.ShipFromAddress.City, + from_zip = lineItem.ShipFromAddress.Zip, + from_state = lineItem.ShipFromAddress.State, + from_country = lineItem.ShipFromAddress.Country, + from_street = lineItem.ShipFromAddress.Street1, + + to_city = lineItem.ShippingAddress.City, + to_zip = lineItem.ShippingAddress.Zip, + to_state = lineItem.ShippingAddress.State, + to_country = lineItem.ShippingAddress.Country, + to_street = lineItem.ShippingAddress.Street1, + + line_items = new List { + new TaxJarLineItem() + { + id = lineItem.ID, + quantity = lineItem.Quantity, + unit_price = lineItem.UnitPrice ?? 0, + description = lineItem.Product.Name, + product_identifier = lineItem.Product.ID, + } + } + }; + } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarResponseMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarResponseMapper.cs new file mode 100644 index 0000000..2be7678 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarResponseMapper.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using System.Linq; + +namespace OrderCloud.Catalyst +{ + public static class TaxJarResponseMapper + { + public static OrderTaxCalculation ToOrderTaxCalculation(IEnumerable<(TaxJarOrder request, TaxJarCalcResponse response)> taxJarOrders) + { + var itemLines = taxJarOrders.Where(r => r.request.line_items.First().product_identifier != TaxJarRequestMapper.ShippingLineCode); + var shippingLines = taxJarOrders.Where(r => r.request.line_items.First().product_identifier == TaxJarRequestMapper.ShippingLineCode); + + return new OrderTaxCalculation() + { + OrderID = GetOrderID(taxJarOrders.First().request), + ExternalTransactionID = null, // There are multiple external transactionIDs + TotalTax = taxJarOrders.Select(r => r.response.tax.amount_to_collect).Sum(), + LineItems = itemLines.Select(ToItemTaxDetails).ToList(), + OrderLevelTaxes = shippingLines.SelectMany(ToShippingTaxDetails).ToList() + }; + } + + private static string GetOrderID(TaxJarOrder order) => order.transaction_id.Split('|')[1]; + private static string GetLineOrShipID(TaxJarOrder order) => order.transaction_id.Split('|')[3]; + + + private static LineItemTaxCalculation ToItemTaxDetails((TaxJarOrder request, TaxJarCalcResponse response) taxJarOrder) + { + return new LineItemTaxCalculation() + { + LineItemID = GetLineOrShipID(taxJarOrder.request), + LineItemTotalTax = taxJarOrder.response.tax.amount_to_collect, + LineItemLevelTaxes = ToTaxDetails(taxJarOrder.response) + }; + } + + private static List ToShippingTaxDetails((TaxJarOrder request, TaxJarCalcResponse response) taxJarOrder) + { + var taxes = ToTaxDetails(taxJarOrder.response); + var shipEstimateID = GetLineOrShipID(taxJarOrder.request); + foreach (var tax in taxes) + { + tax.ShipEstimateID = shipEstimateID; + } + return taxes; + } + + private static List ToTaxDetails(TaxJarCalcResponse taxResponse) + { + var breakdown = taxResponse.tax.breakdown.line_items.First(); + var jurisidctions = new List(); + if (breakdown.county_amount> 0) + { + jurisidctions.Add(new TaxDetails() + { + Tax = breakdown.county_amount, + Taxable = breakdown.county_taxable_amount, + Exempt = 0, + JurisdictionLevel = "County", + JurisdictionValue = taxResponse.tax.jurisdictions.county, + TaxDescription = $"{taxResponse.tax.jurisdictions.county} County tax", + ShipEstimateID = null + }); + } + if (breakdown.city_amount > 0) + { + jurisidctions.Add(new TaxDetails() + { + Tax = breakdown.city_amount, + Taxable = breakdown.city_taxable_amount, + Exempt = 0, + JurisdictionLevel = "City", + JurisdictionValue = taxResponse.tax.jurisdictions.city, + TaxDescription = $"{taxResponse.tax.jurisdictions.city} City tax", + ShipEstimateID = null + }); + } + if (breakdown.state_amount > 0) + { + jurisidctions.Add(new TaxDetails() + { + Tax = breakdown.state_amount, + Taxable = breakdown.state_taxable_amount, + Exempt = 0, + JurisdictionLevel = "State", + JurisdictionValue = taxResponse.tax.jurisdictions.state, + TaxDescription = $"{taxResponse.tax.jurisdictions.state} State tax", + ShipEstimateID = null + }); + } + if (breakdown.special_district_amount > 0) + { + jurisidctions.Add(new TaxDetails() + { + Tax = breakdown.special_district_amount, + Taxable = breakdown.special_district_taxable_amount, + Exempt = 0, + JurisdictionLevel = "Special District", + JurisdictionValue = "Special District", + TaxDescription = "Special District tax", + ShipEstimateID = null + }); + } + return jurisidctions; + } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCalculateResponse.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCalculateResponse.cs new file mode 100644 index 0000000..e07e4e4 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCalculateResponse.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class TaxJarCalcResponse + { + public TaxJarTax tax { get; set; } + } + + public class TaxJarTax + { + public decimal order_total_amount { get; set; } + public decimal shipping { get; set; } + public decimal taxable_amount { get; set; } + public decimal amount_to_collect { get; set; } + public decimal rate { get; set; } + public bool has_nexus { get; set; } + public bool freight_taxable { get; set; } + public string tax_source { get; set; } + public string exemption_type { get; set; } + public TaxJarJurisdiction jurisdictions { get; set; } + public TaxJarBreakdown breakdown { get; set; } + } + + public class TaxJarJurisdiction + { + public string country { get; set; } + public string state { get; set; } + public string county { get; set; } + public string city { get; set; } + } + + public class TaxJarBreakdown + { + public decimal state_tax_rate { get; set; } + public decimal state_tax_collectable { get; set; } + public decimal state_taxable_amount { get; set; } + public decimal county_tax_collectable { get; set; } + public decimal county_taxable_amount { get ; set; } + public decimal city_tax_collectable { get; set; } + public decimal city_taxable_amount { get; set; } + public decimal special_tax_rate { get; set; } + public decimal special_district_tax_collectable { get; set; } + public decimal special_district_taxable_amount { get; set; } + public TaxBreakdownShipping shipping { get; set; } + public List line_items { get; set; } + } + + public class TaxBreakdownLineItem + { + public string id { get; set; } + public decimal state_sales_tax_rate { get; set; } + public decimal state_taxable_amount { get; set; } + public decimal state_amount { get; set; } + public decimal county_amount { get; set; } + public decimal county_taxable_amount { get; set; } + public decimal city_amount { get; set; } + public decimal city_taxable_amount { get; set; } + public decimal special_district_taxable_amount { get; set; } + public decimal special_tax_rate { get; set; } + public decimal special_district_amount { get; set; } + } + + public class TaxBreakdownShipping + { + public decimal state_sales_tax_rate { get; set; } + public decimal state_taxable_amount { get; set; } + public decimal state_amount { get; set; } + public decimal county_amount { get; set; } + public decimal county_taxable_amount { get; set; } + public decimal city_amount { get; set; } + public decimal city_taxable_amount { get; set; } + public decimal special_taxable_amount { get; set; } + public decimal special_tax_rate { get; set; } + public decimal special_district_amount { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCategory.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCategory.cs new file mode 100644 index 0000000..85c44d5 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCategory.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class TaxJarCategories + { + public List categories { get; set; } = new List { }; + } + + public class TaxJarCategory + { + public string name { get; set; } + public string product_tax_code { get; set; } + public string description { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarOrder.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarOrder.cs new file mode 100644 index 0000000..fa875fc --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarOrder.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class TaxJarOrder + { + public decimal sales_tax { get; set; } + public decimal shipping { get; set; } + public decimal amount { get; set; } + public string to_street { get; set; } + public string to_city { get; set; } + public string to_state { get; set; } + public string to_zip { get; set; } + public string to_country { get; set; } + public string from_street { get; set; } + public string from_city { get; set; } + public string from_state { get; set; } + public string from_zip { get; set; } + public string from_country { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string exemption_type { get; set; } + public string provider { get; set; } + public string transaction_date { get; set; } + public string transaction_id { get; set; } + public string customer_id { get; set; } + public List line_items { get; set; } + } + + public class TaxJarLineItem + { + public string id { get; set; } + public int quantity { get; set; } + public string product_identifier { get; set; } + public string description { get; set; } + public string product_tax_code { get; set; } + public decimal unit_price { get; set; } + public decimal discount { get; set; } + public decimal sales_tax { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs new file mode 100644 index 0000000..f411d91 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs @@ -0,0 +1,45 @@ +using Flurl.Http; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace OrderCloud.Catalyst +{ + public class TaxJarClient + { + protected readonly FlurlClient _flurl; + + public TaxJarClient(TaxJarOCIntegrationConfig config) + { + _flurl = new FlurlClient(config.BaseUrl).WithOAuthBearerToken(config.APIToken); + } + + /// + /// https://developers.taxjar.com/api/reference/#post-create-an-order-transaction + /// + public async Task CreateOrderAsync(TaxJarOrder order) + { + var tax = await _flurl.Request("v2", "taxes", "orders").PostJsonAsync(order).ReceiveJson(); + return tax; + } + + /// + /// https://developers.taxjar.com/api/reference/#get-list-tax-categories + /// + public async Task ListCategoriesAsync() + { + var categories = await _flurl.Request("v2", "categories").GetJsonAsync(); + return categories; + } + + /// + /// https://developers.taxjar.com/api/reference/#post-calculate-sales-tax-for-an-order + /// + public async Task CalcTaxForOrderAsync(TaxJarOrder order) + { + var tax = await _flurl.Request("v2", "taxes").PostJsonAsync(order).ReceiveJson(); + return tax; + } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationCommand.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationCommand.cs new file mode 100644 index 0000000..5020e38 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationCommand.cs @@ -0,0 +1,59 @@ +using Flurl.Http; +using OrderCloud.SDK; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace OrderCloud.Catalyst +{ + public class TaxJarOCIntegrationCommand : OCIntegrationCommand, ITaxCalculator, ITaxCodesProvider + { + protected readonly TaxJarOCIntegrationConfig _config; + protected readonly TaxJarClient _client; + + public TaxJarOCIntegrationCommand(TaxJarOCIntegrationConfig config) : base(config) + { + _config = config; + _client = new TaxJarClient(config); + } + + public async Task ListTaxCodesAsync(string filterTerm = "") + { + var categories = await _client.ListCategoriesAsync(); + return TaxJarCategoryMapper.ToTaxCategorization(categories, filterTerm); + } + + public async Task CalculateEstimateAsync(OrderWorksheet orderWorksheet, List promotions) + { + var orders = await CalculateTax(orderWorksheet); + var orderTaxCalculation = TaxJarResponseMapper.ToOrderTaxCalculation(orders); + return orderTaxCalculation; + } + + public async Task CommitTransactionAsync(OrderWorksheet orderWorksheet, List promotions) + { + var orders = await CalculateTax(orderWorksheet); + foreach (var response in orders) + { + response.request.transaction_date = DateTime.UtcNow.ToString("yyyy/MM/dd"); + response.request.sales_tax = response.response.tax.amount_to_collect; + } + + await Throttler.RunAsync(orders, 100, 8, async order => await _client.CreateOrderAsync(order.request)); + + var orderTaxCalculation = TaxJarResponseMapper.ToOrderTaxCalculation(orders); + return orderTaxCalculation; + } + + protected async Task> CalculateTax(OrderWorksheet orderWorksheet) + { + var orders = TaxJarRequestMapper.ToOrders(orderWorksheet); + return await Throttler.RunAsync(orders, 100, 8, async order => + { + var tax = await _client.CalcTaxForOrderAsync(order); + return (order, tax); + }); + } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationConfig.cs new file mode 100644 index 0000000..b604f6c --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationConfig.cs @@ -0,0 +1,13 @@ +using System; + +namespace OrderCloud.Catalyst +{ + public class TaxJarOCIntegrationConfig : OCIntegrationConfig + { + public override string ServiceName { get; } = "TaxJar"; + [RequiredIntegrationField] + public string BaseUrl { get; set; } + [RequiredIntegrationField] + public string APIToken { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLineItem.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLineItem.cs index c5c5de4..c9cb472 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLineItem.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLineItem.cs @@ -15,8 +15,8 @@ public class VertexLineItem /// A standardized, unique code for the product or service. /// public VertexMeasure quantity { get; set; } - public double unitPrice { get; set; } - public double extendedPrice { get; set; } + public decimal unitPrice { get; set; } + public decimal extendedPrice { get; set; } public VertexDiscount discount { get; set; } /// /// An identifier that further defines the line item and corresponds to the transaction stored in the host system. This parameter is needed to perform synchronization services, but it is not used for reporting purposes. @@ -26,7 +26,7 @@ public class VertexLineItem public class VertexMeasure { - public double value { get; set; } + public decimal value { get; set; } } public class VertexProduct diff --git a/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCodeProvider.cs b/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCodeProvider.cs new file mode 100644 index 0000000..bb35fcd --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCodeProvider.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace OrderCloud.Catalyst +{ + /// + /// An interface to define tax categorization for products. Meant to be used as part of product create and edit. + /// + public interface ITaxCodesProvider + { + /// + /// List the various tax categories a product could fall under + /// + Task ListTaxCodesAsync(string filterTerm); + } + + public class TaxCategorizationResponse + { + public List Categories { get; set; } + } + + /// + /// A Tax categorization for a product + /// + public class TaxCategorization + { + /// + /// A code that represents this categorization + /// + public string Code { get; set; } + /// + /// A reasonable short name for this categorization + /// + public string Description { get; set; } + /// + /// A full description for this categorization + /// + public string LongDescription { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs index 983dc72..1bad25a 100644 --- a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs +++ b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs @@ -30,6 +30,4 @@ public void ValidateRequiredFields() } } } - - } From 0567b8334621ad2b6edaa9746ad92e658d4d3afd Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Tue, 8 Feb 2022 10:37:19 -0600 Subject: [PATCH 31/40] vertex looking pretty good --- .../Extensions/ExtensionMethods.cs | 31 ++++ .../IntegrationAuthFailedException.cs | 2 +- .../IntegrationErrorResponseException.cs | 2 +- .../IntegrationMissingConfigsException.cs | 3 +- .../IntegrationNoResponseException.cs | 2 +- .../Integrations/Implementations/README.md | 3 +- .../TaxJar/Mapper/TaxJarRequestMapper.cs | 6 +- .../TaxJar/Models/TaxJarError.cs | 13 ++ .../Implementations/TaxJar/README.md | 40 +++++ .../Implementations/TaxJar/TaxJarClient.cs | 39 ++++- .../Vertex/Mappers/VertexRequestMapper.cs | 10 +- .../Vertex/Mappers/VertexResponseMapper.cs | 8 +- .../Implementations/Vertex/README.md | 2 +- .../IntegrationTests/TaxJar/TaxJarTests.cs | 147 ++++++++++++++++++ 14 files changed, 285 insertions(+), 23 deletions(-) create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarError.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/README.md create mode 100644 tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs diff --git a/library/OrderCloud.Catalyst/Extensions/ExtensionMethods.cs b/library/OrderCloud.Catalyst/Extensions/ExtensionMethods.cs index a47cb44..b1a3736 100644 --- a/library/OrderCloud.Catalyst/Extensions/ExtensionMethods.cs +++ b/library/OrderCloud.Catalyst/Extensions/ExtensionMethods.cs @@ -105,5 +105,36 @@ public static string AndFilter(this object filters, (string Key, object Value) f if (qp.Count == 0) { return null; } return string.Join("&", qp.Select(x => $"{x.Name}={x.Value}")); } + + /// + /// Helper method for dealing with the ShipEstimate model. + /// + /// The selected ShipMethod + public static ShipMethod GetSelectedShipMethod(this ShipEstimate shipEstimate) + { + var selectedMethod = shipEstimate.ShipMethods.FirstOrDefault(m => m.ID == shipEstimate.SelectedShipMethodID); + Require.That(selectedMethod != null, new InvalidOperationException($"SelectedShipMethodID is ${shipEstimate.SelectedShipMethodID} but no matching object was found in ShipMethods List.")); + return selectedMethod; + } + + /// + /// Helper method for dealing with the ShipEstimate model. Maps ShipEstimateItems from lineItem IDs to full LineItems. + /// + /// Full model of all LineItems on the specified ShipEstimate + public static List<(int Quantity, LineItem LineItem)> GetShipEstimateLineItems(this OrderWorksheet order, string shipEstimateID) + { + var shipEstimate = order.ShipEstimateResponse.ShipEstimates.FirstOrDefault(se => se.ID == shipEstimateID); + Require.That(shipEstimate != null, new ArgumentException($"No matching ship estimate found with ID {shipEstimateID}")); + + var lineItems = new List<(int quantity, LineItem lineItem)> { }; + foreach (var shipEstimateItem in shipEstimate.ShipEstimateItems) + { + var lineItem = order.LineItems.FirstOrDefault(li => li.ID == shipEstimateItem.LineItemID); + Require.That(lineItem != null, new InvalidOperationException($"ShipEstimateItem.LineItemID is ${shipEstimateItem.LineItemID} but no matching object was was found in OrderWorksheet.LineItems.")); + lineItems.Add((shipEstimateItem.Quantity, lineItem)); + } + + return lineItems; + } } } diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs index 4e31b8f..d5dfed4 100644 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs @@ -9,7 +9,7 @@ public class IntegrationAuthFailedException : CatalystBaseException { public IntegrationAuthFailedException(OCIntegrationConfig config, string requestUrl) : base( "IntegrationAuthorizationFailed", - $"Authentication to 3rd party service \"{config.ServiceName}\" failed. Check your config credentials.", + $"A request to 3rd party service {config.ServiceName} resulted in an Unauthorized error. Check your config credentials.", new IntegrationAuthFailedError() { ServiceName = config.ServiceName, diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs index 566dd0d..43bf84e 100644 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs @@ -9,7 +9,7 @@ public class IntegrationErrorResponseException : CatalystBaseException { public IntegrationErrorResponseException(OCIntegrationConfig config, string requestUrl, object responseBody) : base( "IntegrationErrorResponse", - $"Request to 3rd party service \"{config.ServiceName}\" resulted in an error. See body for details.", + $"A request to 3rd party service {config.ServiceName} resulted in an error. See ResponseBody for details.", new IntegrationErrorResponseError() { ServiceName = config.ServiceName, diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs index add15f7..7b6e663 100644 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Net; -using System.Text; namespace OrderCloud.Catalyst { @@ -9,7 +8,7 @@ public class IntegrationMissingConfigsException : CatalystBaseException { public IntegrationMissingConfigsException(OCIntegrationConfig config, List missingFields) : base( "IntegrationMissingConfigs", - $"Configuration field(s) for 3rd party service \"{config.ServiceName}\" are null or empty. Check fields on class {config.GetType().Name}.", + $"One or more configuration fields for 3rd party service {config.ServiceName} are null or empty. Requests to the service were not attempted.", new IntegrationMissingConfigs() { ServiceName = config.ServiceName, diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs index e495e6a..425a146 100644 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs @@ -9,7 +9,7 @@ public class IntegrationNoResponseException : CatalystBaseException { public IntegrationNoResponseException(OCIntegrationConfig config, string requestUrl) : base( "IntegrationNoResponse", - $"Request to 3rd party service \"{config.ServiceName}\" returned no response.", + $"A request to 3rd party service {config.ServiceName} returned no response.", new IntegrationNoResponseError() { ServiceName = config.ServiceName, diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/README.md b/library/OrderCloud.Catalyst/Integrations/Implementations/README.md index 9037a61..1fbcb10 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/README.md @@ -1,5 +1,6 @@ # List of Integrations -| Name | Website | Integration Guide | Contributed By | Categories | +| Name | Splash Site | Integration Guide | Contributed By | Categories | | ------------- | ------------- | ------------- | ------------- | ------------- | | Vertex | https://www.vertexinc.com/ | [./Vertex/README.md](./Vertex/README.md) | OrderCloud Team | Tax +| TaxJar | https://www.taxjar.com/ | [./TaxJar/README.md](./TaxJar/README.md) | OrderCloud Team | Tax diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs index 793efe8..a168a86 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs @@ -13,14 +13,14 @@ public static class TaxJarRequestMapper private static string CreateShipTransactionID(string orderID, string shipEstimateID) => $"OrderID:|{orderID}|ShippingEstimateID:|{shipEstimateID}"; /// - /// Returns a list of TaxJarOrders because each order has a single to and from address. They therefor coorespond to OrderCloud LineItems. + /// Returns a list of TaxJarOrders because each order has a single to and from address. They coorespond to OrderCloud LineItems. /// public static List ToOrders(OrderWorksheet order) { var itemLines = order.LineItems.Select(li => ToTaxJarLineOrder(li, order.Order.ID)); var shippingLines = order.ShipEstimateResponse.ShipEstimates.Select(se => { - var firstLineItem = order.LineItems.First(li => li.ID == se.ShipEstimateItems.First().LineItemID); + var firstLineItem = order.GetShipEstimateLineItems(se.ID).First().LineItem; return ToTaxJarShipOrder(se, firstLineItem, order.Order.ID); }); return itemLines.Concat(shippingLines).ToList(); @@ -28,7 +28,7 @@ public static List ToOrders(OrderWorksheet order) private static TaxJarOrder ToTaxJarShipOrder(ShipEstimate shipEstimate, LineItem lineItem, string orderID) { - var selectedShipMethod = shipEstimate.ShipMethods.First(x => x.ID == shipEstimate.SelectedShipMethodID); + var selectedShipMethod = shipEstimate.GetSelectedShipMethod(); return new TaxJarOrder() { transaction_id = CreateShipTransactionID(orderID, shipEstimate.ID), diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarError.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarError.cs new file mode 100644 index 0000000..89b5fda --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarError.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class TaxJarError + { + public string error { get; set; } + public string detail { get; set; } + public int status { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/README.md b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/README.md new file mode 100644 index 0000000..ad7a953 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/README.md @@ -0,0 +1,40 @@ +# Vertex Integration + +## Scope of this integration +This .NET integration calculates sales tax for an Order using the TaxJar API. It can be used during checkout to provide a tax cost to the buyer or after submit to create a vertex transaction for filling. + +Use Cases: +- Sales Tax Estimate +- Finialized Order Forwarding + +## Taxjar Basics +[TaxJar](https://www.taxjar.com/) is reimagining how businesses manage sales tax compliance. Our cloud-based platform automates the entire sales tax life cycle across all of your sales channels — from calculations and nexus tracking to reporting and filing. With innovative technology and award-winning support, we simplify sales tax compliance so you can grow with ease. + +## Sales Tax Estimate +The sales tax cost on an Order is first calculated in checkout after shipping selections are made and before payment. Following that, they are updated whenever the order is changed. + +**TaxJar Side -** Multiple requests are made to Taxjar's [calculate tax endpoint](https://developers.taxjar.com/api/reference/#post-calculate-sales-tax-for-an-order). Multiple because Taxjar's order model is limited to 1 shipping address and an OrderCloud order can contain LineItems shipping to different addresses. A TaxJar order request is made for each OrderCloud LineItem and each ShipEstimate. + +**OrderCloud Side -** This integration should be triggered by the **`OrderCalculate`** Checkout Integration Event. Learn more about [checkout integration events](https://ordercloud.io/knowledge-base/order-checkout-integration); + +## Order Forwarding +A taxable transaction is committed to taxjar asynchronously shortly following order submit. This enables businesses to easily file sales tax returns. OrderCloud guarantees the submitted order details provided will be unchanged since the most recent tax estimate displayed to the user. + +**TaxJar Side -** Multiple requests are made to Taxjar's [create order endpoint](https://developers.taxjar.com/api/reference/#post-create-an-order-transaction). The TaxJar transactionId will look like `OrderID:|{orderID}|LineItemID:|{lineItemID}` or `OrderID:|{orderID}|ShipEstimateID:|{shipEstimateID}`. + +**OrderCloud Side -** This integration should be triggered by the **`PostOrderSubmit`** Checkout Integration Event. Learn more about [checkout integration events](https://ordercloud.io/knowledge-base/order-checkout-integration); + +## Set up steps + +- You should set up a .NET middleware project using the Catalyst library and starter project. [See guide](https://ordercloud.io/knowledge-base/start-dotnet-middleware-from-scratch). +- Using the OrderCloud API Portal, configure an Order Chekout IntegrationEvent object to point to your new middleware. [See guide](https://ordercloud.io/knowledge-base/order-checkout-integration) +- Create a taxjar account online and retrieve all the configuration variables required in [VertexOCIntegrationConfig.cs](./VertexOCIntegrationConfig.cs); + - BaseUrl + - Likely "https://api.sandbox.taxjar.com" or "https://api.taxjar.com") + - APIToken + - Find at https://app.taxjar.com/ under Account -> TaxJar API -> Generate Token +- Within your .NET code project, create an instance of [VertexOCIntegrationCommand.cs](./VertexOCIntegrationCommand.cs). Use the method `CalculateEstimateAsync` within the **`OrderCalculate`** Checkout Integration Event. Use the method `CommitTransactionAsync` within the **`PostOrderSubmit`** Checkout Integration Event. + +## Interfaces + +- It conforms to the [ITaxCalculator](../../Interfaces/ITaxCalculator.cs) interface. diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs index f411d91..625f39e 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs @@ -9,16 +9,18 @@ namespace OrderCloud.Catalyst public class TaxJarClient { protected readonly FlurlClient _flurl; + protected readonly TaxJarOCIntegrationConfig _config; public TaxJarClient(TaxJarOCIntegrationConfig config) { + _config = config; _flurl = new FlurlClient(config.BaseUrl).WithOAuthBearerToken(config.APIToken); } /// /// https://developers.taxjar.com/api/reference/#post-create-an-order-transaction /// - public async Task CreateOrderAsync(TaxJarOrder order) + public async Task CreateOrderAsync(TaxJarOrder order) { var tax = await _flurl.Request("v2", "taxes", "orders").PostJsonAsync(order).ReceiveJson(); return tax; @@ -38,8 +40,39 @@ public async Task ListCategoriesAsync() /// public async Task CalcTaxForOrderAsync(TaxJarOrder order) { - var tax = await _flurl.Request("v2", "taxes").PostJsonAsync(order).ReceiveJson(); - return tax; + var request = _flurl.Request("v2", "taxes"); + return await TryCatchRequestAsync(request, async () => + { + var tax = await request.PostJsonAsync(order).ReceiveJson(); + return tax; + }); + } + + protected async Task TryCatchRequestAsync(IFlurlRequest request, Func> run) + { + try + { + return await run(); + } + catch (FlurlHttpTimeoutException ex) // simulate with this https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error + { + // candidate for retry here? + throw new IntegrationNoResponseException(_config, request.Url); + } + catch (FlurlHttpException ex) + { + if (ex.Call.Response == null || ex.Call.Response.StatusCode > 500) // simulate by putting laptop on airplane mode + { + // candidate for retry here? + throw new IntegrationNoResponseException(_config, request.Url); + } + if (ex.Call.Response.StatusCode == 401) + { + throw new IntegrationAuthFailedException(_config, request.Url); + } + var body = await ex.Call.Response.GetJsonAsync(); + throw new IntegrationErrorResponseException(_config, request.Url, body); + } } } } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs index de74850..69d2dab 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs @@ -17,9 +17,8 @@ public static VertexCalculateTaxRequest ToVertexCalculateTaxRequest(this OrderWo var itemLines = order.LineItems.Select(li => ToVertexLineItem(li)); var shippingLines = order.ShipEstimateResponse.ShipEstimates.Select(se => { - var firstLi = order.LineItems.FirstOrDefault(li => li.ID == se.ShipEstimateItems.First().LineItemID); - Require.That(firstLi != null, new CatalystBaseException("InvalidOrderWorksheet", $"Invalid OrderWorksheet. Based on ShipEstimateItems, expected to find a LineItem with ID {se.ShipEstimateItems.First().LineItemID}", null, HttpStatusCode.BadRequest)); - return ToVertexLineItem(se, firstLi.ShippingAddress); + var firstLi = order.GetShipEstimateLineItems(se.ID).First().LineItem; + return ToVertexShipLineItem(se, firstLi.ShippingAddress); }); return new VertexCalculateTaxRequest() @@ -67,10 +66,9 @@ public static VertexLineItem ToVertexLineItem(LineItem lineItem) }; } - public static VertexLineItem ToVertexLineItem(ShipEstimate shipEstimate, Address shipTo) + public static VertexLineItem ToVertexShipLineItem(ShipEstimate shipEstimate, Address shipTo) { - var selectedMethod = shipEstimate.ShipMethods.FirstOrDefault(m => m.ID == shipEstimate.SelectedShipMethodID); - Require.That(selectedMethod != null, new CatalystBaseException("InvalidOrderWorksheet", $"Invalid OrderWorksheet. Based on SelectedShipMethodID, expected to find a ShipMethod with ID {shipEstimate.SelectedShipMethodID}", null, HttpStatusCode.BadRequest)); + var selectedMethod = shipEstimate.GetSelectedShipMethod(); return new VertexLineItem() { customer = new VertexCustomer() diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs index a514854..907eec5 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs @@ -18,7 +18,7 @@ public static OrderTaxCalculation ToOrderTaxCalculation(this VertexCalculateTaxR { OrderID = response.transactionId, ExternalTransactionID = response.transactionId, - TotalTax = (decimal) response.totalTax, + TotalTax = response.totalTax, LineItems = itemLines.Select(ToItemTaxDetails).ToList(), OrderLevelTaxes = shippingLines.SelectMany(ToShippingTaxDetails).ToList() }; @@ -34,7 +34,7 @@ public static LineItemTaxCalculation ToItemTaxDetails(this VertexResponseLineIte return new LineItemTaxCalculation() { LineItemID = transactionLineModel.lineItemId, - LineItemTotalTax = (decimal) transactionLineModel.totalTax, + LineItemTotalTax = transactionLineModel.totalTax, LineItemLevelTaxes = transactionLineModel.taxes?.Select(detail => detail.ToTaxDetails(null)).ToList() ?? new List() }; } @@ -43,8 +43,8 @@ public static TaxDetails ToTaxDetails(this VertexTax detail, string shipEstimate { return new TaxDetails() { - Tax = (decimal) detail.calculatedTax, - Taxable = (decimal) detail.taxable, + Tax = detail.calculatedTax, + Taxable = detail.taxable, Exempt = 0, // we don't get a property back for exempt TaxDescription = detail.impositionType.value, JurisdictionLevel = detail.jurisdiction.jurisdictionLevel.ToString(), diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md index 4fa9144..61f9573 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md @@ -23,7 +23,7 @@ A taxable transaction is committed to vertex asynchronously shortly following or ## Set up steps - You should set up a .NET middleware project using the Catalyst library and starter project. [See guide](https://ordercloud.io/knowledge-base/start-dotnet-middleware-from-scratch). -- Through the OrderCloud API configure an Order Chekout IntegrationEvent object to point to your new middleware. [See guide](https://ordercloud.io/knowledge-base/order-checkout-integration) +- Using the OrderCloud API Portal, configure an Order Chekout IntegrationEvent object to point to your new middleware. [See guide](https://ordercloud.io/knowledge-base/order-checkout-integration) - Create a vertex account online and retrieve all the configuration variables required in [VertexOCIntegrationConfig.cs](./VertexOCIntegrationConfig.cs); - CompanyName - ClientID diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs new file mode 100644 index 0000000..9cfe78b --- /dev/null +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoFixture; +using Flurl.Http.Testing; +using NUnit.Framework; +using OrderCloud.SDK; + +namespace OrderCloud.Catalyst.Tests +{ + [TestFixture] + public class TaxJarTests + { + private static Fixture _fixture = new Fixture(); + private HttpTest _httpTest; + private static TaxJarOCIntegrationConfig _config = new TaxJarOCIntegrationConfig() + { + APIToken = _fixture.Create(), + BaseUrl = "https://api.fake.com" + }; + private TaxJarOCIntegrationCommand _command = new TaxJarOCIntegrationCommand(_config); + private OrderWorksheetBuilder _worksheetBuilder = new OrderWorksheetBuilder(); + + [SetUp] + public void CreateHttpTest() + { + _httpTest = new HttpTest(); + } + + [TearDown] + public void DisposeHttpTest() + { + _httpTest.Dispose(); + } + + [Test] + public void ShouldThrowErrorIfMissingRequiredConfigs() + { + // Arrange + var config = new TaxJarOCIntegrationConfig(); + // Act + var ex = Assert.Throws(() => new TaxJarOCIntegrationCommand(config)); + // Assert + var data = (IntegrationMissingConfigs)ex.Errors[0].Data; + Assert.AreEqual(data.ServiceName, "TaxJar"); + Assert.AreEqual(data.MissingFieldNames, new List { "BaseUrl", "APIToken" }); + } + + [Test] + public void ShouldThrowErrorIfAuthorizationFails() + { + // Arrange + _httpTest.RespondWith("{\"error\":\"Unauthorized\",\"detail\":\"Not authorized for route 'POST /v2/taxes'\",\"status\":401}", 401); // real taxjar response + + var ex = Assert.ThrowsAsync(async () => + // Act + await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }) + ); + + var data = (IntegrationAuthFailedError)ex.Errors[0].Data; + Assert.AreEqual(data.ServiceName, "TaxJar"); + Assert.AreEqual(data.RequestUrl, $"{_config.BaseUrl}/v2/taxes"); + } + + [Test] + public void ShouldThrowErrorIfNoResponse() + { + // Arrange + _httpTest.SimulateTimeout(); + + var ex = Assert.ThrowsAsync(async () => + // Act + await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }) + ); + + var data = (IntegrationNoResponseError)ex.Errors[0].Data; + Assert.AreEqual(data.ServiceName, "TaxJar"); + } + + [Test] + public void ShouldThrowVertexErrorForBadRequest() + { + // Arrange + _httpTest + // real vertex response + .RespondWith("{\"error\":\"Bad Request\",\"detail\":\"No to zip, required when country is US\",\"status\":400}", 400); + + var ex = Assert.ThrowsAsync(async () => + // Act + await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }) + ); + + var data = (IntegrationErrorResponseError)ex.Errors[0].Data; + Assert.AreEqual(data.ServiceName, "TaxJar"); + Assert.AreEqual(data.RequestUrl, $"{_config.BaseUrl}/v2/taxes"); + Assert.AreEqual(((TaxJarError)data.ResponseBody).detail, "No to zip, required when country is US"); + } + + [Test] + public async Task ShouldCalculateTotalTaxAsSum() + { + // Arrange + var order = _worksheetBuilder.Build(); + var lineItems = order.LineItems; + var shipEstimates = order.ShipEstimateResponse.ShipEstimates; + var response = _fixture.Create(); + _httpTest.RespondWithJson(response); + // Act + var result = await _command.CalculateEstimateAsync(order, new List { }); + // Assert + Assert.AreEqual(((lineItems.Count + shipEstimates.Count) * response.tax.amount_to_collect), result.TotalTax); + } + + [Test] + public async Task EstimateShouldMakeCalculateRequests() + { + // Arrange + var order = _worksheetBuilder.Build(); + var lineItems = order.LineItems; + var shipEstimates = order.ShipEstimateResponse.ShipEstimates; + var response = _fixture.Create(); + _httpTest.RespondWithJson(response); + // Act + var result = await _command.CalculateEstimateAsync(order, new List { }); + // Assert + _httpTest.ShouldHaveCalled($"{_config.BaseUrl}/v2/taxes").Times((lineItems.Count + shipEstimates.Count)); + _httpTest.ShouldNotHaveCalled($"{_config.BaseUrl}/v2/taxes/orders"); + } + + [Test] + public async Task CommitShouldMakeBothRequests() + { + // Arrange + var order = _worksheetBuilder.Build(); + var lineItems = order.LineItems; + var shipEstimates = order.ShipEstimateResponse.ShipEstimates; + var response = _fixture.Create(); + _httpTest.RespondWithJson(response); + // Act + var result = await _command.CommitTransactionAsync(order, new List { }); + // Assert + _httpTest.ShouldHaveCalled($"{_config.BaseUrl}/v2/taxes").Times((lineItems.Count + shipEstimates.Count)); + _httpTest.ShouldHaveCalled($"{_config.BaseUrl}/v2/taxes/orders").Times((lineItems.Count + shipEstimates.Count)); + } + } +} From 080dd6d155751bc4fb75b3cd97bb6ebcb9a99054 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Tue, 8 Feb 2022 16:38:49 -0600 Subject: [PATCH 32/40] refactored Interface to not rely on OrderWorksheet. Instead, there's an intermediate model. unit tests are broken now. --- .../Integrations/CONTRIBUTING.md | 10 +- .../Implementations/Avalara/AvalaraClient.cs | 73 +++++++ .../Implementations/Avalara/AvalaraCommand.cs | 24 +++ .../Implementations/Avalara/AvalaraConfig.cs | 21 ++ .../Avalara/Models/AvalaraAddressesModel.cs | 29 +++ .../Models/AvalaraCreateTransactionModel.cs | 98 +++++++++ .../Avalara/Models/AvalaraLineItemModel.cs | 29 +++ .../Avalara/Models/AvalaraList.cs | 12 ++ .../Avalara/Models/AvalaraTaxCode.cs | 25 +++ .../Models/AvalaraTransactionAddressModel.cs | 32 +++ .../AvalaraTransactionLineDetailModel.cs | 87 ++++++++ .../Models/AvalaraTransactionLineModel.cs | 64 ++++++ .../Avalara/Models/AvalaraTransactionModel.cs | 188 ++++++++++++++++++ .../Integrations/Implementations/README.md | 5 +- .../TaxJar/Mapper/TaxJarRequestMapper.cs | 71 +++---- .../Implementations/TaxJar/TaxJarClient.cs | 4 +- ...IntegrationCommand.cs => TaxJarCommand.cs} | 18 +- ...OCIntegrationConfig.cs => TaxJarConfig.cs} | 2 +- .../Vertex/Mappers/VertexRequestMapper.cs | 44 ++-- .../Implementations/Vertex/VertexClient.cs | 6 +- ...IntegrationCommand.cs => VertexCommand.cs} | 18 +- ...OCIntegrationConfig.cs => VertexConfig.cs} | 2 +- .../Integrations/Interfaces/ITaxCalculator.cs | 45 ++++- .../IntegrationTests/OrderWorksheetBuilder.cs | 32 --- .../IntegrationTests/TaxJar/TaxJarTests.cs | 37 ++-- .../IntegrationTests/Vertex/VertexTests.cs | 18 +- 26 files changed, 838 insertions(+), 156 deletions(-) create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraConfig.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraAddressesModel.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraCreateTransactionModel.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraLineItemModel.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraList.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTaxCode.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionAddressModel.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineDetailModel.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineModel.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionModel.cs rename library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/{TaxJarOCIntegrationCommand.cs => TaxJarCommand.cs} (70%) rename library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/{TaxJarOCIntegrationConfig.cs => TaxJarConfig.cs} (80%) rename library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/{VertexOCIntegrationCommand.cs => VertexCommand.cs} (55%) rename library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/{VertexOCIntegrationConfig.cs => VertexConfig.cs} (88%) delete mode 100644 tests/OrderCloud.Catalyst.Tests/IntegrationTests/OrderWorksheetBuilder.cs diff --git a/library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md b/library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md index 56af291..98daabc 100644 --- a/library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md +++ b/library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md @@ -11,7 +11,7 @@ Creating an integration in this project means it will be published as part of a All integrations should include two classes designed to be exposed and consumed by solutions - an `OCIntegrationConfig` and an `OCIntegrationCommand`. The config is a POCO which contains properties for all the environment variables and secrets needed to authenticate to the service. The command exposes the functionality of your integration through methods. For an example service called "Mississippi" you would create the classes below. ```c# -public class MississippiOCIntegrationConfig : OCIntegrationConfig +public class MississippiConfig : OCIntegrationConfig { public override string ServiceName { get; } = "Mississippi"; @@ -22,11 +22,11 @@ public class MississippiOCIntegrationConfig : OCIntegrationConfig } ``` ```c# -public class MississippiOCIntegrationCommand : OCIntegrationCommand +public class MississippiCommand : OCIntegrationCommand { - protected readonly MississippiOCIntegrationConfig _config; + protected readonly MississippiConfig _config; - public MississippiOCIntegrationCommand(MississippiOCIntegrationConfig config) : base(config) + public MississippiOCIntegrationCommand(MississippiConfig config) : base(config) { _config = config; // used to auth to service } @@ -56,6 +56,8 @@ Feel free open issues recommending changes or additions to the interfaces. - Folders and files - Under [/Integrations/Implementations](./Implementations) create a folder with your service name (e.g. "Mississippi") to contain your files. - At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. + - All files and class names should begin with your service name. + - Many of the existing integrations also have a Client class. For these integrations the Client class is a pure API wrapper, handling HTTP requests and exceptions. This leaves mapping as the responsibility of the Command class. - Errors - Handle error scenarios within your integration by throwing one of the exceptions in [/Integrations/Exceptions](./Exceptions). - Every integration should handle cases like missing configs, invalid authentication, error response, and no response. diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs new file mode 100644 index 0000000..565cefe --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs @@ -0,0 +1,73 @@ +using Flurl.Http; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace OrderCloud.Catalyst +{ + public class AvalaraClient + { + protected readonly FlurlClient _flurl; + protected readonly AvalaraConfig _config; + + public AvalaraClient(AvalaraConfig config) + { + _config = config; + _flurl = new FlurlClient(config.BaseUrl).WithBasicAuth(config.AccountID.ToString(), config.LicenseKey); + } + + /// + /// https://developer.avalara.com/api-reference/avatax/rest/v2/methods/Definitions/ListTaxCodes/ + /// + public async Task> ListTaxCodesAsync(string filterParam) + { + var request = _flurl.Request("api", "v2", "definitions", "taxcodes"); + return await TryCatchRequestAsync(request, async () => + { + var tax = await request.SetQueryParam("$filter", filterParam).GetJsonAsync(); + return tax; + }); + } + + /// + /// https://developer.avalara.com/api-reference/avatax/rest/v2/methods/Transactions/CreateTransaction/ + /// + public async Task CreateTransaction(AvalaraCreateTransactionModel transaction) + { + var request = _flurl.Request("api", "v2", "transactions", "create"); + return await TryCatchRequestAsync(request, async () => + { + var tax = await request.PostJsonAsync(transaction).ReceiveJson(); + return tax; + }); + } + + protected async Task TryCatchRequestAsync(IFlurlRequest request, Func> run) + { + try + { + return await run(); + } + catch (FlurlHttpTimeoutException ex) // simulate with this https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error + { + // candidate for retry here? + throw new IntegrationNoResponseException(_config, request.Url); + } + catch (FlurlHttpException ex) + { + if (ex.Call.Response == null || ex.Call.Response.StatusCode > 500) // simulate by putting laptop on airplane mode + { + // candidate for retry here? + throw new IntegrationNoResponseException(_config, request.Url); + } + if (ex.Call.Response.StatusCode == 401) + { + throw new IntegrationAuthFailedException(_config, request.Url); + } + var body = await ex.Call.Response.GetJsonAsync(); + throw new IntegrationErrorResponseException(_config, request.Url, body); + } + } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs new file mode 100644 index 0000000..316f36d --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace OrderCloud.Catalyst +{ + public class AvalaraCommand : OCIntegrationCommand , ITaxCodesProvider + { + protected readonly AvalaraConfig _config; + protected readonly AvalaraClient _client; + + public AvalaraCommand(AvalaraConfig config) : base(config) + { + _config = config; + _client = new AvalaraClient(config); + } + + public async Task ListTaxCodesAsync(string filterTerm) + { + throw new NotImplementedException(); + } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraConfig.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraConfig.cs new file mode 100644 index 0000000..cf1dc48 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraConfig.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class AvalaraConfig: OCIntegrationConfig + { + public override string ServiceName { get; } = "Avalara"; + [RequiredIntegrationField] + public string BaseUrl { get; set; } + [RequiredIntegrationField] + public int AccountID { get; set; } + [RequiredIntegrationField] + public string LicenseKey { get; set; } + [RequiredIntegrationField] + public int CompanyID { get; set; } + [RequiredIntegrationField] + public string CompanyCode { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraAddressesModel.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraAddressesModel.cs new file mode 100644 index 0000000..7d6c03b --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraAddressesModel.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class AvalaraAddressesModel + { + public AvalaraAddressLocationInfo singleLocation { get; set; } + public AvalaraAddressLocationInfo shipFrom { get; set; } + public AvalaraAddressLocationInfo shipTo { get; set; } + public AvalaraAddressLocationInfo pointOfOrderOrigin { get; set; } + public AvalaraAddressLocationInfo pointOfOrderAcceptance { get; set; } + } + + public class AvalaraAddressLocationInfo + { + public string locationCode { get; set; } + public string line1 { get; set; } + public string line2 { get; set; } + public string line3 { get; set; } + public string city { get; set; } + public string region { get; set; } + public string country { get; set; } + public string postalCode { get; set; } + public decimal? latitude { get; set; } + public decimal? longitude { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraCreateTransactionModel.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraCreateTransactionModel.cs new file mode 100644 index 0000000..79ac308 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraCreateTransactionModel.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; + +namespace OrderCloud.Catalyst +{ + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AvalaraDocumentType + { + Any = -1, + SalesOrder = 0, + SalesInvoice = 1, + PurchaseOrder = 2, + PurchaseInvoice = 3, + ReturnOrder = 4, + ReturnInvoice = 5, + InventoryTransferOrder = 6, + InventoryTransferInvoice = 7, + ReverseChargeOrder = 8, + ReverseChargeInvoice = 9 + } + + public class AvalaraCreateTransactionModel + { + public List parameters { get; set; } + public string description { get; set; } + public bool? isSellerImporterOfRecord { get; set; } + public string businessIdentificationNo { get; set; } + public string posLaneCode { get; set; } + public DateTime? exchangeRateEffectiveDate { get; set; } + public decimal? exchangeRate { get; set; } + public AvalaraServiceMode? serviceMode { get; set; } + public string currencyCode { get; set; } + public AvalaraTaxOverrideModel taxOverride { get; set; } + public string batchCode { get; set; } + public bool? commit { get; set; } + public string reportingLocationCode { get; set; } + public string referenceCode { get; set; } + public AvalaraTaxDebugLevel? debugLevel { get; set; } + public AvalaraAddressLocationInfo addresses { get; set; } + public string exemptionNo { get; set; } + public string purchaseOrderNo { get; set; } + public decimal? discount { get; set; } + public string entityUseCode { get; set; } + public string customerUsageType { get; set; } + public string customerCode { get; set; } + public string salespersonCode { get; set; } + public DateTime date { get; set; } + public string companyCode { get; set; } + public AvalaraDocumentType? type { get; set; } + public List lines { get; set; } + public string code { get; set; } + public string email { get; set; } + } + + public class AvalaraTaxOverrideModel + { + public AvalaraTaxOverrideType? type { get; set; } + public decimal? taxAmount { get; set; } + public DateTime? taxDate { get; set; } + public string reason { get; set; } + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AvalaraTaxOverrideType + { + None = 0, + TaxAmount = 1, + Exemption = 2, + TaxDate = 3, + AccruedTaxAmount = 4, + DeriveTaxable = 5, + OutOfHarbor = 6 + } + + public class AvalaraTransactionParameterModel + { + public string name { get; set; } + public string value { get; set; } + public string unit { get; set; } + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AvalaraServiceMode + { + Automatic = 0, + Local = 1, + Remote = 2 + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AvalaraTaxDebugLevel + { + Normal = 0, + Diagnostic = 1 + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraLineItemModel.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraLineItemModel.cs new file mode 100644 index 0000000..a1c27ab --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraLineItemModel.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class AvalaraLineItemModel + { + public string exemptionCode { get; set; } + public AvalaraTaxOverrideModel taxOverride { get; set; } + public string businessIdentificationNo { get; set; } + public string description { get; set; } + public string ref2 { get; set; } + public string ref1 { get; set; } + public string revenueAccount { get; set; } + public bool? taxIncluded { get; set; } + public bool? discounted { get; set; } + public string hsCode { get; set; } + public string itemCode { get; set; } + public string entityUseCode { get; set; } + public string customerUsageType { get; set; } + public string taxCode { get; set; } + public AvalaraAddressesModel addresses { get; set; } + public decimal amount { get; set; } + public decimal? quantity { get; set; } + public string number { get; set; } + public List parameters { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraList.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraList.cs new file mode 100644 index 0000000..daa3dde --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraList.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class AvalaraList + { + public int @recordsetCount { get; set; } + public List value { get; set; } = new List { }; + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTaxCode.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTaxCode.cs new file mode 100644 index 0000000..b7c8d92 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTaxCode.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public class AvalaraTaxCode + { + public bool? isPhysical { get; set; } + public int? createdUserId { get; set; } + public DateTime? createdDate { get; set; } + public bool? isSSTCertified { get; set; } + public bool? isActive { get; set; } + public string entityUseCode { get; set; } + public long? goodsServiceCode { get; set; } + public int? modifiedUserId { get; set; } + public string parentTaxCode { get; set; } + public string description { get; set; } + public string taxCodeTypeId { get; set; } + public string taxCode { get; set; } + public int? companyId { get; set; } + public int? id { get; set; } + public DateTime? modifiedDate { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionAddressModel.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionAddressModel.cs new file mode 100644 index 0000000..eaeeccf --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionAddressModel.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; + +namespace OrderCloud.Catalyst +{ + public class AvalaraTransactionAddressModel + { + public long? id { get; set; } + public long? transactionId { get; set; } + public AvalaraBoundaryLevel? boundaryLevel { get; set; } + public string line1 { get; set; } + public string line2 { get; set; } + public string line3 { get; set; } + public string city { get; set; } + public string region { get; set; } + public string postalCode { get; set; } + public string country { get; set; } + public int? taxRegionId { get; set; } + public string latitude { get; set; } + public string longitude { get; set; } + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AvalaraBoundaryLevel + { + Address = 0, + Zip9 = 1, + Zip5 = 2 + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineDetailModel.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineDetailModel.cs new file mode 100644 index 0000000..d01ade7 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineDetailModel.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; + +namespace OrderCloud.Catalyst +{ + public class AvalaraTransactionLineDetailModel + { + public AvalaraTaxRuleTypeId? nonTaxableType { get; set; } + public int? rateSourceId { get; set; } + public string serCode { get; set; } + public AvalaraSourcing? sourcing { get; set; } + public decimal? tax { get; set; } + public decimal? taxableAmount { get; set; } + public string taxType { get; set; } + public string taxSubTypeId { get; set; } + public string taxTypeGroupId { get; set; } + public string taxName { get; set; } + public int? taxAuthorityTypeId { get; set; } + public int? taxRegionId { get; set; } + public decimal? taxCalculated { get; set; } + public decimal? taxOverride { get; set; } + public AvalaraRateType? rateType { get; set; } + public string rateTypeCode { get; set; } + public decimal? taxableUnits { get; set; } + public decimal? nonTaxableUnits { get; set; } + public decimal? exemptUnits { get; set; } + public string unitOfBasis { get; set; } + public int? rateRuleId { get; set; } + public decimal? rate { get; set; } + public bool? isFee { get; set; } + public int? nonTaxableRuleId { get; set; } + public long? id { get; set; } + public long? transactionLineId { get; set; } + public long? transactionId { get; set; } + public long? addressId { get; set; } + public string country { get; set; } + public string region { get; set; } + public string countyFIPS { get; set; } + public string stateFIPS { get; set; } + public decimal? exemptAmount { get; set; } + public int? exemptReasonId { get; set; } + public bool? inState { get; set; } + public string jurisCode { get; set; } + public string jurisName { get; set; } + public int? jurisdictionId { get; set; } + public string signatureCode { get; set; } + public string stateAssignedNo { get; set; } + public AvalaraJurisTypeId? jurisType { get; set; } + public AvalaraJurisdictionType? jurisdictionType { get; set; } + public decimal? nonTaxableAmount { get; set; } + public bool? isNonPassThru { get; set; } + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum TaxRuleTypeId + { + RateRule = 0, + RateOverrideRule = 1, + BaseRule = 2, + ExemptEntityRule = 3, + ProductTaxabilityRule = 4, + NexusRule = 5 + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AvalaraJurisTypeId + { + STA = 1, + CTY = 2, + CIT = 3, + STJ = 4, + CNT = 5 + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AvalaraTaxRuleTypeId + { + RateRule = 0, + RateOverrideRule = 1, + BaseRule = 2, + ExemptEntityRule = 3, + ProductTaxabilityRule = 4, + NexusRule = 5 + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineModel.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineModel.cs new file mode 100644 index 0000000..71b1a92 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineModel.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; + +namespace OrderCloud.Catalyst +{ + public class AvalaraTransactionLineModel + { + public string ref2 { get; set; } + public AvalaraSourcing? sourcing { get; set; } + public decimal? tax { get; set; } + public decimal? taxableAmount { get; set; } + public decimal? taxCalculated { get; set; } + public string taxCode { get; set; } + public int? taxCodeId { get; set; } + public DateTime? taxDate { get; set; } + public string taxEngine { get; set; } + public AvalaraTaxOverrideType? taxOverrideType { get; set; } + public string businessIdentificationNo { get; set; } + public decimal? taxOverrideAmount { get; set; } + public string taxOverrideReason { get; set; } + public bool? taxIncluded { get; set; } + public List details { get; set; } + public List nonPassthroughDetails { get; set; } + public List lineLocationTypes { get; set; } + public List parameters { get; set; } + public string hsCode { get; set; } + public decimal? costInsuranceFreight { get; set; } + public string revAccount { get; set; } + public DateTime? reportingDate { get; set; } + public int? vatNumberTypeId { get; set; } + public string ref1 { get; set; } + public long? id { get; set; } + public long? transactionId { get; set; } + public string lineNumber { get; set; } + public int? boundaryOverrideId { get; set; } + public string customerUsageType { get; set; } + public string entityUseCode { get; set; } + public string description { get; set; } + public long? destinationAddressId { get; set; } + public long? originAddressId { get; set; } + public string vatCode { get; set; } + public decimal? discountAmount { get; set; } + public decimal? exemptAmount { get; set; } + public int? exemptCertId { get; set; } + public string certificateId { get; set; } + public string exemptNo { get; set; } + public bool? isItemTaxable { get; set; } + public bool? isSSTP { get; set; } + public string itemCode { get; set; } + public decimal? lineAmount { get; set; } + public decimal? quantity { get; set; } + public int? discountTypeId { get; set; } + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AvalaraSourcing + { + Mixed = 42, + Destination = 68, + Origin = 79 + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionModel.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionModel.cs new file mode 100644 index 0000000..16d16b4 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionModel.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; + +namespace OrderCloud.Catalyst +{ + public class AvalaraTransactionModel + { + public decimal? totalTax { get; set; } + public AvalaraAdjustmentReason? adjustmentReason { get; set; } + public string adjustmentDescription { get; set; } + public bool? locked { get; set; } + public string region { get; set; } + public string country { get; set; } + public int? version { get; set; } + public string softwareVersion { get; set; } + public long? originAddressId { get; set; } + public long? destinationAddressId { get; set; } + public DateTime? exchangeRateEffectiveDate { get; set; } + public decimal? exchangeRate { get; set; } + public bool? isSellerImporterOfRecord { get; set; } + public string description { get; set; } + public string email { get; set; } + public string businessIdentificationNo { get; set; } + public DateTime? modifiedDate { get; set; } + public int? modifiedUserId { get; set; } + public DateTime? taxDate { get; set; } + public List lines { get; set; } + public List addresses { get; set; } + public List locationTypes { get; set; } + public List summary { get; set; } + public List taxDetailsByTaxType { get; set; } + public List parameters { get; set; } + public decimal? totalTaxCalculated { get; set; } + public decimal? totalTaxable { get; set; } + public List invoiceMessages { get; set; } + public decimal? totalDiscount { get; set; } + public long? id { get; set; } + public string code { get; set; } + public int? companyId { get; set; } + public DateTime? date { get; set; } + public DateTime? paymentDate { get; set; } + public AvalaraDocumentStatus? status { get; set; } + public AvalaraDocumentType? type { get; set; } + public string batchCode { get; set; } + public string currencyCode { get; set; } + public string customerUsageType { get; set; } + public string entityUseCode { get; set; } + public string customerVendorCode { get; set; } + public string customerCode { get; set; } + public string exemptNo { get; set; } + public bool? reconciled { get; set; } + public string locationCode { get; set; } + public string reportingLocationCode { get; set; } + public string purchaseOrderNo { get; set; } + public string referenceCode { get; set; } + public string salespersonCode { get; set; } + public AvalaraTaxOverrideType? taxOverrideType { get; set; } + public decimal? taxOverrideAmount { get; set; } + public string taxOverrideReason { get; set; } + public decimal? totalAmount { get; set; } + public decimal? totalExempt { get; set; } + public List messages { get; set; } + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AvalaraAdjustmentReason + { + NotAdjusted = 0, + SourcingIssue = 1, + ReconciledWithGeneralLedger = 2, + ExemptCertApplied = 3, + PriceAdjusted = 4, + ProductReturned = 5, + ProductExchanged = 6, + BadDebt = 7, + Other = 8, + Offline = 9 + } + + public class AvalaraTaxMessage + { + public string summary { get; set; } + public string details { get; set; } + public string refersTo { get; set; } + public string severity { get; set; } + public string source { get; set; } + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AvalaraDocumentStatus + { + Any = -1, + Temporary = 0, + Saved = 1, + Posted = 2, + Committed = 3, + Cancelled = 4, + Adjusted = 5, + Queued = 6, + PendingApproval = 7 + } + + public class AvalaraInvoiceMessageModel + { + public string content { get; set; } + public List lineNumbers { get; set; } + } + + public class AvalaraTaxDetailsByTaxType + { + public string taxType { get; set; } + public decimal? totalTaxable { get; set; } + public decimal? totalExempt { get; set; } + public decimal? totalNonTaxable { get; set; } + public decimal? totalTax { get; set; } + public List taxSubTypeDetails { get; set; } + } + + public class AvalaraTaxDetailsByTaxSubType + { + public string taxSubType { get; set; } + public decimal? totalTaxable { get; set; } + public decimal? totalExempt { get; set; } + public decimal? totalNonTaxable { get; set; } + public decimal? totalTax { get; set; } + } + + public class AvalaraTransactionSummary + { + public string taxSubType { get; set; } + public decimal? taxCalculated { get; set; } + public decimal? tax { get; set; } + public decimal? rate { get; set; } + public decimal? taxable { get; set; } + public string rateTypeCode { get; set; } + public AvalaraRateType? rateType { get; set; } + public string taxGroup { get; set; } + public string taxName { get; set; } + public decimal? exemption { get; set; } + public string taxType { get; set; } + public string stateAssignedNo { get; set; } + public int? taxAuthorityType { get; set; } + public string jurisName { get; set; } + public string jurisCode { get; set; } + public AvalaraJurisdictionType? jurisType { get; set; } + public string region { get; set; } + public string country { get; set; } + public decimal? nonTaxable { get; set; } + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AvalaraRateType + { + ReducedA = 65, + ReducedB = 66, + Food = 70, + General = 71, + IncreasedStandard = 73, + LinenRental = 76, + Medical = 77, + Parking = 80, + SuperReduced = 81, + ReducedR = 82, + Standard = 83, + Services = 88, + Zero = 90 + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum AvalaraJurisdictionType + { + Country = 0, + State = 1, + County = 2, + City = 3, + Special = 4 + } + + public class AvalaraTransactionLocationTypeModel + { + public long? documentLocationTypeId { get; set; } + public long? documentId { get; set; } + public long? documentAddressId { get; set; } + public string locationTypeCode { get; set; } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/README.md b/library/OrderCloud.Catalyst/Integrations/Implementations/README.md index 1fbcb10..ee9b9d3 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/README.md +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/README.md @@ -2,5 +2,6 @@ | Name | Splash Site | Integration Guide | Contributed By | Categories | | ------------- | ------------- | ------------- | ------------- | ------------- | -| Vertex | https://www.vertexinc.com/ | [./Vertex/README.md](./Vertex/README.md) | OrderCloud Team | Tax -| TaxJar | https://www.taxjar.com/ | [./TaxJar/README.md](./TaxJar/README.md) | OrderCloud Team | Tax +| **Vertex** | https://www.vertexinc.com/ | [README.md](./Vertex/README.md) | OrderCloud Team | Tax +| **Avalara** | https://www.avalara.com/us/en/index.html | [README.md](./Avalara/README.md) | OrderCloud Team | Tax +| **TaxJar** | https://www.taxjar.com/ | [README.md](./TaxJar/README.md) | OrderCloud Team | Tax diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs index a168a86..6eefd37 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs @@ -15,77 +15,70 @@ public static class TaxJarRequestMapper /// /// Returns a list of TaxJarOrders because each order has a single to and from address. They coorespond to OrderCloud LineItems. /// - public static List ToOrders(OrderWorksheet order) + public static List ToOrders(OrderSummaryForTax order) { - var itemLines = order.LineItems.Select(li => ToTaxJarLineOrder(li, order.Order.ID)); - var shippingLines = order.ShipEstimateResponse.ShipEstimates.Select(se => - { - var firstLineItem = order.GetShipEstimateLineItems(se.ID).First().LineItem; - return ToTaxJarShipOrder(se, firstLineItem, order.Order.ID); - }); + var itemLines = order.LineItems.Select(li => ToTaxJarLineOrder(li, order.OrderID)); + var shippingLines = order.ShipEstimates.Select(se => ToTaxJarShipOrder(se, order.OrderID)); return itemLines.Concat(shippingLines).ToList(); } - private static TaxJarOrder ToTaxJarShipOrder(ShipEstimate shipEstimate, LineItem lineItem, string orderID) + private static TaxJarOrder ToTaxJarShipOrder(ShipEstimateSummaryForTax shipEstimate, string orderID) { - var selectedShipMethod = shipEstimate.GetSelectedShipMethod(); return new TaxJarOrder() { - transaction_id = CreateShipTransactionID(orderID, shipEstimate.ID), + transaction_id = CreateShipTransactionID(orderID, shipEstimate.ShipEstimateID), shipping = 0, // will create separate lines for shipping - from_city = lineItem.ShipFromAddress.City, - from_zip = lineItem.ShipFromAddress.Zip, - from_state = lineItem.ShipFromAddress.State, - from_country = lineItem.ShipFromAddress.Country, - from_street = lineItem.ShipFromAddress.Street1, + from_city = shipEstimate.ShipFrom.Zip, + from_state = shipEstimate.ShipFrom.State, + from_country = shipEstimate.ShipFrom.Country, + from_street = shipEstimate.ShipFrom.Street1, - to_city = lineItem.ShippingAddress.City, - to_zip = lineItem.ShippingAddress.Zip, - to_state = lineItem.ShippingAddress.State, - to_country = lineItem.ShippingAddress.Country, - to_street = lineItem.ShippingAddress.Street1, + to_city = shipEstimate.ShipTo.City, + to_zip = shipEstimate.ShipTo.Zip, + to_state = shipEstimate.ShipTo.State, + to_country = shipEstimate.ShipTo.Country, + to_street = shipEstimate.ShipTo.Street1, line_items = new List { new TaxJarLineItem() { - id = shipEstimate.ID, + id = shipEstimate.ShipEstimateID, quantity = 1, - unit_price = selectedShipMethod.Cost, - description = selectedShipMethod.Name, + unit_price = shipEstimate.Cost, + description = shipEstimate.Description, product_identifier = ShippingLineCode, } } }; } - private static TaxJarOrder ToTaxJarLineOrder(LineItem lineItem, string orderID) + private static TaxJarOrder ToTaxJarLineOrder(LineItemSummaryForTax lineItem, string orderID) { return new TaxJarOrder() { - transaction_id = CreateLineTransactionID(orderID, lineItem.ID), + transaction_id = CreateLineTransactionID(orderID, lineItem.LineItemID), shipping = 0, // will create separate lines for shipping - from_city = lineItem.ShipFromAddress.City, - from_zip = lineItem.ShipFromAddress.Zip, - from_state = lineItem.ShipFromAddress.State, - from_country = lineItem.ShipFromAddress.Country, - from_street = lineItem.ShipFromAddress.Street1, + from_city = lineItem.ShipFrom.Zip, + from_state = lineItem.ShipFrom.State, + from_country = lineItem.ShipFrom.Country, + from_street = lineItem.ShipFrom.Street1, - to_city = lineItem.ShippingAddress.City, - to_zip = lineItem.ShippingAddress.Zip, - to_state = lineItem.ShippingAddress.State, - to_country = lineItem.ShippingAddress.Country, - to_street = lineItem.ShippingAddress.Street1, + to_city = lineItem.ShipTo.City, + to_zip = lineItem.ShipTo.Zip, + to_state = lineItem.ShipTo.State, + to_country = lineItem.ShipTo.Country, + to_street = lineItem.ShipTo.Street1, line_items = new List { new TaxJarLineItem() { - id = lineItem.ID, + id = lineItem.LineItemID, quantity = lineItem.Quantity, - unit_price = lineItem.UnitPrice ?? 0, - description = lineItem.Product.Name, - product_identifier = lineItem.Product.ID, + unit_price = lineItem.UnitPrice, + description = lineItem.ProductName, + product_identifier = lineItem.ProductID, } } }; diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs index 625f39e..4818b4e 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs @@ -9,9 +9,9 @@ namespace OrderCloud.Catalyst public class TaxJarClient { protected readonly FlurlClient _flurl; - protected readonly TaxJarOCIntegrationConfig _config; + protected readonly TaxJarConfig _config; - public TaxJarClient(TaxJarOCIntegrationConfig config) + public TaxJarClient(TaxJarConfig config) { _config = config; _flurl = new FlurlClient(config.BaseUrl).WithOAuthBearerToken(config.APIToken); diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationCommand.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarCommand.cs similarity index 70% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationCommand.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarCommand.cs index 5020e38..7bd5a4f 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationCommand.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarCommand.cs @@ -7,12 +7,12 @@ namespace OrderCloud.Catalyst { - public class TaxJarOCIntegrationCommand : OCIntegrationCommand, ITaxCalculator, ITaxCodesProvider + public class TaxJarCommand : OCIntegrationCommand, ITaxCalculator, ITaxCodesProvider { - protected readonly TaxJarOCIntegrationConfig _config; + protected readonly TaxJarConfig _config; protected readonly TaxJarClient _client; - public TaxJarOCIntegrationCommand(TaxJarOCIntegrationConfig config) : base(config) + public TaxJarCommand(TaxJarConfig config) : base(config) { _config = config; _client = new TaxJarClient(config); @@ -24,16 +24,16 @@ public async Task ListTaxCodesAsync(string filterTerm return TaxJarCategoryMapper.ToTaxCategorization(categories, filterTerm); } - public async Task CalculateEstimateAsync(OrderWorksheet orderWorksheet, List promotions) + public async Task CalculateEstimateAsync(OrderSummaryForTax orderSummary) { - var orders = await CalculateTax(orderWorksheet); + var orders = await CalculateTax(orderSummary); var orderTaxCalculation = TaxJarResponseMapper.ToOrderTaxCalculation(orders); return orderTaxCalculation; } - public async Task CommitTransactionAsync(OrderWorksheet orderWorksheet, List promotions) + public async Task CommitTransactionAsync(OrderSummaryForTax orderSummary) { - var orders = await CalculateTax(orderWorksheet); + var orders = await CalculateTax(orderSummary); foreach (var response in orders) { response.request.transaction_date = DateTime.UtcNow.ToString("yyyy/MM/dd"); @@ -46,9 +46,9 @@ public async Task CommitTransactionAsync(OrderWorksheet ord return orderTaxCalculation; } - protected async Task> CalculateTax(OrderWorksheet orderWorksheet) + protected async Task> CalculateTax(OrderSummaryForTax orderSummary) { - var orders = TaxJarRequestMapper.ToOrders(orderWorksheet); + var orders = TaxJarRequestMapper.ToOrders(orderSummary); return await Throttler.RunAsync(orders, 100, 8, async order => { var tax = await _client.CalcTaxForOrderAsync(order); diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarConfig.cs similarity index 80% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationConfig.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarConfig.cs index b604f6c..63a947d 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarOCIntegrationConfig.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarConfig.cs @@ -2,7 +2,7 @@ namespace OrderCloud.Catalyst { - public class TaxJarOCIntegrationConfig : OCIntegrationConfig + public class TaxJarConfig : OCIntegrationConfig { public override string ServiceName { get; } = "TaxJar"; [RequiredIntegrationField] diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs index 69d2dab..147b8ce 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs @@ -12,21 +12,17 @@ public static class VertexRequestMapper { public const string ShippingLineCode = "shipping_code"; - public static VertexCalculateTaxRequest ToVertexCalculateTaxRequest(this OrderWorksheet order, List promosOnOrder, string companyCode, VertexSaleMessageType type) + public static VertexCalculateTaxRequest ToVertexCalculateTaxRequest(OrderSummaryForTax order, string companyCode, VertexSaleMessageType type) { - var itemLines = order.LineItems.Select(li => ToVertexLineItem(li)); - var shippingLines = order.ShipEstimateResponse.ShipEstimates.Select(se => - { - var firstLi = order.GetShipEstimateLineItems(se.ID).First().LineItem; - return ToVertexShipLineItem(se, firstLi.ShippingAddress); - }); + var itemLines = order.LineItems.Select(ToVertexLineItem); + var shippingLines = order.ShipEstimates.Select(ToVertexShipLineItem); return new VertexCalculateTaxRequest() { postingDate = DateTime.UtcNow.ToString("yyyy-MM-dd"), saleMessageType = type.ToString(), transactionType = VertexTransactionType.SALE.ToString(), - transactionId = order.Order.ID, + transactionId = order.OrderID, seller = new VertexSeller() { company = companyCode @@ -35,61 +31,63 @@ public static VertexCalculateTaxRequest ToVertexCalculateTaxRequest(this OrderWo { customerCode = new VertexCustomerCode() { - classCode = order.Order.FromUserID, - value = order.Order.FromUser.Email + value = order.CustomerCode }, }, lineItems = itemLines.Concat(shippingLines).ToList() }; } - public static VertexLineItem ToVertexLineItem(LineItem lineItem) + public static VertexLineItem ToVertexLineItem(LineItemSummaryForTax lineItem) { return new VertexLineItem() { customer = new VertexCustomer() { - destination = lineItem.ShippingAddress.ToVertexLocation(), + destination = ToVertexLocation(lineItem.ShipTo), }, product = new VertexProduct() { - productClass = lineItem.Product.ID, - value = lineItem.Product.Name + productClass = lineItem.ProductID, + value = lineItem.ProductName }, quantity = new VertexMeasure() { value = lineItem.Quantity }, - unitPrice = lineItem.UnitPrice ?? 0, - lineItemId = lineItem.ID, + unitPrice = lineItem.UnitPrice, + lineItemId = lineItem.LineItemID, + discount = new VertexDiscount() + { + + }, extendedPrice = lineItem.LineTotal // this takes precedence over quanitity and unit price in determining tax cost }; } - public static VertexLineItem ToVertexShipLineItem(ShipEstimate shipEstimate, Address shipTo) + public static VertexLineItem ToVertexShipLineItem(ShipEstimateSummaryForTax shipEstimate) { - var selectedMethod = shipEstimate.GetSelectedShipMethod(); return new VertexLineItem() { customer = new VertexCustomer() { - destination = shipTo.ToVertexLocation(), + destination = ToVertexLocation(shipEstimate.ShipTo), }, product = new VertexProduct() { productClass = ShippingLineCode, - value = selectedMethod.Name + value = shipEstimate.Description }, quantity = new VertexMeasure() { value = 1 }, - unitPrice = selectedMethod.Cost, - lineItemId = shipEstimate.ID, + unitPrice = shipEstimate.Cost, + lineItemId = shipEstimate.ShipEstimateID, }; } - public static VertexLocation ToVertexLocation(this Address address) + public static VertexLocation ToVertexLocation(Address address) { return new VertexLocation() { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs index 47096e4..d5c46b5 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs @@ -12,10 +12,10 @@ public class VertexClient protected const string ApiUrl = "https://restconnect.vertexsmb.com"; protected const string AuthUrl = "https://auth.vertexsmb.com"; protected DateTimeOffset? CurrentTokenExpires = null; - protected readonly VertexOCIntegrationConfig _config; + protected readonly VertexConfig _config; protected VertexTokenResponse _token; - public VertexClient(VertexOCIntegrationConfig config) + public VertexClient(VertexConfig config) { _config = config; } @@ -49,7 +49,7 @@ public async Task CalculateTax(VertexCalculateTaxReq } - protected async Task GetToken(VertexOCIntegrationConfig config) + protected async Task GetToken(VertexConfig config) { if (_token?.access_token != null && CurrentTokenExpires != null && CurrentTokenExpires > DateTimeOffset.Now) { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationCommand.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexCommand.cs similarity index 55% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationCommand.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexCommand.cs index f3899aa..04f820f 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationCommand.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexCommand.cs @@ -4,12 +4,12 @@ namespace OrderCloud.Catalyst { - public class VertexOCIntegrationCommand : OCIntegrationCommand, ITaxCalculator + public class VertexCommand : OCIntegrationCommand, ITaxCalculator { protected readonly VertexClient _client; - protected readonly VertexOCIntegrationConfig _config; + protected readonly VertexConfig _config; - public VertexOCIntegrationCommand(VertexOCIntegrationConfig config) : base(config) + public VertexCommand(VertexConfig config) : base(config) { _config = config; _client = new VertexClient(config); @@ -18,18 +18,18 @@ public VertexOCIntegrationCommand(VertexOCIntegrationConfig config) : base(confi /// /// Calculates tax for an order without creating any records. Use this to display tax amount to user prior to order submit. /// - public async Task CalculateEstimateAsync(OrderWorksheet orderWorksheet, List promotions) => - await CalculateTaxAsync(orderWorksheet, promotions, VertexSaleMessageType.QUOTATION); + public async Task CalculateEstimateAsync(OrderSummaryForTax orderSummary) => + await CalculateTaxAsync(orderSummary, VertexSaleMessageType.QUOTATION); /// /// Creates a tax transaction record in the calculating system. Use this once on purchase, payment capture, or fulfillment. /// - public async Task CommitTransactionAsync(OrderWorksheet orderWorksheet, List promotions) => - await CalculateTaxAsync(orderWorksheet, promotions, VertexSaleMessageType.INVOICE); + public async Task CommitTransactionAsync(OrderSummaryForTax orderSummary) => + await CalculateTaxAsync(orderSummary, VertexSaleMessageType.INVOICE); - protected async Task CalculateTaxAsync(OrderWorksheet orderWorksheet, List promotions, VertexSaleMessageType type) + protected async Task CalculateTaxAsync(OrderSummaryForTax orderSummary, VertexSaleMessageType type) { - var request = orderWorksheet.ToVertexCalculateTaxRequest(promotions, _config.CompanyName, type); + var request = VertexRequestMapper.ToVertexCalculateTaxRequest(orderSummary, _config.CompanyName, type); var response = await _client.CalculateTax(request); var orderTaxCalculation = response.ToOrderTaxCalculation(); return orderTaxCalculation; diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexConfig.cs similarity index 88% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationConfig.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexConfig.cs index d7bbfb9..6ff659b 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexOCIntegrationConfig.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexConfig.cs @@ -2,7 +2,7 @@ namespace OrderCloud.Catalyst { - public class VertexOCIntegrationConfig : OCIntegrationConfig + public class VertexConfig : OCIntegrationConfig { public override string ServiceName { get; } = "Vertex"; [RequiredIntegrationField] diff --git a/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs b/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs index e9f16ef..26398b8 100644 --- a/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs +++ b/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs @@ -14,11 +14,11 @@ public interface ITaxCalculator /// /// Calculates tax for an order without creating any records. Use this to display tax amount to user prior to order submit. /// - Task CalculateEstimateAsync(OrderWorksheet orderWorksheet, List promotions); + Task CalculateEstimateAsync(OrderSummaryForTax orderSummary); /// /// Creates a tax transaction record in the calculating system. Use this once per order - on order submit, payment capture, or fulfillment. /// - Task CommitTransactionAsync(OrderWorksheet orderWorksheet, List promotions); + Task CommitTransactionAsync(OrderSummaryForTax orderSummary); } /// @@ -101,4 +101,45 @@ public class TaxDetails /// public string ShipEstimateID { get; set; } } + + public class OrderSummaryForTax + { + public string OrderID { get; set; } + public string CustomerCode { get; set; } + /// + /// Sum of the LineItem-level PromotionDiscounts plus the discount from all Order-level promotions. + /// + public decimal PromotionDiscount { get; set; } + public List LineItems { get; set; } + public List ShipEstimates { get; set; } + } + + public class LineItemSummaryForTax + { + public string LineItemID { get; set; } + public string ProductID { get; set; } + public string ProductName { get; set; } + public int Quantity { get; set; } + public decimal UnitPrice { get; set; } + public decimal PromotionDiscount { get; set; } + /// + /// (UnitPrice * Quantity) - PromotionDiscount + /// + public decimal LineTotal { get; set; } + public string TaxCode { get; set; } + public Address ShipTo { get; set; } + public Address ShipFrom { get; set; } + } + + public class ShipEstimateSummaryForTax + { + public string ShipEstimateID { get; set; } + /// + /// E.G. "Fedex 2-day priority" + /// + public string Description { get; set; } + public decimal Cost { get; set; } + public Address ShipTo { get; set; } + public Address ShipFrom { get; set; } + } } diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/OrderWorksheetBuilder.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/OrderWorksheetBuilder.cs deleted file mode 100644 index 98b38da..0000000 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/OrderWorksheetBuilder.cs +++ /dev/null @@ -1,32 +0,0 @@ -using AutoFixture; -using OrderCloud.SDK; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace OrderCloud.Catalyst.Tests -{ - public class OrderWorksheetBuilder - { - private static Fixture _fixture = new Fixture(); - - public OrderWorksheet Build() - { - var workSheet = _fixture.Create(); - - // The object fixture auto-generates is invalid because some ID references don't exist. - var lineItemID = workSheet.LineItems.First().ID; - foreach (var shipEstimate in workSheet.ShipEstimateResponse.ShipEstimates) - { - shipEstimate.SelectedShipMethodID = shipEstimate.ShipMethods.First().ID; - foreach (var item in shipEstimate.ShipEstimateItems) - { - item.LineItemID = lineItemID; - } - } - - return workSheet; - } - } -} diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs index 9cfe78b..471903b 100644 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs @@ -14,13 +14,13 @@ public class TaxJarTests { private static Fixture _fixture = new Fixture(); private HttpTest _httpTest; - private static TaxJarOCIntegrationConfig _config = new TaxJarOCIntegrationConfig() + private static TaxJarConfig _config = new TaxJarConfig() { APIToken = _fixture.Create(), BaseUrl = "https://api.fake.com" }; - private TaxJarOCIntegrationCommand _command = new TaxJarOCIntegrationCommand(_config); - private OrderWorksheetBuilder _worksheetBuilder = new OrderWorksheetBuilder(); + private TaxJarCommand _command = new TaxJarCommand(_config); + private OrderSummaryForTax _order = new OrderSummaryForTax(); [SetUp] public void CreateHttpTest() @@ -38,9 +38,9 @@ public void DisposeHttpTest() public void ShouldThrowErrorIfMissingRequiredConfigs() { // Arrange - var config = new TaxJarOCIntegrationConfig(); + var config = new TaxJarConfig(); // Act - var ex = Assert.Throws(() => new TaxJarOCIntegrationCommand(config)); + var ex = Assert.Throws(() => new TaxJarCommand(config)); // Assert var data = (IntegrationMissingConfigs)ex.Errors[0].Data; Assert.AreEqual(data.ServiceName, "TaxJar"); @@ -55,7 +55,7 @@ public void ShouldThrowErrorIfAuthorizationFails() var ex = Assert.ThrowsAsync(async () => // Act - await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }) + await _command.CalculateEstimateAsync(_order) ); var data = (IntegrationAuthFailedError)ex.Errors[0].Data; @@ -71,7 +71,7 @@ public void ShouldThrowErrorIfNoResponse() var ex = Assert.ThrowsAsync(async () => // Act - await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }) + await _command.CalculateEstimateAsync(_order) ); var data = (IntegrationNoResponseError)ex.Errors[0].Data; @@ -88,7 +88,7 @@ public void ShouldThrowVertexErrorForBadRequest() var ex = Assert.ThrowsAsync(async () => // Act - await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }) + await _command.CalculateEstimateAsync(_order) ); var data = (IntegrationErrorResponseError)ex.Errors[0].Data; @@ -101,13 +101,12 @@ public void ShouldThrowVertexErrorForBadRequest() public async Task ShouldCalculateTotalTaxAsSum() { // Arrange - var order = _worksheetBuilder.Build(); - var lineItems = order.LineItems; - var shipEstimates = order.ShipEstimateResponse.ShipEstimates; + var lineItems = _order.LineItems; + var shipEstimates = _order.ShipEstimates; var response = _fixture.Create(); _httpTest.RespondWithJson(response); // Act - var result = await _command.CalculateEstimateAsync(order, new List { }); + var result = await _command.CalculateEstimateAsync(_order); // Assert Assert.AreEqual(((lineItems.Count + shipEstimates.Count) * response.tax.amount_to_collect), result.TotalTax); } @@ -116,13 +115,12 @@ public async Task ShouldCalculateTotalTaxAsSum() public async Task EstimateShouldMakeCalculateRequests() { // Arrange - var order = _worksheetBuilder.Build(); - var lineItems = order.LineItems; - var shipEstimates = order.ShipEstimateResponse.ShipEstimates; + var lineItems = _order.LineItems; + var shipEstimates = _order.ShipEstimates; var response = _fixture.Create(); _httpTest.RespondWithJson(response); // Act - var result = await _command.CalculateEstimateAsync(order, new List { }); + var result = await _command.CalculateEstimateAsync(_order); // Assert _httpTest.ShouldHaveCalled($"{_config.BaseUrl}/v2/taxes").Times((lineItems.Count + shipEstimates.Count)); _httpTest.ShouldNotHaveCalled($"{_config.BaseUrl}/v2/taxes/orders"); @@ -132,13 +130,12 @@ public async Task EstimateShouldMakeCalculateRequests() public async Task CommitShouldMakeBothRequests() { // Arrange - var order = _worksheetBuilder.Build(); - var lineItems = order.LineItems; - var shipEstimates = order.ShipEstimateResponse.ShipEstimates; + var lineItems = _order.LineItems; + var shipEstimates = _order.ShipEstimates; var response = _fixture.Create(); _httpTest.RespondWithJson(response); // Act - var result = await _command.CommitTransactionAsync(order, new List { }); + var result = await _command.CommitTransactionAsync(_order); // Assert _httpTest.ShouldHaveCalled($"{_config.BaseUrl}/v2/taxes").Times((lineItems.Count + shipEstimates.Count)); _httpTest.ShouldHaveCalled($"{_config.BaseUrl}/v2/taxes/orders").Times((lineItems.Count + shipEstimates.Count)); diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs index 4b27957..013d039 100644 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs @@ -14,9 +14,9 @@ public class VertexTests { private static Fixture _fixture = new Fixture(); private HttpTest _httpTest; - private static VertexOCIntegrationConfig _config = _fixture.Create(); - private VertexOCIntegrationCommand _command = new VertexOCIntegrationCommand(_config); - private OrderWorksheetBuilder _worksheetBuilder = new OrderWorksheetBuilder(); + private static VertexConfig _config = _fixture.Create(); + private VertexCommand _command = new VertexCommand(_config); + private OrderSummaryForTax _order = new OrderSummaryForTax(); [SetUp] public void CreateHttpTest() @@ -34,9 +34,9 @@ public void DisposeHttpTest() public void ShouldThrowErrorIfMissingRequiredConfigs() { // Arrange - var config = new VertexOCIntegrationConfig(); + var config = new VertexConfig(); // Act - var ex = Assert.Throws(() => new VertexOCIntegrationCommand(config)); + var ex = Assert.Throws(() => new VertexCommand(config)); // Assert var data = (IntegrationMissingConfigs) ex.Errors[0].Data; Assert.AreEqual(data.ServiceName, "Vertex"); @@ -51,7 +51,7 @@ public void ShouldThrowErrorIfAuthorizationFails() var ex = Assert.ThrowsAsync(async () => // Act - await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }) + await _command.CalculateEstimateAsync(_order) ); var data = (IntegrationAuthFailedError) ex.Errors[0].Data; @@ -67,7 +67,7 @@ public void ShouldThrowErrorIfNoResponse() var ex = Assert.ThrowsAsync(async () => // Act - await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }) + await _command.CalculateEstimateAsync(_order) ); var data = (IntegrationNoResponseError) ex.Errors[0].Data; @@ -86,7 +86,7 @@ public void ShouldThrowVertexErrorForBadRequest() var ex = Assert.ThrowsAsync(async () => // Act - await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }) + await _command.CalculateEstimateAsync(_order) ); var data = (IntegrationErrorResponseError)ex.Errors[0].Data; @@ -102,7 +102,7 @@ public async Task SuccessResponseShouldBeMappedCorrectly() var response = _fixture.Create>(); _httpTest.RespondWithJson(response); // Act - var result = await _command.CalculateEstimateAsync(_worksheetBuilder.Build(), new List { }); + var result = await _command.CalculateEstimateAsync(_order); // Assert Assert.AreEqual(response.data.transactionId, result.OrderID); Assert.AreEqual(response.data.transactionId, result.ExternalTransactionID); From eaae6f59669e8fcb29d4050d39113abf5c87dd08 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Tue, 8 Feb 2022 16:40:20 -0600 Subject: [PATCH 33/40] units tests are back. nice! --- .../IntegrationTests/TaxJar/TaxJarTests.cs | 2 +- .../IntegrationTests/Vertex/VertexTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs index 471903b..1cad876 100644 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs @@ -20,7 +20,7 @@ public class TaxJarTests BaseUrl = "https://api.fake.com" }; private TaxJarCommand _command = new TaxJarCommand(_config); - private OrderSummaryForTax _order = new OrderSummaryForTax(); + private OrderSummaryForTax _order = _fixture.Create(); [SetUp] public void CreateHttpTest() diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs index 013d039..4742245 100644 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs @@ -16,7 +16,7 @@ public class VertexTests private HttpTest _httpTest; private static VertexConfig _config = _fixture.Create(); private VertexCommand _command = new VertexCommand(_config); - private OrderSummaryForTax _order = new OrderSummaryForTax(); + private OrderSummaryForTax _order = _fixture.Create(); [SetUp] public void CreateHttpTest() From b07c27022294bf5fc79ceae29418fb27adc842b9 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Wed, 9 Feb 2022 10:58:07 -0600 Subject: [PATCH 34/40] Avalara looks good! still needs tests and readme --- .../Errors/CatalystBaseException.cs | 8 +- .../Integrations/CONTRIBUTING.md | 2 +- .../IntegrationAuthFailedException.cs | 7 +- .../IntegrationErrorResponseException.cs | 6 +- .../Implementations/Avalara/AvalaraClient.cs | 17 ++-- .../Implementations/Avalara/AvalaraCommand.cs | 28 +++++-- .../Implementations/Avalara/AvalaraConfig.cs | 4 +- .../Avalara/Mappers/AvalaraRequestMapper.cs | 84 +++++++++++++++++++ .../Avalara/Mappers/AvalaraResponseMapper.cs | 53 ++++++++++++ .../Avalara/Mappers/AvalaraTaxCodeMapper.cs | 36 ++++++++ .../{AvalaraList.cs => AvalaraFetchResult.cs} | 2 +- .../Implementations/TaxJar/TaxJarClient.cs | 13 +-- .../Implementations/Vertex/VertexClient.cs | 13 +-- 13 files changed, 234 insertions(+), 39 deletions(-) create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraRequestMapper.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraResponseMapper.cs create mode 100644 library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraTaxCodeMapper.cs rename library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/{AvalaraList.cs => AvalaraFetchResult.cs} (85%) diff --git a/library/OrderCloud.Catalyst/Errors/CatalystBaseException.cs b/library/OrderCloud.Catalyst/Errors/CatalystBaseException.cs index f7a366c..15af3d6 100644 --- a/library/OrderCloud.Catalyst/Errors/CatalystBaseException.cs +++ b/library/OrderCloud.Catalyst/Errors/CatalystBaseException.cs @@ -42,12 +42,12 @@ public CatalystBaseException(ErrorCode errorCode, object data = null) // Keeping these depreacated constructors that take an Int for backwards compatibility. - //public CatalystBaseException(ApiError apiError, int httpStatus) : this(apiError, (HttpStatusCode)httpStatus) { } + public CatalystBaseException(ApiError apiError, int httpStatus) : this(apiError, (HttpStatusCode)httpStatus) { } - //public CatalystBaseException(IList errors, int httpStatus): this(errors, (HttpStatusCode)httpStatus) { } + public CatalystBaseException(IList errors, int httpStatus) : this(errors, (HttpStatusCode)httpStatus) { } - //public CatalystBaseException(string errorCode, string message, object data, int httpStatus) - // : this(errorCode, message, data, (HttpStatusCode)httpStatus) { } + public CatalystBaseException(string errorCode, string message, object data, int httpStatus) + : this(errorCode, message, data, (HttpStatusCode)httpStatus) { } } diff --git a/library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md b/library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md index 98daabc..1b3d7df 100644 --- a/library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md +++ b/library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md @@ -8,7 +8,7 @@ Creating an integration in this project means it will be published as part of a ## Exposed Contracts -All integrations should include two classes designed to be exposed and consumed by solutions - an `OCIntegrationConfig` and an `OCIntegrationCommand`. The config is a POCO which contains properties for all the environment variables and secrets needed to authenticate to the service. The command exposes the functionality of your integration through methods. For an example service called "Mississippi" you would create the classes below. +All integrations should include two classes designed to be exposed and consumed by solutions - an `OCIntegrationConfig` and an `OCIntegrationCommand`. The config is a POCO which contains `string` properties for all the environment variables needed to authenticate to the service. The command exposes the functionality of your integration through methods. For an example service called "Mississippi" you would create the classes below. ```c# public class MississippiConfig : OCIntegrationConfig diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs index d5dfed4..630c159 100644 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs @@ -7,13 +7,14 @@ namespace OrderCloud.Catalyst { public class IntegrationAuthFailedException : CatalystBaseException { - public IntegrationAuthFailedException(OCIntegrationConfig config, string requestUrl) : base( + public IntegrationAuthFailedException(OCIntegrationConfig config, string requestUrl, int responseStatus) : base( "IntegrationAuthorizationFailed", - $"A request to 3rd party service {config.ServiceName} resulted in an Unauthorized error. Check your config credentials.", + $"A request to 3rd party service {config.ServiceName} resulted in an UnAuthorized error. Check your config credentials.", new IntegrationAuthFailedError() { ServiceName = config.ServiceName, RequestUrl = requestUrl, + ResponseStatus = responseStatus }, HttpStatusCode.BadRequest) {} } @@ -22,5 +23,7 @@ public class IntegrationAuthFailedError { public string ServiceName { get; set; } public string RequestUrl { get; set; } + public int ResponseStatus { get; set; } + } } diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs index 43bf84e..e7a911d 100644 --- a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs +++ b/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs @@ -7,14 +7,15 @@ namespace OrderCloud.Catalyst { public class IntegrationErrorResponseException : CatalystBaseException { - public IntegrationErrorResponseException(OCIntegrationConfig config, string requestUrl, object responseBody) : base( + public IntegrationErrorResponseException(OCIntegrationConfig config, string requestUrl, int responseStatus, object responseBody) : base( "IntegrationErrorResponse", $"A request to 3rd party service {config.ServiceName} resulted in an error. See ResponseBody for details.", new IntegrationErrorResponseError() { ServiceName = config.ServiceName, RequestUrl = requestUrl, - ResponseBody = responseBody + ResponseStatus = responseStatus, + ResponseBody = responseBody, } , HttpStatusCode.BadRequest) { } @@ -24,6 +25,7 @@ public class IntegrationErrorResponseError { public string ServiceName { get; set; } public string RequestUrl { get; set; } + public int ResponseStatus { get; set; } public object ResponseBody { get; set; } } } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs index 565cefe..03e6e1c 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs @@ -1,6 +1,7 @@ using Flurl.Http; using System; using System.Collections.Generic; +using System.Net; using System.Text; using System.Threading.Tasks; @@ -14,7 +15,7 @@ public class AvalaraClient public AvalaraClient(AvalaraConfig config) { _config = config; - _flurl = new FlurlClient(config.BaseUrl).WithBasicAuth(config.AccountID.ToString(), config.LicenseKey); + _flurl = new FlurlClient(config.BaseUrl).WithBasicAuth(config.AccountID, config.LicenseKey); } /// @@ -25,8 +26,8 @@ public async Task> ListTaxCodesAsync(string filterParam) var request = _flurl.Request("api", "v2", "definitions", "taxcodes"); return await TryCatchRequestAsync(request, async () => { - var tax = await request.SetQueryParam("$filter", filterParam).GetJsonAsync(); - return tax; + var tax = await request.SetQueryParam("$filter", filterParam).GetJsonAsync>(); + return tax.value; }); } @@ -56,17 +57,17 @@ protected async Task TryCatchRequestAsync(IFlurlRequest request, Func 500) // simulate by putting laptop on airplane mode + var status = ex?.Call?.Response?.StatusCode; + if (status == null) // simulate by putting laptop on airplane mode { - // candidate for retry here? throw new IntegrationNoResponseException(_config, request.Url); } - if (ex.Call.Response.StatusCode == 401) + if (status == 401) { - throw new IntegrationAuthFailedException(_config, request.Url); + throw new IntegrationAuthFailedException(_config, request.Url, (int)status); } var body = await ex.Call.Response.GetJsonAsync(); - throw new IntegrationErrorResponseException(_config, request.Url, body); + throw new IntegrationErrorResponseException(_config, request.Url, (int)status, body); } } } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs index 316f36d..60b192c 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs @@ -1,11 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace OrderCloud.Catalyst { - public class AvalaraCommand : OCIntegrationCommand , ITaxCodesProvider + public class AvalaraCommand : OCIntegrationCommand , ITaxCodesProvider, ITaxCalculator { protected readonly AvalaraConfig _config; protected readonly AvalaraClient _client; @@ -16,9 +13,28 @@ public AvalaraCommand(AvalaraConfig config) : base(config) _client = new AvalaraClient(config); } + public async Task CalculateEstimateAsync(OrderSummaryForTax orderSummary) => + await CreateTransactionAsync(AvalaraDocumentType.SalesOrder, orderSummary); + + public async Task CommitTransactionAsync(OrderSummaryForTax orderSummary) => + await CreateTransactionAsync(AvalaraDocumentType.SalesInvoice, orderSummary); + + protected async Task CreateTransactionAsync(AvalaraDocumentType type, OrderSummaryForTax orderSummary) + { + var createTransaction = AvalaraRequestMapper.ToAvalaraTransactionModel(orderSummary, _config.CompanyCode, type); + var transaction = await _client.CreateTransaction(createTransaction); + var calculation = AvalaraResponseMapper.ToOrderTaxCalculation(transaction); + return calculation; + } + public async Task ListTaxCodesAsync(string filterTerm) { - throw new NotImplementedException(); + var filter = AvalaraTaxCodeMapper.MapFilterTerm(filterTerm); + var codes = await _client.ListTaxCodesAsync(filter); + return new TaxCategorizationResponse() + { + Categories = AvalaraTaxCodeMapper.MapTaxCodes(codes) + }; } } } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraConfig.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraConfig.cs index cf1dc48..b4fe0fc 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraConfig.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraConfig.cs @@ -10,12 +10,10 @@ public class AvalaraConfig: OCIntegrationConfig [RequiredIntegrationField] public string BaseUrl { get; set; } [RequiredIntegrationField] - public int AccountID { get; set; } + public string AccountID { get; set; } [RequiredIntegrationField] public string LicenseKey { get; set; } [RequiredIntegrationField] - public int CompanyID { get; set; } - [RequiredIntegrationField] public string CompanyCode { get; set; } } } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraRequestMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraRequestMapper.cs new file mode 100644 index 0000000..81ed1a9 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraRequestMapper.cs @@ -0,0 +1,84 @@ +using OrderCloud.SDK; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public static class AvalaraRequestMapper + { + public static AvalaraCreateTransactionModel ToAvalaraTransactionModel(OrderSummaryForTax order, string companyCode, AvalaraDocumentType docType) + { + var shippingLines = order.ShipEstimates.Select(ToLineItemModel); + var productLines = order.LineItems.Select(ToLineItemModel); + return new AvalaraCreateTransactionModel() + { + companyCode = companyCode, + type = docType, + customerCode = order.CustomerCode, + date = DateTime.Now, + discount = GetOrderOnlyTotalDiscount(order), + lines = productLines.Concat(shippingLines).ToList(), + purchaseOrderNo = order.OrderID + }; + } + + private static AvalaraLineItemModel ToLineItemModel(LineItemSummaryForTax lineItem) + { + return new AvalaraLineItemModel() + { + amount = lineItem.LineTotal, // Total after line-item level promotions have been applied + quantity = lineItem.Quantity, + taxCode = lineItem.TaxCode, + itemCode = lineItem.ProductID, + discounted = true, // Assumption that all products are eligible for order-level promotions + customerUsageType = null, + number = lineItem.LineItemID, + addresses = ToAddressesModel(lineItem.ShipFrom, lineItem.ShipTo) + }; + } + + private static AvalaraLineItemModel ToLineItemModel(ShipEstimateSummaryForTax shipEstimate) + { + return new AvalaraLineItemModel() + { + amount = shipEstimate.Cost, + taxCode = "FR", + itemCode = shipEstimate.Description, + customerUsageType = null, + number = shipEstimate.ShipEstimateID, + addresses = ToAddressesModel(shipEstimate.ShipFrom, shipEstimate.ShipTo) + }; + } + + private static decimal GetOrderOnlyTotalDiscount(OrderSummaryForTax order) + { + var sumOfLineItemLevelDiscounts = order.LineItems.Sum(li => li.PromotionDiscount); + var orderLevelDiscount = order.PromotionDiscount - sumOfLineItemLevelDiscounts; + return orderLevelDiscount; + } + + private static AvalaraAddressesModel ToAddressesModel(Address shipFrom, Address shipTo) + { + return new AvalaraAddressesModel() + { + shipFrom = shipFrom.ToAddressLocationInfo(), + shipTo = shipTo.ToAddressLocationInfo(), + }; + } + + private static AvalaraAddressLocationInfo ToAddressLocationInfo(this Address address) + { + return new AvalaraAddressLocationInfo() + { + line1 = address.Street1, + line2 = address.Street2, + city = address.City, + region = address.State, + postalCode = address.Zip, + country = address.Country + }; + } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraResponseMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraResponseMapper.cs new file mode 100644 index 0000000..baee8bd --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraResponseMapper.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OrderCloud.Catalyst +{ + public static class AvalaraResponseMapper + { + public static OrderTaxCalculation ToOrderTaxCalculation(AvalaraTransactionModel avalaraTransaction) + { + var shippingLines = avalaraTransaction.lines?.Where(line => line.taxCode == "FR") ?? new List(); + var itemLines = avalaraTransaction.lines?.Where(line => line.taxCode != "FR") ?? new List(); + return new OrderTaxCalculation() + { + OrderID = avalaraTransaction.purchaseOrderNo, + ExternalTransactionID = avalaraTransaction.code, + TotalTax = avalaraTransaction.totalTax ?? 0, + LineItems = itemLines.Select(ToItemTaxDetails).ToList(), + OrderLevelTaxes = shippingLines.SelectMany(ToShippingTaxDetails).ToList() + }; + } + + public static IEnumerable ToShippingTaxDetails(AvalaraTransactionLineModel transactionLineModel) + { + return transactionLineModel.details?.Select(detail => ToTaxDetails(detail, transactionLineModel.lineNumber)) ?? new List(); + } + + public static LineItemTaxCalculation ToItemTaxDetails(AvalaraTransactionLineModel transactionLineModel) + { + return new LineItemTaxCalculation() + { + LineItemID = transactionLineModel.lineNumber, + LineItemTotalTax = transactionLineModel.taxCalculated ?? 0, + LineItemLevelTaxes = transactionLineModel.details?.Select(detail => ToTaxDetails(detail, null)).ToList() ?? new List() + }; + } + + public static TaxDetails ToTaxDetails(AvalaraTransactionLineDetailModel detail, string shipEstimateID) + { + return new TaxDetails() + { + Tax = detail.tax ?? 0, + Taxable = detail.taxableAmount ?? 0, + Exempt = detail.exemptAmount ?? 0, + TaxDescription = detail.taxName, + JurisdictionLevel = detail.jurisdictionType.ToString(), + JurisdictionValue = detail.jurisName, + ShipEstimateID = shipEstimateID + }; + } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraTaxCodeMapper.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraTaxCodeMapper.cs new file mode 100644 index 0000000..2e9fa27 --- /dev/null +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraTaxCodeMapper.cs @@ -0,0 +1,36 @@ +using OrderCloud.SDK; +using System; +using System.Linq; +using OrderCloud.Catalyst; +using System.Collections.Generic; + +namespace OrderCloud.Catalyst +{ + public static class AvalaraTaxCodeMapper + { + // Tax Codes for lines on Transactions + public static List MapTaxCodes(List codes) + { + return codes.Select(MapTaxCode).ToList(); + } + + public static TaxCategorization MapTaxCode(AvalaraTaxCode code) + { + return new TaxCategorization + { + Code = code.taxCode, + Description = code.description + }; + } + + public static string MapFilterTerm(string searchTerm) + { + var searchString = $"isActive eq true"; + if (searchTerm != "") + { + searchString = $"{searchString} and (taxCode contains '{searchTerm}' OR description contains '{searchTerm}')"; + } + return searchString; + } + } +} diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraList.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraFetchResult.cs similarity index 85% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraList.cs rename to library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraFetchResult.cs index daa3dde..becade1 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraList.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraFetchResult.cs @@ -4,7 +4,7 @@ namespace OrderCloud.Catalyst { - public class AvalaraList + public class AvalaraFetchResult { public int @recordsetCount { get; set; } public List value { get; set; } = new List { }; diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs index 4818b4e..055a737 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs @@ -1,6 +1,7 @@ using Flurl.Http; using System; using System.Collections.Generic; +using System.Net; using System.Text; using System.Threading.Tasks; @@ -61,17 +62,17 @@ protected async Task TryCatchRequestAsync(IFlurlRequest request, Func 500) // simulate by putting laptop on airplane mode + var status = ex?.Call?.Response?.StatusCode; + if (status == null) // simulate by putting laptop on airplane mode { - // candidate for retry here? throw new IntegrationNoResponseException(_config, request.Url); } - if (ex.Call.Response.StatusCode == 401) + if (status == 401) { - throw new IntegrationAuthFailedException(_config, request.Url); + throw new IntegrationAuthFailedException(_config, request.Url, (int)status); } - var body = await ex.Call.Response.GetJsonAsync(); - throw new IntegrationErrorResponseException(_config, request.Url, body); + var body = await ex.Call.Response.GetJsonAsync(); + throw new IntegrationErrorResponseException(_config, request.Url, (int)status, body); } } } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs index d5c46b5..3456f79 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using System.Net; namespace OrderCloud.Catalyst { @@ -38,13 +39,13 @@ public async Task CalculateTax(VertexCalculateTaxReq } catch (FlurlHttpException ex) { - if (ex.Call.Response == null || ex.Call.Response.StatusCode > 500) // simulate by putting laptop on airplane mode + var status = ex?.Call?.Response?.StatusCode; + if (status == null) // simulate by putting laptop on airplane mode { - // candidate for retry here? throw new IntegrationNoResponseException(_config, url); } var body = await ex.Call.Response.GetJsonAsync>(); - throw new IntegrationErrorResponseException(_config, url, body.errors); + throw new IntegrationErrorResponseException(_config, url, ex.Call.Response.StatusCode, body.errors); } } @@ -80,12 +81,12 @@ protected async Task GetToken(VertexConfig config) } catch (FlurlHttpException ex) { - if (ex.Call.Response == null || ex.Call.Response.StatusCode > 500) // simulate by putting laptop on airplane mode + var status = ex?.Call?.Response?.StatusCode; + if (status == null) // simulate by putting laptop on airplane mode { - // candidate for retry here? throw new IntegrationNoResponseException(_config, url); } - throw new IntegrationAuthFailedException(_config, url); + throw new IntegrationAuthFailedException(_config, url, (int)status); } } } From 7f53c36378ba1667774ad40be44a55ca4f9f668c Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Fri, 11 Feb 2022 11:55:23 -0600 Subject: [PATCH 35/40] updated to support config overrides. This is important if different siuations call for different account credentials. --- .../Extensions/ExtensionMethods.cs | 2 +- .../Implementations/Avalara/AvalaraClient.cs | 42 ++++--- .../Implementations/Avalara/AvalaraCommand.cs | 29 ++--- .../Implementations/TaxJar/TaxJarClient.cs | 57 +++++----- .../Implementations/TaxJar/TaxJarCommand.cs | 30 +++-- .../Implementations/Vertex/VertexClient.cs | 34 ++---- .../Implementations/Vertex/VertexCommand.cs | 23 ++-- .../Integrations/Interfaces/ITaxCalculator.cs | 6 +- .../Interfaces/ITaxCodeProvider.cs | 2 +- .../Integrations/OCIntegrationCommand.cs | 34 +++++- .../Integrations/OCIntegrationConfig.cs | 18 --- .../IntegrationTests/Avalara/AvalaraTests.cs | 103 ++++++++++++++++++ .../IntegrationTests/TaxJar/TaxJarTests.cs | 12 +- .../IntegrationTests/Vertex/VertexTests.cs | 10 +- 14 files changed, 249 insertions(+), 153 deletions(-) create mode 100644 tests/OrderCloud.Catalyst.Tests/IntegrationTests/Avalara/AvalaraTests.cs diff --git a/library/OrderCloud.Catalyst/Extensions/ExtensionMethods.cs b/library/OrderCloud.Catalyst/Extensions/ExtensionMethods.cs index b1a3736..cc936d4 100644 --- a/library/OrderCloud.Catalyst/Extensions/ExtensionMethods.cs +++ b/library/OrderCloud.Catalyst/Extensions/ExtensionMethods.cs @@ -124,7 +124,7 @@ public static ShipMethod GetSelectedShipMethod(this ShipEstimate shipEstimate) public static List<(int Quantity, LineItem LineItem)> GetShipEstimateLineItems(this OrderWorksheet order, string shipEstimateID) { var shipEstimate = order.ShipEstimateResponse.ShipEstimates.FirstOrDefault(se => se.ID == shipEstimateID); - Require.That(shipEstimate != null, new ArgumentException($"No matching ship estimate found with ID {shipEstimateID}")); + Require.That(shipEstimate != null, new ArgumentException($"No matching ship estimate found with ID {shipEstimateID}", "shipEstimateID")); var lineItems = new List<(int quantity, LineItem lineItem)> { }; foreach (var shipEstimateItem in shipEstimate.ShipEstimateItems) diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs index 03e6e1c..c6c8f63 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs @@ -9,24 +9,18 @@ namespace OrderCloud.Catalyst { public class AvalaraClient { - protected readonly FlurlClient _flurl; - protected readonly AvalaraConfig _config; - - public AvalaraClient(AvalaraConfig config) - { - _config = config; - _flurl = new FlurlClient(config.BaseUrl).WithBasicAuth(config.AccountID, config.LicenseKey); - } - /// /// https://developer.avalara.com/api-reference/avatax/rest/v2/methods/Definitions/ListTaxCodes/ /// - public async Task> ListTaxCodesAsync(string filterParam) + public static async Task> ListTaxCodesAsync(string filterParam, AvalaraConfig config) { - var request = _flurl.Request("api", "v2", "definitions", "taxcodes"); - return await TryCatchRequestAsync(request, async () => + + return await TryCatchRequestAsync(config, async (request) => { - var tax = await request.SetQueryParam("$filter", filterParam).GetJsonAsync>(); + var tax = await request + .AppendPathSegments("api", "v2", "definitions", "taxcodes") + .SetQueryParam("$filter", filterParam) + .GetJsonAsync>(); return tax.value; }); } @@ -34,40 +28,42 @@ public async Task> ListTaxCodesAsync(string filterParam) /// /// https://developer.avalara.com/api-reference/avatax/rest/v2/methods/Transactions/CreateTransaction/ /// - public async Task CreateTransaction(AvalaraCreateTransactionModel transaction) + public static async Task CreateTransaction(AvalaraCreateTransactionModel transaction, AvalaraConfig config) { - var request = _flurl.Request("api", "v2", "transactions", "create"); - return await TryCatchRequestAsync(request, async () => + return await TryCatchRequestAsync(config, async (request) => { - var tax = await request.PostJsonAsync(transaction).ReceiveJson(); + var tax = await request + .AppendPathSegments("api", "v2", "transactions", "create") + .PostJsonAsync(transaction).ReceiveJson(); return tax; }); } - protected async Task TryCatchRequestAsync(IFlurlRequest request, Func> run) + protected static async Task TryCatchRequestAsync(AvalaraConfig config, Func> run) { + var request = config.BaseUrl.WithBasicAuth(config.AccountID, config.LicenseKey); try { - return await run(); + return await run(request); } catch (FlurlHttpTimeoutException ex) // simulate with this https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error { // candidate for retry here? - throw new IntegrationNoResponseException(_config, request.Url); + throw new IntegrationNoResponseException(config, request.Url); } catch (FlurlHttpException ex) { var status = ex?.Call?.Response?.StatusCode; if (status == null) // simulate by putting laptop on airplane mode { - throw new IntegrationNoResponseException(_config, request.Url); + throw new IntegrationNoResponseException(config, request.Url); } if (status == 401) { - throw new IntegrationAuthFailedException(_config, request.Url, (int)status); + throw new IntegrationAuthFailedException(config, request.Url, (int)status); } var body = await ex.Call.Response.GetJsonAsync(); - throw new IntegrationErrorResponseException(_config, request.Url, (int)status, body); + throw new IntegrationErrorResponseException(config, request.Url, (int)status, body); } } } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs index 60b192c..323316c 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs @@ -4,33 +4,28 @@ namespace OrderCloud.Catalyst { public class AvalaraCommand : OCIntegrationCommand , ITaxCodesProvider, ITaxCalculator { - protected readonly AvalaraConfig _config; - protected readonly AvalaraClient _client; + public AvalaraCommand(AvalaraConfig configDefault) : base(configDefault) { } - public AvalaraCommand(AvalaraConfig config) : base(config) - { - _config = config; - _client = new AvalaraClient(config); - } - - public async Task CalculateEstimateAsync(OrderSummaryForTax orderSummary) => - await CreateTransactionAsync(AvalaraDocumentType.SalesOrder, orderSummary); + public async Task CalculateEstimateAsync(OrderSummaryForTax orderSummary, OCIntegrationConfig configOverride = null) => + await CreateTransactionAsync(AvalaraDocumentType.SalesOrder, orderSummary, configOverride); - public async Task CommitTransactionAsync(OrderSummaryForTax orderSummary) => - await CreateTransactionAsync(AvalaraDocumentType.SalesInvoice, orderSummary); + public async Task CommitTransactionAsync(OrderSummaryForTax orderSummary, OCIntegrationConfig configOverride = null) => + await CreateTransactionAsync(AvalaraDocumentType.SalesInvoice, orderSummary, configOverride); - protected async Task CreateTransactionAsync(AvalaraDocumentType type, OrderSummaryForTax orderSummary) + protected async Task CreateTransactionAsync(AvalaraDocumentType type, OrderSummaryForTax orderSummary, OCIntegrationConfig configOverride = null) { - var createTransaction = AvalaraRequestMapper.ToAvalaraTransactionModel(orderSummary, _config.CompanyCode, type); - var transaction = await _client.CreateTransaction(createTransaction); + var config = GetValidatedConfig(configOverride); + var createTransaction = AvalaraRequestMapper.ToAvalaraTransactionModel(orderSummary, config.CompanyCode, type); + var transaction = await AvalaraClient.CreateTransaction(createTransaction, config); var calculation = AvalaraResponseMapper.ToOrderTaxCalculation(transaction); return calculation; } - public async Task ListTaxCodesAsync(string filterTerm) + public async Task ListTaxCodesAsync(string filterTerm, OCIntegrationConfig configOverride = null) { + var config = GetValidatedConfig(configOverride); var filter = AvalaraTaxCodeMapper.MapFilterTerm(filterTerm); - var codes = await _client.ListTaxCodesAsync(filter); + var codes = await AvalaraClient.ListTaxCodesAsync(filter, config); return new TaxCategorizationResponse() { Categories = AvalaraTaxCodeMapper.MapTaxCodes(codes) diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs index 055a737..300faa3 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs @@ -9,70 +9,75 @@ namespace OrderCloud.Catalyst { public class TaxJarClient { - protected readonly FlurlClient _flurl; - protected readonly TaxJarConfig _config; - - public TaxJarClient(TaxJarConfig config) - { - _config = config; - _flurl = new FlurlClient(config.BaseUrl).WithOAuthBearerToken(config.APIToken); - } - /// /// https://developers.taxjar.com/api/reference/#post-create-an-order-transaction /// - public async Task CreateOrderAsync(TaxJarOrder order) + public static async Task CreateOrderAsync(TaxJarOrder order, TaxJarConfig config) { - var tax = await _flurl.Request("v2", "taxes", "orders").PostJsonAsync(order).ReceiveJson(); - return tax; + return await TryCatchRequestAsync(config, async (request) => + { + var tax = await request + .AppendPathSegments("v2", "taxes", "orders") + .PostJsonAsync(order) + .ReceiveJson(); + return tax; + }); } /// /// https://developers.taxjar.com/api/reference/#get-list-tax-categories /// - public async Task ListCategoriesAsync() + public static async Task ListCategoriesAsync(TaxJarConfig config) { - var categories = await _flurl.Request("v2", "categories").GetJsonAsync(); - return categories; + return await TryCatchRequestAsync(config, async (request) => + { + var categories = await request + .AppendPathSegments("v2", "categories") + .GetJsonAsync(); + return categories; + }); } /// /// https://developers.taxjar.com/api/reference/#post-calculate-sales-tax-for-an-order /// - public async Task CalcTaxForOrderAsync(TaxJarOrder order) + public static async Task CalcTaxForOrderAsync(TaxJarOrder order, TaxJarConfig config) { - var request = _flurl.Request("v2", "taxes"); - return await TryCatchRequestAsync(request, async () => + return await TryCatchRequestAsync(config, async (request) => { - var tax = await request.PostJsonAsync(order).ReceiveJson(); + var tax = await request + .AppendPathSegments("v2", "taxes") + .PostJsonAsync(order) + .ReceiveJson(); return tax; }); } - protected async Task TryCatchRequestAsync(IFlurlRequest request, Func> run) + protected static async Task TryCatchRequestAsync(TaxJarConfig config, Func> run) { + var request = config.BaseUrl.WithOAuthBearerToken(config.APIToken); try { - return await run(); + return await run(request); } catch (FlurlHttpTimeoutException ex) // simulate with this https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error { // candidate for retry here? - throw new IntegrationNoResponseException(_config, request.Url); + throw new IntegrationNoResponseException(config, request.Url); } catch (FlurlHttpException ex) { var status = ex?.Call?.Response?.StatusCode; if (status == null) // simulate by putting laptop on airplane mode { - throw new IntegrationNoResponseException(_config, request.Url); + throw new IntegrationNoResponseException(config, request.Url); } if (status == 401) { - throw new IntegrationAuthFailedException(_config, request.Url, (int)status); + throw new IntegrationAuthFailedException(config, request.Url, (int)status); } - var body = await ex.Call.Response.GetJsonAsync(); - throw new IntegrationErrorResponseException(_config, request.Url, (int)status, body); + var body = await ex.Call.Response.GetJsonAsync(); + throw new IntegrationErrorResponseException(config, request.Url, (int)status, body); } } } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarCommand.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarCommand.cs index 7bd5a4f..ab5f644 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarCommand.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarCommand.cs @@ -9,49 +9,45 @@ namespace OrderCloud.Catalyst { public class TaxJarCommand : OCIntegrationCommand, ITaxCalculator, ITaxCodesProvider { - protected readonly TaxJarConfig _config; - protected readonly TaxJarClient _client; + public TaxJarCommand(TaxJarConfig configDefault) : base(configDefault) { } - public TaxJarCommand(TaxJarConfig config) : base(config) + public async Task ListTaxCodesAsync(string filterTerm = "", OCIntegrationConfig configOverride = null) { - _config = config; - _client = new TaxJarClient(config); - } - - public async Task ListTaxCodesAsync(string filterTerm = "") - { - var categories = await _client.ListCategoriesAsync(); + var config = GetValidatedConfig(configOverride); + var categories = await TaxJarClient.ListCategoriesAsync(config); return TaxJarCategoryMapper.ToTaxCategorization(categories, filterTerm); } - public async Task CalculateEstimateAsync(OrderSummaryForTax orderSummary) + public async Task CalculateEstimateAsync(OrderSummaryForTax orderSummary, OCIntegrationConfig configOverride = null) { - var orders = await CalculateTax(orderSummary); + var config = GetValidatedConfig(configOverride); + var orders = await CalculateTax(orderSummary, config); var orderTaxCalculation = TaxJarResponseMapper.ToOrderTaxCalculation(orders); return orderTaxCalculation; } - public async Task CommitTransactionAsync(OrderSummaryForTax orderSummary) + public async Task CommitTransactionAsync(OrderSummaryForTax orderSummary, OCIntegrationConfig configOverride = null) { - var orders = await CalculateTax(orderSummary); + var config = GetValidatedConfig(configOverride); + var orders = await CalculateTax(orderSummary, config); foreach (var response in orders) { response.request.transaction_date = DateTime.UtcNow.ToString("yyyy/MM/dd"); response.request.sales_tax = response.response.tax.amount_to_collect; } - await Throttler.RunAsync(orders, 100, 8, async order => await _client.CreateOrderAsync(order.request)); + await Throttler.RunAsync(orders, 100, 8, async order => await TaxJarClient.CreateOrderAsync(order.request, config)); var orderTaxCalculation = TaxJarResponseMapper.ToOrderTaxCalculation(orders); return orderTaxCalculation; } - protected async Task> CalculateTax(OrderSummaryForTax orderSummary) + protected async Task> CalculateTax(OrderSummaryForTax orderSummary, TaxJarConfig config) { var orders = TaxJarRequestMapper.ToOrders(orderSummary); return await Throttler.RunAsync(orders, 100, 8, async order => { - var tax = await _client.CalcTaxForOrderAsync(order); + var tax = await TaxJarClient.CalcTaxForOrderAsync(order, config); return (order, tax); }); } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs index 3456f79..6ee2a46 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs @@ -12,18 +12,11 @@ public class VertexClient { protected const string ApiUrl = "https://restconnect.vertexsmb.com"; protected const string AuthUrl = "https://auth.vertexsmb.com"; - protected DateTimeOffset? CurrentTokenExpires = null; - protected readonly VertexConfig _config; - protected VertexTokenResponse _token; - public VertexClient(VertexConfig config) + public static async Task CalculateTax(VertexCalculateTaxRequest request, VertexConfig config) { - _config = config; - } - - public async Task CalculateTax(VertexCalculateTaxRequest request) - { - var token = await GetToken(_config); + // TODO - cache the token. But, support config overrides and don't create multiple HttpClient objects in memory + var token = await GetToken(config); var url = $"{ApiUrl}/vertex-restapi/v1/sale"; try { @@ -35,28 +28,23 @@ public async Task CalculateTax(VertexCalculateTaxReq } catch (FlurlHttpTimeoutException ex) // simulate with this https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error { // candidate for retry here? - throw new IntegrationNoResponseException(_config, url); + throw new IntegrationNoResponseException(config, url); } catch (FlurlHttpException ex) { var status = ex?.Call?.Response?.StatusCode; if (status == null) // simulate by putting laptop on airplane mode { - throw new IntegrationNoResponseException(_config, url); + throw new IntegrationNoResponseException(config, url); } var body = await ex.Call.Response.GetJsonAsync>(); - throw new IntegrationErrorResponseException(_config, url, ex.Call.Response.StatusCode, body.errors); + throw new IntegrationErrorResponseException(config, url, ex.Call.Response.StatusCode, body.errors); } } - protected async Task GetToken(VertexConfig config) + protected static async Task GetToken(VertexConfig config) { - if (_token?.access_token != null && CurrentTokenExpires != null && CurrentTokenExpires > DateTimeOffset.Now) - { - return _token; - } - var body = new { scope = "calc-rest-api", @@ -71,22 +59,20 @@ protected async Task GetToken(VertexConfig config) { var response = await url.PostUrlEncodedAsync(body); var token = await response.GetJsonAsync(); - _token = token; - CurrentTokenExpires = DateTimeOffset.Now.AddSeconds(token.expires_in); return token; } catch (FlurlHttpTimeoutException ex) // simulate with this https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error { // candidate for retry here? - throw new IntegrationNoResponseException(_config, url); + throw new IntegrationNoResponseException(config, url); } catch (FlurlHttpException ex) { var status = ex?.Call?.Response?.StatusCode; if (status == null) // simulate by putting laptop on airplane mode { - throw new IntegrationNoResponseException(_config, url); + throw new IntegrationNoResponseException(config, url); } - throw new IntegrationAuthFailedException(_config, url, (int)status); + throw new IntegrationAuthFailedException(config, url, (int)status); } } } diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexCommand.cs b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexCommand.cs index 04f820f..d2ad200 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexCommand.cs +++ b/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexCommand.cs @@ -6,31 +6,26 @@ namespace OrderCloud.Catalyst { public class VertexCommand : OCIntegrationCommand, ITaxCalculator { - protected readonly VertexClient _client; - protected readonly VertexConfig _config; - public VertexCommand(VertexConfig config) : base(config) - { - _config = config; - _client = new VertexClient(config); - } + public VertexCommand(VertexConfig configDefault) : base(configDefault) { } /// /// Calculates tax for an order without creating any records. Use this to display tax amount to user prior to order submit. /// - public async Task CalculateEstimateAsync(OrderSummaryForTax orderSummary) => - await CalculateTaxAsync(orderSummary, VertexSaleMessageType.QUOTATION); + public async Task CalculateEstimateAsync(OrderSummaryForTax orderSummary, OCIntegrationConfig configOverride = null) => + await CalculateTaxAsync(VertexSaleMessageType.QUOTATION, orderSummary, configOverride); /// /// Creates a tax transaction record in the calculating system. Use this once on purchase, payment capture, or fulfillment. /// - public async Task CommitTransactionAsync(OrderSummaryForTax orderSummary) => - await CalculateTaxAsync(orderSummary, VertexSaleMessageType.INVOICE); + public async Task CommitTransactionAsync(OrderSummaryForTax orderSummary, OCIntegrationConfig configOverride = null) => + await CalculateTaxAsync(VertexSaleMessageType.INVOICE, orderSummary, configOverride); - protected async Task CalculateTaxAsync(OrderSummaryForTax orderSummary, VertexSaleMessageType type) + protected async Task CalculateTaxAsync(VertexSaleMessageType type, OrderSummaryForTax orderSummary, OCIntegrationConfig configOverride) { - var request = VertexRequestMapper.ToVertexCalculateTaxRequest(orderSummary, _config.CompanyName, type); - var response = await _client.CalculateTax(request); + var config = GetValidatedConfig(configOverride); + var request = VertexRequestMapper.ToVertexCalculateTaxRequest(orderSummary, config.CompanyName, type); + var response = await VertexClient.CalculateTax(request, config); var orderTaxCalculation = response.ToOrderTaxCalculation(); return orderTaxCalculation; } diff --git a/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs b/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs index 26398b8..c5cd8a0 100644 --- a/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs +++ b/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs @@ -9,16 +9,16 @@ namespace OrderCloud.Catalyst /// /// An interface to define expected responses from tax calculation. Meant to be used in OrderCloud ecommerce checkout. /// - public interface ITaxCalculator + public interface ITaxCalculator { /// /// Calculates tax for an order without creating any records. Use this to display tax amount to user prior to order submit. /// - Task CalculateEstimateAsync(OrderSummaryForTax orderSummary); + Task CalculateEstimateAsync(OrderSummaryForTax orderSummary, OCIntegrationConfig configOverride = null); /// /// Creates a tax transaction record in the calculating system. Use this once per order - on order submit, payment capture, or fulfillment. /// - Task CommitTransactionAsync(OrderSummaryForTax orderSummary); + Task CommitTransactionAsync(OrderSummaryForTax orderSummary, OCIntegrationConfig configOverride = null); } /// diff --git a/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCodeProvider.cs b/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCodeProvider.cs index bb35fcd..fbba41e 100644 --- a/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCodeProvider.cs +++ b/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCodeProvider.cs @@ -13,7 +13,7 @@ public interface ITaxCodesProvider /// /// List the various tax categories a product could fall under /// - Task ListTaxCodesAsync(string filterTerm); + Task ListTaxCodesAsync(string filterTerm, OCIntegrationConfig configOverride = null); } public class TaxCategorizationResponse diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs b/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs index a76024c..9e154b6 100644 --- a/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs +++ b/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace OrderCloud.Catalyst @@ -9,9 +10,38 @@ namespace OrderCloud.Catalyst /// public abstract class OCIntegrationCommand { - public OCIntegrationCommand(OCIntegrationConfig config) + protected readonly OCIntegrationConfig _configDefault; + + public OCIntegrationCommand(OCIntegrationConfig configDefault) + { + _configDefault = configDefault; + } + + public T GetValidatedConfig(OCIntegrationConfig configOverride) where T : OCIntegrationConfig { - config.ValidateRequiredFields(); + var config = configOverride ?? _configDefault; + var type = config.GetType(); + if (type != typeof(T)) + { + throw new ArgumentException($"Integration configuration must be of type {typeof(T).Name} to match this command. Found {type.Name} instead.", "configOverride"); + } + + var missing = type + .GetProperties() + .Where(prop => + { + var value = (string)prop.GetValue(config); + var isRequired = Attribute.IsDefined(prop, typeof(RequiredIntegrationFieldAttribute)); + return isRequired && value.IsNullOrEmpty(); + }); + + if (missing.Any()) + { + var names = missing.Select(p => p.Name).ToList(); + throw new IntegrationMissingConfigsException(config, names); + } + + return config as T; } } } diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs index 1bad25a..47a7a09 100644 --- a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs +++ b/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs @@ -11,23 +11,5 @@ namespace OrderCloud.Catalyst public abstract class OCIntegrationConfig { public abstract string ServiceName { get; } - - public void ValidateRequiredFields() - { - var props = GetType() - .GetProperties(); - var missing = props.Where(prop => - { - var value = (string)prop.GetValue(this); - var isRequired = Attribute.IsDefined(prop, typeof(RequiredIntegrationFieldAttribute)); - return isRequired && value.IsNullOrEmpty(); - }); - - if (missing.Any()) - { - var names = missing.Select(p => p.Name).ToList(); - throw new IntegrationMissingConfigsException(this, names); - } - } } } diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Avalara/AvalaraTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Avalara/AvalaraTests.cs new file mode 100644 index 0000000..ce3d997 --- /dev/null +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Avalara/AvalaraTests.cs @@ -0,0 +1,103 @@ +using AutoFixture; +using Flurl.Http.Testing; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OrderCloud.Catalyst.Tests +{ + [TestFixture] + public class AvalaraTests + { + private static Fixture _fixture = new Fixture(); + private HttpTest _httpTest; + private static AvalaraConfig _config = new AvalaraConfig() + { + AccountID = _fixture.Create(), + LicenseKey = _fixture.Create(), + CompanyCode = _fixture.Create(), + BaseUrl = "https://api.fake.com" + }; + private AvalaraCommand _command = new AvalaraCommand(_config); + private OrderSummaryForTax _order = _fixture.Create(); + + [SetUp] + public void CreateHttpTest() + { + _httpTest = new HttpTest(); + } + + [TearDown] + public void DisposeHttpTest() + { + _httpTest.Dispose(); + } + + [Test] + public void ShouldThrowErrorIfMissingRequiredConfigs() + { + // Arrange + var config = new AvalaraConfig(); + // Act + var ex = Assert.ThrowsAsync(async () => + await _command.CalculateEstimateAsync(_order, config) + ); + // Assert + var data = (IntegrationMissingConfigs)ex.Errors[0].Data; + Assert.AreEqual(data.ServiceName, "Avalara"); + Assert.AreEqual(data.MissingFieldNames, new List { "BaseUrl", "AccountID", "LicenseKey", "CompanyCode" }); + } + + [Test] + public void ShouldThrowErrorIfAuthorizationFails() + { + // Arrange + _httpTest.RespondWith("{\"error\": {\"code\": \"AuthenticationException\",\"message\": \"Authentication failed.\",\"details\": [{\"code\": \"AuthenticationException\",\"message\": \"Authentication failed.\",\"description\": \"Missing authentication or unable to authenticate the user or the account.\",\"faultCode\": \"Client\",\"helpLink\": \"http://developer.avalara.com/avatax/errors/AuthenticationException\"}]}}", 401); // real avalara response + + var ex = Assert.ThrowsAsync(async () => + // Act + await _command.CalculateEstimateAsync(_order) + ); + + var data = (IntegrationAuthFailedError)ex.Errors[0].Data; + Assert.AreEqual(data.ServiceName, "Avalara"); + Assert.AreEqual(data.ResponseStatus, 401); + Assert.AreEqual(data.RequestUrl, $"{_config.BaseUrl}/api/v2/transactions/create"); + } + + [Test] + public void ShouldThrowErrorIfNoResponse() + { + // Arrange + _httpTest.SimulateTimeout(); + + var ex = Assert.ThrowsAsync(async () => + // Act + await _command.CalculateEstimateAsync(_order) + ); + + var data = (IntegrationNoResponseError)ex.Errors[0].Data; + Assert.AreEqual(data.ServiceName, "Avalara"); + } + + [Test] + public void ShouldThrowErrorForBadRequest() + { + // Arrange + _httpTest + // real avalara response + .RespondWith("{\"error\":{\"code\":\"InvalidAddress\",\"message\":\"The address value was incomplete.\",\"target\":\"IncorrectData\",\"details\":[{\"code\":\"InvalidAddress\",\"number\":309,\"message\":\"The address value was incomplete.\",\"description\":\"The address value ShipTo was incomplete.You must provide either a valid postal code, line1 + city + region, or line1 + postal code.\",\"faultCode\":\"Client\",\"helpLink\":\"http://developer.avalara.com/avatax/errors/InvalidAddress\",\"severity\":\"Error\"}]}}", 400); + + var ex = Assert.ThrowsAsync(async () => + // Act + await _command.CalculateEstimateAsync(_order) + ); + + var data = (IntegrationErrorResponseError) ex.Errors[0].Data; + Assert.AreEqual(data.ServiceName, "Avalara"); + Assert.AreEqual(data.RequestUrl, $"{_config.BaseUrl}/api/v2/transactions/create"); + Assert.AreEqual(data.ResponseStatus, 400); + } + } +} diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs index 1cad876..a6dff5e 100644 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs @@ -40,7 +40,9 @@ public void ShouldThrowErrorIfMissingRequiredConfigs() // Arrange var config = new TaxJarConfig(); // Act - var ex = Assert.Throws(() => new TaxJarCommand(config)); + var ex = Assert.ThrowsAsync(async () => + await _command.CalculateEstimateAsync(_order, config) + ); // Assert var data = (IntegrationMissingConfigs)ex.Errors[0].Data; Assert.AreEqual(data.ServiceName, "TaxJar"); @@ -60,6 +62,7 @@ await _command.CalculateEstimateAsync(_order) var data = (IntegrationAuthFailedError)ex.Errors[0].Data; Assert.AreEqual(data.ServiceName, "TaxJar"); + Assert.AreEqual(data.ResponseStatus, 401); Assert.AreEqual(data.RequestUrl, $"{_config.BaseUrl}/v2/taxes"); } @@ -79,7 +82,7 @@ await _command.CalculateEstimateAsync(_order) } [Test] - public void ShouldThrowVertexErrorForBadRequest() + public void ShouldThrowErrorForBadRequest() { // Arrange _httpTest @@ -91,10 +94,11 @@ public void ShouldThrowVertexErrorForBadRequest() await _command.CalculateEstimateAsync(_order) ); - var data = (IntegrationErrorResponseError)ex.Errors[0].Data; + var data = (IntegrationErrorResponseError) ex.Errors[0].Data; Assert.AreEqual(data.ServiceName, "TaxJar"); + Assert.AreEqual(data.ResponseStatus, 400); Assert.AreEqual(data.RequestUrl, $"{_config.BaseUrl}/v2/taxes"); - Assert.AreEqual(((TaxJarError)data.ResponseBody).detail, "No to zip, required when country is US"); + Assert.AreEqual(((TaxJarError) data.ResponseBody).detail, "No to zip, required when country is US"); } [Test] diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs index 4742245..fa5ebb6 100644 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs +++ b/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs @@ -36,7 +36,9 @@ public void ShouldThrowErrorIfMissingRequiredConfigs() // Arrange var config = new VertexConfig(); // Act - var ex = Assert.Throws(() => new VertexCommand(config)); + var ex = Assert.ThrowsAsync(async () => + await _command.CalculateEstimateAsync(_order, config) + ); // Assert var data = (IntegrationMissingConfigs) ex.Errors[0].Data; Assert.AreEqual(data.ServiceName, "Vertex"); @@ -55,6 +57,7 @@ await _command.CalculateEstimateAsync(_order) ); var data = (IntegrationAuthFailedError) ex.Errors[0].Data; + Assert.AreEqual(data.ResponseStatus, 400); Assert.AreEqual(data.ServiceName, "Vertex"); Assert.AreEqual(data.RequestUrl, "https://auth.vertexsmb.com/identity/connect/token"); } @@ -75,7 +78,7 @@ await _command.CalculateEstimateAsync(_order) } [Test] - public void ShouldThrowVertexErrorForBadRequest() + public void ShouldThrowErrorForBadRequest() { // Arrange _httpTest @@ -89,8 +92,9 @@ public void ShouldThrowVertexErrorForBadRequest() await _command.CalculateEstimateAsync(_order) ); - var data = (IntegrationErrorResponseError)ex.Errors[0].Data; + var data = (IntegrationErrorResponseError) ex.Errors[0].Data; Assert.AreEqual(data.ServiceName, "Vertex"); + Assert.AreEqual(data.ResponseStatus, 400); Assert.AreEqual(data.RequestUrl, "https://restconnect.vertexsmb.com/vertex-restapi/v1/sale"); Assert.AreEqual(((List) data.ResponseBody)[0].detail, "The LocationRole being added is invalid.This might be due to an invalid location or an invalid address field.Make sure that the locationRole is valid, and try again.\n"); } From 4b1ada0150e282ce84fffb1b8bf29c1f7eadf7ba Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Fri, 11 Feb 2022 12:26:29 -0600 Subject: [PATCH 36/40] move to separate projects --- .../AvalaraClient.cs | 2 +- .../AvalaraCommand.cs | 2 +- .../AvalaraConfig.cs | 2 +- .../Mappers/AvalaraRequestMapper.cs | 2 +- .../Mappers/AvalaraResponseMapper.cs | 2 +- .../Mappers/AvalaraTaxCodeMapper.cs | 2 +- .../Models/AvalaraAddressesModel.cs | 2 +- .../Models/AvalaraCreateTransactionModel.cs | 2 +- .../Models/AvalaraFetchResult.cs | 2 +- .../Models/AvalaraLineItemModel.cs | 2 +- .../Models/AvalaraTaxCode.cs | 2 +- .../Models/AvalaraTransactionAddressModel.cs | 2 +- .../AvalaraTransactionLineDetailModel.cs | 2 +- .../Models/AvalaraTransactionLineModel.cs | 2 +- .../Models/AvalaraTransactionModel.cs | 2 +- .../OrderCloud.Catalyst.Tax.Avalara.csproj | 30 ++++++++++++++++++ .../Mapper/TaxJarCategoryMapper.cs | 2 +- .../Mapper/TaxJarRequestMapper.cs | 2 +- .../Mapper/TaxJarResponseMapper.cs | 2 +- .../Models/TaxJarCalculateResponse.cs | 2 +- .../Models/TaxJarCategory.cs | 2 +- .../Models/TaxJarError.cs | 2 +- .../Models/TaxJarOrder.cs | 2 +- .../OrderCloud.Catalyst.Tax.TaxJar.csproj | 30 ++++++++++++++++++ .../README.md | 0 .../TaxJarClient.cs | 2 +- .../TaxJarCommand.cs | 2 +- .../TaxJarConfig.cs | 2 +- .../Mappers/VertexRequestMapper.cs | 2 +- .../Mappers/VertexResponseMapper.cs | 2 +- .../Models/VertexCalculateTaxRequest.cs | 2 +- .../Models/VertexCalculateTaxResponse.cs | 2 +- .../Models/VertexCurrency.cs | 2 +- .../Models/VertexCustomer.cs | 2 +- .../Models/VertexDiscount.cs | 2 +- .../Models/VertexException.cs | 2 +- .../Models/VertexJurisdiction.cs | 2 +- .../Models/VertexLineItem.cs | 2 +- .../Models/VertexLocation.cs | 2 +- .../Models/VertexResponseLineItem.cs | 2 +- .../Models/VertexSeller.cs | 2 +- .../Models/VertexTokenResponse.cs | 2 +- .../OrderCloud.Catalyst.Tax.Vertex.csproj | 31 +++++++++++++++++++ .../README.md | 0 .../VertexClient.cs | 2 +- .../VertexCommand.cs | 2 +- .../VertexConfig.cs | 2 +- .../Commands/ExampleCommand.cs | 0 .../Controllers/DemoController.cs | 0 .../Controllers/EnvController.cs | 0 .../Controllers/ModelValidation/README.md | 0 .../Controllers/WebhookController.cs | 0 .../OrderCloud.Catalyst.TestApi.csproj | 5 ++- .../Program.cs | 0 .../Services/LazyCacheService.cs | 0 .../Services/RedisCacheService.cs | 0 .../Startup.cs | 0 .../TestSettings.cs | 0 .../DataAnnotationTests.cs | 0 .../ApiIntegrationTests/GeneralErrorTests.cs | 0 .../ListArgPageOnlyTests.cs | 0 .../ApiIntegrationTests/ListArgTests.cs | 0 .../ApiIntegrationTests/SearchArgsTests.cs | 0 .../ApiIntegrationTests/TokenTests.cs | 0 .../ApiIntegrationTests/UserAuthTests.cs | 0 .../UserTypeRestrictedTests.cs | 0 .../ApiIntegrationTests/WebhookAuthTests.cs | 0 .../AutoNSubstituteDataAttribute.cs | 0 .../DataMovementTests/ListAllAsyncTests.cs | 0 .../DataMovementTests/ListByIDTests.cs | 0 .../DataMovementTests/RetryTests.cs | 0 .../DataMovementTests/ThrottlerTests.cs | 0 .../IntegrationTests/Avalara/AvalaraTests.cs | 1 + .../IntegrationTests/TaxJar/TaxJarTests.cs | 1 + .../IntegrationTests/Vertex/VertexTests.cs | 1 + .../OrderCloud.Catalyst.Tests.csproj | 0 .../TestFramework.cs | 0 OrderCloud.Catalyst.sln | 29 ++++++++++++++--- .../Api/CatalystController.cs | 0 .../AssemblyInfo.cs | 0 .../Auth/UserAuth/DecodedToken.cs | 0 .../Auth/UserAuth/FakeOrderCloudToken.cs | 0 .../Auth/UserAuth/OrderCloudUserAuth.cs | 0 .../Auth/UserAuth/README.md | 0 .../UserAuth/RequestAuthenticationService.cs | 0 .../UserAuth/UserTypeRestrictedToAttribute.cs | 0 .../Auth/WebhookAuth/OrderCloudWebhookAuth.cs | 0 .../Auth/WebhookAuth/README.md | 0 .../DataMovement/Caching/ISimpleCache.cs | 0 .../DataMovement/Caching/LazyCacheService.cs | 0 .../DataMovement/Caching/README.md | 0 .../DataMovement/Differ.cs | 0 .../ListAllAsync/ListAllHelper.cs | 0 .../DataMovement/ListAllAsync/README.md | 0 .../DataMovement/RetryPolicy.cs | 0 .../DataMovement/Throttler/README.md | 0 .../DataMovement/Throttler/Throttler.cs | 0 .../Errors/CatalystBaseException.cs | 0 .../Errors/ErrorCode.cs | 0 .../Errors/Exceptions.cs | 0 .../Errors/GlobalExceptionHandler.cs | 0 .../Errors/README.md | 0 .../Errors/Require.cs | 0 .../Errors/ValidateModel.cs | 0 .../Extensions/ExtensionMethods.cs | 0 .../Generated/ListAllExtensions.cs | 0 .../Generated/ListByIDExtensions.cs | 0 .../Generated/ListExtensions.cs | 0 .../Integrations/CONTRIBUTING.md | 0 .../IntegrationAuthFailedException.cs | 0 .../IntegrationErrorResponseException.cs | 0 .../IntegrationMissingConfigsException.cs | 0 .../IntegrationNoResponseException.cs | 0 .../Integrations/Interfaces/ITaxCalculator.cs | 0 .../Interfaces/ITaxCodeProvider.cs | 0 .../Integrations/OCIntegrationCommand.cs | 0 .../Integrations/OCIntegrationConfig.cs | 0 .../Integrations}/README.md | 0 .../Jobs/BaseJob.cs | 0 .../Models/ListOptions/ListArgs.cs | 0 .../Models/ListOptions/ListArgsBinder.cs | 0 .../Models/ListOptions/ListArgsPageOnly.cs | 0 .../Models/ListOptions/ListFilter.cs | 0 .../Models/ListOptions/README.md | 0 .../Models/ListOptions/SearchArgs.cs | 0 .../Webhooks/OpenIDConnectUserPayload.cs | 0 .../Models/Webhooks/OrderCalculatePayload.cs | 0 .../Models/Webhooks/PreWebhookResponse.cs | 0 .../OrderCloud.Catalyst.csproj | 2 +- codegen/package.json | 2 +- 130 files changed, 167 insertions(+), 49 deletions(-) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/AvalaraClient.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/AvalaraCommand.cs (97%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/AvalaraConfig.cs (91%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/Mappers/AvalaraRequestMapper.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/Mappers/AvalaraResponseMapper.cs (97%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/Mappers/AvalaraTaxCodeMapper.cs (95%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/Models/AvalaraAddressesModel.cs (95%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/Models/AvalaraCreateTransactionModel.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/Models/AvalaraFetchResult.cs (83%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/Models/AvalaraLineItemModel.cs (95%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/Models/AvalaraTaxCode.cs (94%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/Models/AvalaraTransactionAddressModel.cs (95%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/Models/AvalaraTransactionLineDetailModel.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/Models/AvalaraTransactionLineModel.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Avalara => OrderCloud.Catalyst.Tax.Avalara}/Models/AvalaraTransactionModel.cs (99%) create mode 100644 OrderCloud.Catalyst.Tax.Avalara/OrderCloud.Catalyst.Tax.Avalara.csproj rename {library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar => OrderCloud.Catalyst.Tax.TaxJar}/Mapper/TaxJarCategoryMapper.cs (95%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar => OrderCloud.Catalyst.Tax.TaxJar}/Mapper/TaxJarRequestMapper.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar => OrderCloud.Catalyst.Tax.TaxJar}/Mapper/TaxJarResponseMapper.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar => OrderCloud.Catalyst.Tax.TaxJar}/Models/TaxJarCalculateResponse.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar => OrderCloud.Catalyst.Tax.TaxJar}/Models/TaxJarCategory.cs (89%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar => OrderCloud.Catalyst.Tax.TaxJar}/Models/TaxJarError.cs (83%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar => OrderCloud.Catalyst.Tax.TaxJar}/Models/TaxJarOrder.cs (97%) create mode 100644 OrderCloud.Catalyst.Tax.TaxJar/OrderCloud.Catalyst.Tax.TaxJar.csproj rename {library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar => OrderCloud.Catalyst.Tax.TaxJar}/README.md (100%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar => OrderCloud.Catalyst.Tax.TaxJar}/TaxJarClient.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar => OrderCloud.Catalyst.Tax.TaxJar}/TaxJarCommand.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar => OrderCloud.Catalyst.Tax.TaxJar}/TaxJarConfig.cs (86%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Mappers/VertexRequestMapper.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Mappers/VertexResponseMapper.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Models/VertexCalculateTaxRequest.cs (97%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Models/VertexCalculateTaxResponse.cs (96%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Models/VertexCurrency.cs (86%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Models/VertexCustomer.cs (93%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Models/VertexDiscount.cs (89%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Models/VertexException.cs (90%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Models/VertexJurisdiction.cs (97%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Models/VertexLineItem.cs (97%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Models/VertexLocation.cs (91%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Models/VertexResponseLineItem.cs (99%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Models/VertexSeller.cs (78%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/Models/VertexTokenResponse.cs (85%) create mode 100644 OrderCloud.Catalyst.Tax.Vertex/OrderCloud.Catalyst.Tax.Vertex.csproj rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/README.md (100%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/VertexClient.cs (98%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/VertexCommand.cs (97%) rename {library/OrderCloud.Catalyst/Integrations/Implementations/Vertex => OrderCloud.Catalyst.Tax.Vertex}/VertexConfig.cs (92%) rename {tests/OrderCloud.Catalyst.TestApi => OrderCloud.Catalyst.TestApi}/Commands/ExampleCommand.cs (100%) rename {tests/OrderCloud.Catalyst.TestApi => OrderCloud.Catalyst.TestApi}/Controllers/DemoController.cs (100%) rename {tests/OrderCloud.Catalyst.TestApi => OrderCloud.Catalyst.TestApi}/Controllers/EnvController.cs (100%) rename {tests/OrderCloud.Catalyst.TestApi => OrderCloud.Catalyst.TestApi}/Controllers/ModelValidation/README.md (100%) rename {tests/OrderCloud.Catalyst.TestApi => OrderCloud.Catalyst.TestApi}/Controllers/WebhookController.cs (100%) rename {tests/OrderCloud.Catalyst.TestApi => OrderCloud.Catalyst.TestApi}/OrderCloud.Catalyst.TestApi.csproj (66%) rename {tests/OrderCloud.Catalyst.TestApi => OrderCloud.Catalyst.TestApi}/Program.cs (100%) rename {tests/OrderCloud.Catalyst.TestApi => OrderCloud.Catalyst.TestApi}/Services/LazyCacheService.cs (100%) rename {tests/OrderCloud.Catalyst.TestApi => OrderCloud.Catalyst.TestApi}/Services/RedisCacheService.cs (100%) rename {tests/OrderCloud.Catalyst.TestApi => OrderCloud.Catalyst.TestApi}/Startup.cs (100%) rename {tests/OrderCloud.Catalyst.TestApi => OrderCloud.Catalyst.TestApi}/TestSettings.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/ApiIntegrationTests/DataAnnotationTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/ApiIntegrationTests/GeneralErrorTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/ApiIntegrationTests/ListArgPageOnlyTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/ApiIntegrationTests/ListArgTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/ApiIntegrationTests/SearchArgsTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/ApiIntegrationTests/TokenTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/ApiIntegrationTests/UserAuthTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/ApiIntegrationTests/UserTypeRestrictedTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/ApiIntegrationTests/WebhookAuthTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/AutoNSubstituteDataAttribute.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/DataMovementTests/ListAllAsyncTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/DataMovementTests/ListByIDTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/DataMovementTests/RetryTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/DataMovementTests/ThrottlerTests.cs (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/IntegrationTests/Avalara/AvalaraTests.cs (98%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/IntegrationTests/TaxJar/TaxJarTests.cs (99%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/IntegrationTests/Vertex/VertexTests.cs (99%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/OrderCloud.Catalyst.Tests.csproj (100%) rename {tests/OrderCloud.Catalyst.Tests => OrderCloud.Catalyst.Tests}/TestFramework.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Api/CatalystController.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/AssemblyInfo.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Auth/UserAuth/DecodedToken.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Auth/UserAuth/FakeOrderCloudToken.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Auth/UserAuth/OrderCloudUserAuth.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Auth/UserAuth/README.md (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Auth/UserAuth/RequestAuthenticationService.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Auth/UserAuth/UserTypeRestrictedToAttribute.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Auth/WebhookAuth/OrderCloudWebhookAuth.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Auth/WebhookAuth/README.md (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/DataMovement/Caching/ISimpleCache.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/DataMovement/Caching/LazyCacheService.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/DataMovement/Caching/README.md (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/DataMovement/Differ.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/DataMovement/ListAllAsync/ListAllHelper.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/DataMovement/ListAllAsync/README.md (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/DataMovement/RetryPolicy.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/DataMovement/Throttler/README.md (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/DataMovement/Throttler/Throttler.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Errors/CatalystBaseException.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Errors/ErrorCode.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Errors/Exceptions.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Errors/GlobalExceptionHandler.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Errors/README.md (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Errors/Require.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Errors/ValidateModel.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Extensions/ExtensionMethods.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Generated/ListAllExtensions.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Generated/ListByIDExtensions.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Generated/ListExtensions.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Integrations/CONTRIBUTING.md (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Integrations/Exceptions/IntegrationAuthFailedException.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Integrations/Exceptions/IntegrationErrorResponseException.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Integrations/Exceptions/IntegrationMissingConfigsException.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Integrations/Exceptions/IntegrationNoResponseException.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Integrations/Interfaces/ITaxCalculator.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Integrations/Interfaces/ITaxCodeProvider.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Integrations/OCIntegrationCommand.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Integrations/OCIntegrationConfig.cs (100%) rename {library/OrderCloud.Catalyst/Integrations/Implementations => OrderCloud.Catalyst/Integrations}/README.md (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Jobs/BaseJob.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Models/ListOptions/ListArgs.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Models/ListOptions/ListArgsBinder.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Models/ListOptions/ListArgsPageOnly.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Models/ListOptions/ListFilter.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Models/ListOptions/README.md (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Models/ListOptions/SearchArgs.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Models/Webhooks/OpenIDConnectUserPayload.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Models/Webhooks/OrderCalculatePayload.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/Models/Webhooks/PreWebhookResponse.cs (100%) rename {library/OrderCloud.Catalyst => OrderCloud.Catalyst}/OrderCloud.Catalyst.csproj (96%) diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs b/OrderCloud.Catalyst.Tax.Avalara/AvalaraClient.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs rename to OrderCloud.Catalyst.Tax.Avalara/AvalaraClient.cs index c6c8f63..6892f68 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraClient.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/AvalaraClient.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public class AvalaraClient { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs b/OrderCloud.Catalyst.Tax.Avalara/AvalaraCommand.cs similarity index 97% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs rename to OrderCloud.Catalyst.Tax.Avalara/AvalaraCommand.cs index 323316c..210301b 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraCommand.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/AvalaraCommand.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public class AvalaraCommand : OCIntegrationCommand , ITaxCodesProvider, ITaxCalculator { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraConfig.cs b/OrderCloud.Catalyst.Tax.Avalara/AvalaraConfig.cs similarity index 91% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraConfig.cs rename to OrderCloud.Catalyst.Tax.Avalara/AvalaraConfig.cs index b4fe0fc..dcaf333 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/AvalaraConfig.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/AvalaraConfig.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public class AvalaraConfig: OCIntegrationConfig { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraRequestMapper.cs b/OrderCloud.Catalyst.Tax.Avalara/Mappers/AvalaraRequestMapper.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraRequestMapper.cs rename to OrderCloud.Catalyst.Tax.Avalara/Mappers/AvalaraRequestMapper.cs index 81ed1a9..d049ce7 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraRequestMapper.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/Mappers/AvalaraRequestMapper.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public static class AvalaraRequestMapper { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraResponseMapper.cs b/OrderCloud.Catalyst.Tax.Avalara/Mappers/AvalaraResponseMapper.cs similarity index 97% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraResponseMapper.cs rename to OrderCloud.Catalyst.Tax.Avalara/Mappers/AvalaraResponseMapper.cs index baee8bd..2cb7c46 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraResponseMapper.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/Mappers/AvalaraResponseMapper.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public static class AvalaraResponseMapper { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraTaxCodeMapper.cs b/OrderCloud.Catalyst.Tax.Avalara/Mappers/AvalaraTaxCodeMapper.cs similarity index 95% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraTaxCodeMapper.cs rename to OrderCloud.Catalyst.Tax.Avalara/Mappers/AvalaraTaxCodeMapper.cs index 2e9fa27..0cdbb9b 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Mappers/AvalaraTaxCodeMapper.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/Mappers/AvalaraTaxCodeMapper.cs @@ -4,7 +4,7 @@ using OrderCloud.Catalyst; using System.Collections.Generic; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public static class AvalaraTaxCodeMapper { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraAddressesModel.cs b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraAddressesModel.cs similarity index 95% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraAddressesModel.cs rename to OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraAddressesModel.cs index 7d6c03b..5f3faf0 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraAddressesModel.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraAddressesModel.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public class AvalaraAddressesModel { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraCreateTransactionModel.cs b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraCreateTransactionModel.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraCreateTransactionModel.cs rename to OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraCreateTransactionModel.cs index 79ac308..09e6214 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraCreateTransactionModel.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraCreateTransactionModel.cs @@ -3,7 +3,7 @@ using System.Text; using System.Text.Json.Serialization; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { [JsonConverter(typeof(JsonStringEnumConverter))] public enum AvalaraDocumentType diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraFetchResult.cs b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraFetchResult.cs similarity index 83% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraFetchResult.cs rename to OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraFetchResult.cs index becade1..85741ea 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraFetchResult.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraFetchResult.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public class AvalaraFetchResult { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraLineItemModel.cs b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraLineItemModel.cs similarity index 95% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraLineItemModel.cs rename to OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraLineItemModel.cs index a1c27ab..502e2d8 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraLineItemModel.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraLineItemModel.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public class AvalaraLineItemModel { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTaxCode.cs b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTaxCode.cs similarity index 94% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTaxCode.cs rename to OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTaxCode.cs index b7c8d92..f0b9cf7 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTaxCode.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTaxCode.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public class AvalaraTaxCode { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionAddressModel.cs b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTransactionAddressModel.cs similarity index 95% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionAddressModel.cs rename to OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTransactionAddressModel.cs index eaeeccf..c3f0b1c 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionAddressModel.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTransactionAddressModel.cs @@ -3,7 +3,7 @@ using System.Text; using System.Text.Json.Serialization; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public class AvalaraTransactionAddressModel { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineDetailModel.cs b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTransactionLineDetailModel.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineDetailModel.cs rename to OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTransactionLineDetailModel.cs index d01ade7..651dd51 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineDetailModel.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTransactionLineDetailModel.cs @@ -3,7 +3,7 @@ using System.Text; using System.Text.Json.Serialization; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public class AvalaraTransactionLineDetailModel { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineModel.cs b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTransactionLineModel.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineModel.cs rename to OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTransactionLineModel.cs index 71b1a92..1674f7d 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionLineModel.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTransactionLineModel.cs @@ -3,7 +3,7 @@ using System.Text; using System.Text.Json.Serialization; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public class AvalaraTransactionLineModel { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionModel.cs b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTransactionModel.cs similarity index 99% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionModel.cs rename to OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTransactionModel.cs index 16d16b4..c0c2914 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Avalara/Models/AvalaraTransactionModel.cs +++ b/OrderCloud.Catalyst.Tax.Avalara/Models/AvalaraTransactionModel.cs @@ -3,7 +3,7 @@ using System.Text; using System.Text.Json.Serialization; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Avalara { public class AvalaraTransactionModel { diff --git a/OrderCloud.Catalyst.Tax.Avalara/OrderCloud.Catalyst.Tax.Avalara.csproj b/OrderCloud.Catalyst.Tax.Avalara/OrderCloud.Catalyst.Tax.Avalara.csproj new file mode 100644 index 0000000..d1700eb --- /dev/null +++ b/OrderCloud.Catalyst.Tax.Avalara/OrderCloud.Catalyst.Tax.Avalara.csproj @@ -0,0 +1,30 @@ + + + + netstandard2.0 + True + 1.5.1 + OrderCloud.Catalyst.Tax.Avalara + OrderCloud Tax Integration with Avalara + Oliver Heywood + Integrate the OrderCloud ecommerce platform with Avalara for tax calculation. + Copyright 2021 Four51, Inc. + https://github.com/ordercloud-api/ordercloud-dotnet-catalyst + + ordercloud-logo-blue.png + ecommerce b2b azure aspnetcore webjobs four51 ordercloud + + https://github.com/ordercloud-api/ordercloud-dotnet-catalyst + git + Four51 Inc + OrderCloud + MIT + + + + + + + + + diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarCategoryMapper.cs b/OrderCloud.Catalyst.Tax.TaxJar/Mapper/TaxJarCategoryMapper.cs similarity index 95% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarCategoryMapper.cs rename to OrderCloud.Catalyst.Tax.TaxJar/Mapper/TaxJarCategoryMapper.cs index 29d7e71..e8ffa8a 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarCategoryMapper.cs +++ b/OrderCloud.Catalyst.Tax.TaxJar/Mapper/TaxJarCategoryMapper.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.TaxJar { public static class TaxJarCategoryMapper { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs b/OrderCloud.Catalyst.Tax.TaxJar/Mapper/TaxJarRequestMapper.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs rename to OrderCloud.Catalyst.Tax.TaxJar/Mapper/TaxJarRequestMapper.cs index 6eefd37..f98973a 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarRequestMapper.cs +++ b/OrderCloud.Catalyst.Tax.TaxJar/Mapper/TaxJarRequestMapper.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.TaxJar { public static class TaxJarRequestMapper { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarResponseMapper.cs b/OrderCloud.Catalyst.Tax.TaxJar/Mapper/TaxJarResponseMapper.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarResponseMapper.cs rename to OrderCloud.Catalyst.Tax.TaxJar/Mapper/TaxJarResponseMapper.cs index 2be7678..c150fca 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Mapper/TaxJarResponseMapper.cs +++ b/OrderCloud.Catalyst.Tax.TaxJar/Mapper/TaxJarResponseMapper.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.TaxJar { public static class TaxJarResponseMapper { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCalculateResponse.cs b/OrderCloud.Catalyst.Tax.TaxJar/Models/TaxJarCalculateResponse.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCalculateResponse.cs rename to OrderCloud.Catalyst.Tax.TaxJar/Models/TaxJarCalculateResponse.cs index e07e4e4..055179f 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCalculateResponse.cs +++ b/OrderCloud.Catalyst.Tax.TaxJar/Models/TaxJarCalculateResponse.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.TaxJar { public class TaxJarCalcResponse { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCategory.cs b/OrderCloud.Catalyst.Tax.TaxJar/Models/TaxJarCategory.cs similarity index 89% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCategory.cs rename to OrderCloud.Catalyst.Tax.TaxJar/Models/TaxJarCategory.cs index 85c44d5..e7d1dbe 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarCategory.cs +++ b/OrderCloud.Catalyst.Tax.TaxJar/Models/TaxJarCategory.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.TaxJar { public class TaxJarCategories { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarError.cs b/OrderCloud.Catalyst.Tax.TaxJar/Models/TaxJarError.cs similarity index 83% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarError.cs rename to OrderCloud.Catalyst.Tax.TaxJar/Models/TaxJarError.cs index 89b5fda..b8b71e8 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarError.cs +++ b/OrderCloud.Catalyst.Tax.TaxJar/Models/TaxJarError.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.TaxJar { public class TaxJarError { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarOrder.cs b/OrderCloud.Catalyst.Tax.TaxJar/Models/TaxJarOrder.cs similarity index 97% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarOrder.cs rename to OrderCloud.Catalyst.Tax.TaxJar/Models/TaxJarOrder.cs index fa875fc..9e65c91 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/Models/TaxJarOrder.cs +++ b/OrderCloud.Catalyst.Tax.TaxJar/Models/TaxJarOrder.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.TaxJar { public class TaxJarOrder { diff --git a/OrderCloud.Catalyst.Tax.TaxJar/OrderCloud.Catalyst.Tax.TaxJar.csproj b/OrderCloud.Catalyst.Tax.TaxJar/OrderCloud.Catalyst.Tax.TaxJar.csproj new file mode 100644 index 0000000..3880675 --- /dev/null +++ b/OrderCloud.Catalyst.Tax.TaxJar/OrderCloud.Catalyst.Tax.TaxJar.csproj @@ -0,0 +1,30 @@ + + + + netstandard2.0 + True + 1.5.1 + OrderCloud.Catalyst.Tax.TaxJar + OrderCloud Tax Integration with TaxJar + Oliver Heywood + Integrate the OrderCloud ecommerce platform with TaxJar for tax calculation. + Copyright 2021 Four51, Inc. + https://github.com/ordercloud-api/ordercloud-dotnet-catalyst + + ordercloud-logo-blue.png + ecommerce b2b azure aspnetcore webjobs four51 ordercloud + + https://github.com/ordercloud-api/ordercloud-dotnet-catalyst + git + Four51 Inc + OrderCloud + MIT + + + + + + + + + diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/README.md b/OrderCloud.Catalyst.Tax.TaxJar/README.md similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/README.md rename to OrderCloud.Catalyst.Tax.TaxJar/README.md diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs b/OrderCloud.Catalyst.Tax.TaxJar/TaxJarClient.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs rename to OrderCloud.Catalyst.Tax.TaxJar/TaxJarClient.cs index 300faa3..ad04bdc 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarClient.cs +++ b/OrderCloud.Catalyst.Tax.TaxJar/TaxJarClient.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.TaxJar { public class TaxJarClient { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarCommand.cs b/OrderCloud.Catalyst.Tax.TaxJar/TaxJarCommand.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarCommand.cs rename to OrderCloud.Catalyst.Tax.TaxJar/TaxJarCommand.cs index ab5f644..9a30dcc 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarCommand.cs +++ b/OrderCloud.Catalyst.Tax.TaxJar/TaxJarCommand.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading.Tasks; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.TaxJar { public class TaxJarCommand : OCIntegrationCommand, ITaxCalculator, ITaxCodesProvider { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarConfig.cs b/OrderCloud.Catalyst.Tax.TaxJar/TaxJarConfig.cs similarity index 86% rename from library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarConfig.cs rename to OrderCloud.Catalyst.Tax.TaxJar/TaxJarConfig.cs index 63a947d..9a0011c 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/TaxJar/TaxJarConfig.cs +++ b/OrderCloud.Catalyst.Tax.TaxJar/TaxJarConfig.cs @@ -1,6 +1,6 @@ using System; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.TaxJar { public class TaxJarConfig : OCIntegrationConfig { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs b/OrderCloud.Catalyst.Tax.Vertex/Mappers/VertexRequestMapper.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs rename to OrderCloud.Catalyst.Tax.Vertex/Mappers/VertexRequestMapper.cs index 147b8ce..dd944a4 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexRequestMapper.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Mappers/VertexRequestMapper.cs @@ -6,7 +6,7 @@ using System.Net; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public static class VertexRequestMapper { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs b/OrderCloud.Catalyst.Tax.Vertex/Mappers/VertexResponseMapper.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs rename to OrderCloud.Catalyst.Tax.Vertex/Mappers/VertexResponseMapper.cs index 907eec5..2c0d202 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Mappers/VertexResponseMapper.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Mappers/VertexResponseMapper.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public static class VertexResponseMapper { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxRequest.cs b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexCalculateTaxRequest.cs similarity index 97% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxRequest.cs rename to OrderCloud.Catalyst.Tax.Vertex/Models/VertexCalculateTaxRequest.cs index 9f5ecd3..07fa860 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxRequest.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexCalculateTaxRequest.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexCalculateTaxRequest { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxResponse.cs b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexCalculateTaxResponse.cs similarity index 96% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxResponse.cs rename to OrderCloud.Catalyst.Tax.Vertex/Models/VertexCalculateTaxResponse.cs index 3429fb0..2e953ec 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCalculateTaxResponse.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexCalculateTaxResponse.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexResponse { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCurrency.cs b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexCurrency.cs similarity index 86% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCurrency.cs rename to OrderCloud.Catalyst.Tax.Vertex/Models/VertexCurrency.cs index 1c18678..a2c5c3c 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCurrency.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexCurrency.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexCurrency { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCustomer.cs b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexCustomer.cs similarity index 93% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCustomer.cs rename to OrderCloud.Catalyst.Tax.Vertex/Models/VertexCustomer.cs index ce815f5..b38a58e 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexCustomer.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexCustomer.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexCustomer { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexDiscount.cs b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexDiscount.cs similarity index 89% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexDiscount.cs rename to OrderCloud.Catalyst.Tax.Vertex/Models/VertexDiscount.cs index 77cfa49..808c54f 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexDiscount.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexDiscount.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexDiscount { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexException.cs b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexException.cs similarity index 90% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexException.cs rename to OrderCloud.Catalyst.Tax.Vertex/Models/VertexException.cs index 9039575..3cf8e4c 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexException.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexException.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexException : CatalystBaseException { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexJurisdiction.cs b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexJurisdiction.cs similarity index 97% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexJurisdiction.cs rename to OrderCloud.Catalyst.Tax.Vertex/Models/VertexJurisdiction.cs index 4ea12a3..476a93e 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexJurisdiction.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexJurisdiction.cs @@ -1,6 +1,6 @@ using System; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexJurisdiction { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLineItem.cs b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexLineItem.cs similarity index 97% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLineItem.cs rename to OrderCloud.Catalyst.Tax.Vertex/Models/VertexLineItem.cs index c9cb472..9af3818 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLineItem.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexLineItem.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexLineItem { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLocation.cs b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexLocation.cs similarity index 91% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLocation.cs rename to OrderCloud.Catalyst.Tax.Vertex/Models/VertexLocation.cs index 464751a..52aee8f 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexLocation.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexLocation.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexLocation { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexResponseLineItem.cs b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexResponseLineItem.cs similarity index 99% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexResponseLineItem.cs rename to OrderCloud.Catalyst.Tax.Vertex/Models/VertexResponseLineItem.cs index 097a785..31833c5 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexResponseLineItem.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexResponseLineItem.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexResponseLineItem : VertexLineItem { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexSeller.cs b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexSeller.cs similarity index 78% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexSeller.cs rename to OrderCloud.Catalyst.Tax.Vertex/Models/VertexSeller.cs index 397230c..410de2b 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexSeller.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexSeller.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexSeller { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexTokenResponse.cs b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexTokenResponse.cs similarity index 85% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexTokenResponse.cs rename to OrderCloud.Catalyst.Tax.Vertex/Models/VertexTokenResponse.cs index 7f0ca8c..a0b5f92 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/Models/VertexTokenResponse.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/Models/VertexTokenResponse.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexTokenResponse { diff --git a/OrderCloud.Catalyst.Tax.Vertex/OrderCloud.Catalyst.Tax.Vertex.csproj b/OrderCloud.Catalyst.Tax.Vertex/OrderCloud.Catalyst.Tax.Vertex.csproj new file mode 100644 index 0000000..93d1783 --- /dev/null +++ b/OrderCloud.Catalyst.Tax.Vertex/OrderCloud.Catalyst.Tax.Vertex.csproj @@ -0,0 +1,31 @@ + + + + netstandard2.0 + True + 1.5.1 + OrderCloud.Catalyst.Tax.Vertex + OrderCloud Tax Integration with Vertex + Oliver Heywood + Integrate the OrderCloud ecommerce platform with Vertex for tax calculation. + Copyright 2021 Four51, Inc. + https://github.com/ordercloud-api/ordercloud-dotnet-catalyst + + ordercloud-logo-blue.png + ecommerce b2b azure aspnetcore webjobs four51 ordercloud + + https://github.com/ordercloud-api/ordercloud-dotnet-catalyst + git + Four51 Inc + OrderCloud + MIT + + + + + + + + + + diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md b/OrderCloud.Catalyst.Tax.Vertex/README.md similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/README.md rename to OrderCloud.Catalyst.Tax.Vertex/README.md diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs b/OrderCloud.Catalyst.Tax.Vertex/VertexClient.cs similarity index 98% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs rename to OrderCloud.Catalyst.Tax.Vertex/VertexClient.cs index 6ee2a46..9b06ddf 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexClient.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/VertexClient.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using System.Net; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexClient { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexCommand.cs b/OrderCloud.Catalyst.Tax.Vertex/VertexCommand.cs similarity index 97% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexCommand.cs rename to OrderCloud.Catalyst.Tax.Vertex/VertexCommand.cs index d2ad200..1e3214e 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexCommand.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/VertexCommand.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using OrderCloud.SDK; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexCommand : OCIntegrationCommand, ITaxCalculator { diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexConfig.cs b/OrderCloud.Catalyst.Tax.Vertex/VertexConfig.cs similarity index 92% rename from library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexConfig.cs rename to OrderCloud.Catalyst.Tax.Vertex/VertexConfig.cs index 6ff659b..ca6f5bc 100644 --- a/library/OrderCloud.Catalyst/Integrations/Implementations/Vertex/VertexConfig.cs +++ b/OrderCloud.Catalyst.Tax.Vertex/VertexConfig.cs @@ -1,6 +1,6 @@ using System; -namespace OrderCloud.Catalyst +namespace OrderCloud.Catalyst.Tax.Vertex { public class VertexConfig : OCIntegrationConfig { diff --git a/tests/OrderCloud.Catalyst.TestApi/Commands/ExampleCommand.cs b/OrderCloud.Catalyst.TestApi/Commands/ExampleCommand.cs similarity index 100% rename from tests/OrderCloud.Catalyst.TestApi/Commands/ExampleCommand.cs rename to OrderCloud.Catalyst.TestApi/Commands/ExampleCommand.cs diff --git a/tests/OrderCloud.Catalyst.TestApi/Controllers/DemoController.cs b/OrderCloud.Catalyst.TestApi/Controllers/DemoController.cs similarity index 100% rename from tests/OrderCloud.Catalyst.TestApi/Controllers/DemoController.cs rename to OrderCloud.Catalyst.TestApi/Controllers/DemoController.cs diff --git a/tests/OrderCloud.Catalyst.TestApi/Controllers/EnvController.cs b/OrderCloud.Catalyst.TestApi/Controllers/EnvController.cs similarity index 100% rename from tests/OrderCloud.Catalyst.TestApi/Controllers/EnvController.cs rename to OrderCloud.Catalyst.TestApi/Controllers/EnvController.cs diff --git a/tests/OrderCloud.Catalyst.TestApi/Controllers/ModelValidation/README.md b/OrderCloud.Catalyst.TestApi/Controllers/ModelValidation/README.md similarity index 100% rename from tests/OrderCloud.Catalyst.TestApi/Controllers/ModelValidation/README.md rename to OrderCloud.Catalyst.TestApi/Controllers/ModelValidation/README.md diff --git a/tests/OrderCloud.Catalyst.TestApi/Controllers/WebhookController.cs b/OrderCloud.Catalyst.TestApi/Controllers/WebhookController.cs similarity index 100% rename from tests/OrderCloud.Catalyst.TestApi/Controllers/WebhookController.cs rename to OrderCloud.Catalyst.TestApi/Controllers/WebhookController.cs diff --git a/tests/OrderCloud.Catalyst.TestApi/OrderCloud.Catalyst.TestApi.csproj b/OrderCloud.Catalyst.TestApi/OrderCloud.Catalyst.TestApi.csproj similarity index 66% rename from tests/OrderCloud.Catalyst.TestApi/OrderCloud.Catalyst.TestApi.csproj rename to OrderCloud.Catalyst.TestApi/OrderCloud.Catalyst.TestApi.csproj index 0efee45..a4f729f 100644 --- a/tests/OrderCloud.Catalyst.TestApi/OrderCloud.Catalyst.TestApi.csproj +++ b/OrderCloud.Catalyst.TestApi/OrderCloud.Catalyst.TestApi.csproj @@ -21,7 +21,10 @@ - + + + + diff --git a/tests/OrderCloud.Catalyst.TestApi/Program.cs b/OrderCloud.Catalyst.TestApi/Program.cs similarity index 100% rename from tests/OrderCloud.Catalyst.TestApi/Program.cs rename to OrderCloud.Catalyst.TestApi/Program.cs diff --git a/tests/OrderCloud.Catalyst.TestApi/Services/LazyCacheService.cs b/OrderCloud.Catalyst.TestApi/Services/LazyCacheService.cs similarity index 100% rename from tests/OrderCloud.Catalyst.TestApi/Services/LazyCacheService.cs rename to OrderCloud.Catalyst.TestApi/Services/LazyCacheService.cs diff --git a/tests/OrderCloud.Catalyst.TestApi/Services/RedisCacheService.cs b/OrderCloud.Catalyst.TestApi/Services/RedisCacheService.cs similarity index 100% rename from tests/OrderCloud.Catalyst.TestApi/Services/RedisCacheService.cs rename to OrderCloud.Catalyst.TestApi/Services/RedisCacheService.cs diff --git a/tests/OrderCloud.Catalyst.TestApi/Startup.cs b/OrderCloud.Catalyst.TestApi/Startup.cs similarity index 100% rename from tests/OrderCloud.Catalyst.TestApi/Startup.cs rename to OrderCloud.Catalyst.TestApi/Startup.cs diff --git a/tests/OrderCloud.Catalyst.TestApi/TestSettings.cs b/OrderCloud.Catalyst.TestApi/TestSettings.cs similarity index 100% rename from tests/OrderCloud.Catalyst.TestApi/TestSettings.cs rename to OrderCloud.Catalyst.TestApi/TestSettings.cs diff --git a/tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/DataAnnotationTests.cs b/OrderCloud.Catalyst.Tests/ApiIntegrationTests/DataAnnotationTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/DataAnnotationTests.cs rename to OrderCloud.Catalyst.Tests/ApiIntegrationTests/DataAnnotationTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/GeneralErrorTests.cs b/OrderCloud.Catalyst.Tests/ApiIntegrationTests/GeneralErrorTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/GeneralErrorTests.cs rename to OrderCloud.Catalyst.Tests/ApiIntegrationTests/GeneralErrorTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/ListArgPageOnlyTests.cs b/OrderCloud.Catalyst.Tests/ApiIntegrationTests/ListArgPageOnlyTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/ListArgPageOnlyTests.cs rename to OrderCloud.Catalyst.Tests/ApiIntegrationTests/ListArgPageOnlyTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/ListArgTests.cs b/OrderCloud.Catalyst.Tests/ApiIntegrationTests/ListArgTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/ListArgTests.cs rename to OrderCloud.Catalyst.Tests/ApiIntegrationTests/ListArgTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/SearchArgsTests.cs b/OrderCloud.Catalyst.Tests/ApiIntegrationTests/SearchArgsTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/SearchArgsTests.cs rename to OrderCloud.Catalyst.Tests/ApiIntegrationTests/SearchArgsTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/TokenTests.cs b/OrderCloud.Catalyst.Tests/ApiIntegrationTests/TokenTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/TokenTests.cs rename to OrderCloud.Catalyst.Tests/ApiIntegrationTests/TokenTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/UserAuthTests.cs b/OrderCloud.Catalyst.Tests/ApiIntegrationTests/UserAuthTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/UserAuthTests.cs rename to OrderCloud.Catalyst.Tests/ApiIntegrationTests/UserAuthTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/UserTypeRestrictedTests.cs b/OrderCloud.Catalyst.Tests/ApiIntegrationTests/UserTypeRestrictedTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/UserTypeRestrictedTests.cs rename to OrderCloud.Catalyst.Tests/ApiIntegrationTests/UserTypeRestrictedTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/WebhookAuthTests.cs b/OrderCloud.Catalyst.Tests/ApiIntegrationTests/WebhookAuthTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/ApiIntegrationTests/WebhookAuthTests.cs rename to OrderCloud.Catalyst.Tests/ApiIntegrationTests/WebhookAuthTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/AutoNSubstituteDataAttribute.cs b/OrderCloud.Catalyst.Tests/AutoNSubstituteDataAttribute.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/AutoNSubstituteDataAttribute.cs rename to OrderCloud.Catalyst.Tests/AutoNSubstituteDataAttribute.cs diff --git a/tests/OrderCloud.Catalyst.Tests/DataMovementTests/ListAllAsyncTests.cs b/OrderCloud.Catalyst.Tests/DataMovementTests/ListAllAsyncTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/DataMovementTests/ListAllAsyncTests.cs rename to OrderCloud.Catalyst.Tests/DataMovementTests/ListAllAsyncTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/DataMovementTests/ListByIDTests.cs b/OrderCloud.Catalyst.Tests/DataMovementTests/ListByIDTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/DataMovementTests/ListByIDTests.cs rename to OrderCloud.Catalyst.Tests/DataMovementTests/ListByIDTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/DataMovementTests/RetryTests.cs b/OrderCloud.Catalyst.Tests/DataMovementTests/RetryTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/DataMovementTests/RetryTests.cs rename to OrderCloud.Catalyst.Tests/DataMovementTests/RetryTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/DataMovementTests/ThrottlerTests.cs b/OrderCloud.Catalyst.Tests/DataMovementTests/ThrottlerTests.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/DataMovementTests/ThrottlerTests.cs rename to OrderCloud.Catalyst.Tests/DataMovementTests/ThrottlerTests.cs diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Avalara/AvalaraTests.cs b/OrderCloud.Catalyst.Tests/IntegrationTests/Avalara/AvalaraTests.cs similarity index 98% rename from tests/OrderCloud.Catalyst.Tests/IntegrationTests/Avalara/AvalaraTests.cs rename to OrderCloud.Catalyst.Tests/IntegrationTests/Avalara/AvalaraTests.cs index ce3d997..c22df63 100644 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Avalara/AvalaraTests.cs +++ b/OrderCloud.Catalyst.Tests/IntegrationTests/Avalara/AvalaraTests.cs @@ -1,6 +1,7 @@ using AutoFixture; using Flurl.Http.Testing; using NUnit.Framework; +using OrderCloud.Catalyst.Tax.Avalara; using System; using System.Collections.Generic; using System.Text; diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs b/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs similarity index 99% rename from tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs rename to OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs index a6dff5e..e803f82 100644 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs +++ b/OrderCloud.Catalyst.Tests/IntegrationTests/TaxJar/TaxJarTests.cs @@ -5,6 +5,7 @@ using AutoFixture; using Flurl.Http.Testing; using NUnit.Framework; +using OrderCloud.Catalyst.Tax.TaxJar; using OrderCloud.SDK; namespace OrderCloud.Catalyst.Tests diff --git a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs b/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs similarity index 99% rename from tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs rename to OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs index fa5ebb6..18cf980 100644 --- a/tests/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs +++ b/OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs @@ -5,6 +5,7 @@ using AutoFixture; using Flurl.Http.Testing; using NUnit.Framework; +using OrderCloud.Catalyst.Tax.Vertex; using OrderCloud.SDK; namespace OrderCloud.Catalyst.Tests diff --git a/tests/OrderCloud.Catalyst.Tests/OrderCloud.Catalyst.Tests.csproj b/OrderCloud.Catalyst.Tests/OrderCloud.Catalyst.Tests.csproj similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/OrderCloud.Catalyst.Tests.csproj rename to OrderCloud.Catalyst.Tests/OrderCloud.Catalyst.Tests.csproj diff --git a/tests/OrderCloud.Catalyst.Tests/TestFramework.cs b/OrderCloud.Catalyst.Tests/TestFramework.cs similarity index 100% rename from tests/OrderCloud.Catalyst.Tests/TestFramework.cs rename to OrderCloud.Catalyst.Tests/TestFramework.cs diff --git a/OrderCloud.Catalyst.sln b/OrderCloud.Catalyst.sln index 67d23f0..ca697de 100644 --- a/OrderCloud.Catalyst.sln +++ b/OrderCloud.Catalyst.sln @@ -5,13 +5,19 @@ VisualStudioVersion = 16.0.30907.101 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8B971560-E70A-427B-98EF-A65E7C1B67B0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrderCloud.Catalyst.TestApi", "tests\OrderCloud.Catalyst.TestApi\OrderCloud.Catalyst.TestApi.csproj", "{4EC1404C-7DDD-488E-9977-FEA3B44A6E22}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrderCloud.Catalyst.TestApi", "OrderCloud.Catalyst.TestApi\OrderCloud.Catalyst.TestApi.csproj", "{4EC1404C-7DDD-488E-9977-FEA3B44A6E22}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "library", "library", "{04D2632E-8BB1-449B-9E2F-751295F5C2EF}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libraries", "libraries", "{04D2632E-8BB1-449B-9E2F-751295F5C2EF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrderCloud.Catalyst.Tests", "tests\OrderCloud.Catalyst.Tests\OrderCloud.Catalyst.Tests.csproj", "{BE648C2F-33BD-4C6E-AD1E-87FE9DCBB7CB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrderCloud.Catalyst.Tests", "OrderCloud.Catalyst.Tests\OrderCloud.Catalyst.Tests.csproj", "{BE648C2F-33BD-4C6E-AD1E-87FE9DCBB7CB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderCloud.Catalyst", "library\OrderCloud.Catalyst\OrderCloud.Catalyst.csproj", "{6FF92BF8-491D-4F7F-8104-440387572E3C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrderCloud.Catalyst", "OrderCloud.Catalyst\OrderCloud.Catalyst.csproj", "{6FF92BF8-491D-4F7F-8104-440387572E3C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrderCloud.Catalyst.Tax.Avalara", "OrderCloud.Catalyst.Tax.Avalara\OrderCloud.Catalyst.Tax.Avalara.csproj", "{4F5B79DC-8C4F-4EE1-BCE3-2C7887584ABD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderCloud.Catalyst.Tax.Vertex", "OrderCloud.Catalyst.Tax.Vertex\OrderCloud.Catalyst.Tax.Vertex.csproj", "{85191118-5B5C-4BCE-BB27-CF6FF3760BAD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderCloud.Catalyst.Tax.TaxJar", "OrderCloud.Catalyst.Tax.TaxJar\OrderCloud.Catalyst.Tax.TaxJar.csproj", "{CFA2757C-D155-4225-85FD-ED2AD3DB3E0C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -31,6 +37,18 @@ Global {6FF92BF8-491D-4F7F-8104-440387572E3C}.Debug|Any CPU.Build.0 = Debug|Any CPU {6FF92BF8-491D-4F7F-8104-440387572E3C}.Release|Any CPU.ActiveCfg = Release|Any CPU {6FF92BF8-491D-4F7F-8104-440387572E3C}.Release|Any CPU.Build.0 = Release|Any CPU + {4F5B79DC-8C4F-4EE1-BCE3-2C7887584ABD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F5B79DC-8C4F-4EE1-BCE3-2C7887584ABD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F5B79DC-8C4F-4EE1-BCE3-2C7887584ABD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F5B79DC-8C4F-4EE1-BCE3-2C7887584ABD}.Release|Any CPU.Build.0 = Release|Any CPU + {85191118-5B5C-4BCE-BB27-CF6FF3760BAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85191118-5B5C-4BCE-BB27-CF6FF3760BAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85191118-5B5C-4BCE-BB27-CF6FF3760BAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85191118-5B5C-4BCE-BB27-CF6FF3760BAD}.Release|Any CPU.Build.0 = Release|Any CPU + {CFA2757C-D155-4225-85FD-ED2AD3DB3E0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFA2757C-D155-4225-85FD-ED2AD3DB3E0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFA2757C-D155-4225-85FD-ED2AD3DB3E0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFA2757C-D155-4225-85FD-ED2AD3DB3E0C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -39,6 +57,9 @@ Global {4EC1404C-7DDD-488E-9977-FEA3B44A6E22} = {8B971560-E70A-427B-98EF-A65E7C1B67B0} {BE648C2F-33BD-4C6E-AD1E-87FE9DCBB7CB} = {8B971560-E70A-427B-98EF-A65E7C1B67B0} {6FF92BF8-491D-4F7F-8104-440387572E3C} = {04D2632E-8BB1-449B-9E2F-751295F5C2EF} + {4F5B79DC-8C4F-4EE1-BCE3-2C7887584ABD} = {04D2632E-8BB1-449B-9E2F-751295F5C2EF} + {85191118-5B5C-4BCE-BB27-CF6FF3760BAD} = {04D2632E-8BB1-449B-9E2F-751295F5C2EF} + {CFA2757C-D155-4225-85FD-ED2AD3DB3E0C} = {04D2632E-8BB1-449B-9E2F-751295F5C2EF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A5996C63-5092-41D0-9499-2B3B330BA82E} diff --git a/library/OrderCloud.Catalyst/Api/CatalystController.cs b/OrderCloud.Catalyst/Api/CatalystController.cs similarity index 100% rename from library/OrderCloud.Catalyst/Api/CatalystController.cs rename to OrderCloud.Catalyst/Api/CatalystController.cs diff --git a/library/OrderCloud.Catalyst/AssemblyInfo.cs b/OrderCloud.Catalyst/AssemblyInfo.cs similarity index 100% rename from library/OrderCloud.Catalyst/AssemblyInfo.cs rename to OrderCloud.Catalyst/AssemblyInfo.cs diff --git a/library/OrderCloud.Catalyst/Auth/UserAuth/DecodedToken.cs b/OrderCloud.Catalyst/Auth/UserAuth/DecodedToken.cs similarity index 100% rename from library/OrderCloud.Catalyst/Auth/UserAuth/DecodedToken.cs rename to OrderCloud.Catalyst/Auth/UserAuth/DecodedToken.cs diff --git a/library/OrderCloud.Catalyst/Auth/UserAuth/FakeOrderCloudToken.cs b/OrderCloud.Catalyst/Auth/UserAuth/FakeOrderCloudToken.cs similarity index 100% rename from library/OrderCloud.Catalyst/Auth/UserAuth/FakeOrderCloudToken.cs rename to OrderCloud.Catalyst/Auth/UserAuth/FakeOrderCloudToken.cs diff --git a/library/OrderCloud.Catalyst/Auth/UserAuth/OrderCloudUserAuth.cs b/OrderCloud.Catalyst/Auth/UserAuth/OrderCloudUserAuth.cs similarity index 100% rename from library/OrderCloud.Catalyst/Auth/UserAuth/OrderCloudUserAuth.cs rename to OrderCloud.Catalyst/Auth/UserAuth/OrderCloudUserAuth.cs diff --git a/library/OrderCloud.Catalyst/Auth/UserAuth/README.md b/OrderCloud.Catalyst/Auth/UserAuth/README.md similarity index 100% rename from library/OrderCloud.Catalyst/Auth/UserAuth/README.md rename to OrderCloud.Catalyst/Auth/UserAuth/README.md diff --git a/library/OrderCloud.Catalyst/Auth/UserAuth/RequestAuthenticationService.cs b/OrderCloud.Catalyst/Auth/UserAuth/RequestAuthenticationService.cs similarity index 100% rename from library/OrderCloud.Catalyst/Auth/UserAuth/RequestAuthenticationService.cs rename to OrderCloud.Catalyst/Auth/UserAuth/RequestAuthenticationService.cs diff --git a/library/OrderCloud.Catalyst/Auth/UserAuth/UserTypeRestrictedToAttribute.cs b/OrderCloud.Catalyst/Auth/UserAuth/UserTypeRestrictedToAttribute.cs similarity index 100% rename from library/OrderCloud.Catalyst/Auth/UserAuth/UserTypeRestrictedToAttribute.cs rename to OrderCloud.Catalyst/Auth/UserAuth/UserTypeRestrictedToAttribute.cs diff --git a/library/OrderCloud.Catalyst/Auth/WebhookAuth/OrderCloudWebhookAuth.cs b/OrderCloud.Catalyst/Auth/WebhookAuth/OrderCloudWebhookAuth.cs similarity index 100% rename from library/OrderCloud.Catalyst/Auth/WebhookAuth/OrderCloudWebhookAuth.cs rename to OrderCloud.Catalyst/Auth/WebhookAuth/OrderCloudWebhookAuth.cs diff --git a/library/OrderCloud.Catalyst/Auth/WebhookAuth/README.md b/OrderCloud.Catalyst/Auth/WebhookAuth/README.md similarity index 100% rename from library/OrderCloud.Catalyst/Auth/WebhookAuth/README.md rename to OrderCloud.Catalyst/Auth/WebhookAuth/README.md diff --git a/library/OrderCloud.Catalyst/DataMovement/Caching/ISimpleCache.cs b/OrderCloud.Catalyst/DataMovement/Caching/ISimpleCache.cs similarity index 100% rename from library/OrderCloud.Catalyst/DataMovement/Caching/ISimpleCache.cs rename to OrderCloud.Catalyst/DataMovement/Caching/ISimpleCache.cs diff --git a/library/OrderCloud.Catalyst/DataMovement/Caching/LazyCacheService.cs b/OrderCloud.Catalyst/DataMovement/Caching/LazyCacheService.cs similarity index 100% rename from library/OrderCloud.Catalyst/DataMovement/Caching/LazyCacheService.cs rename to OrderCloud.Catalyst/DataMovement/Caching/LazyCacheService.cs diff --git a/library/OrderCloud.Catalyst/DataMovement/Caching/README.md b/OrderCloud.Catalyst/DataMovement/Caching/README.md similarity index 100% rename from library/OrderCloud.Catalyst/DataMovement/Caching/README.md rename to OrderCloud.Catalyst/DataMovement/Caching/README.md diff --git a/library/OrderCloud.Catalyst/DataMovement/Differ.cs b/OrderCloud.Catalyst/DataMovement/Differ.cs similarity index 100% rename from library/OrderCloud.Catalyst/DataMovement/Differ.cs rename to OrderCloud.Catalyst/DataMovement/Differ.cs diff --git a/library/OrderCloud.Catalyst/DataMovement/ListAllAsync/ListAllHelper.cs b/OrderCloud.Catalyst/DataMovement/ListAllAsync/ListAllHelper.cs similarity index 100% rename from library/OrderCloud.Catalyst/DataMovement/ListAllAsync/ListAllHelper.cs rename to OrderCloud.Catalyst/DataMovement/ListAllAsync/ListAllHelper.cs diff --git a/library/OrderCloud.Catalyst/DataMovement/ListAllAsync/README.md b/OrderCloud.Catalyst/DataMovement/ListAllAsync/README.md similarity index 100% rename from library/OrderCloud.Catalyst/DataMovement/ListAllAsync/README.md rename to OrderCloud.Catalyst/DataMovement/ListAllAsync/README.md diff --git a/library/OrderCloud.Catalyst/DataMovement/RetryPolicy.cs b/OrderCloud.Catalyst/DataMovement/RetryPolicy.cs similarity index 100% rename from library/OrderCloud.Catalyst/DataMovement/RetryPolicy.cs rename to OrderCloud.Catalyst/DataMovement/RetryPolicy.cs diff --git a/library/OrderCloud.Catalyst/DataMovement/Throttler/README.md b/OrderCloud.Catalyst/DataMovement/Throttler/README.md similarity index 100% rename from library/OrderCloud.Catalyst/DataMovement/Throttler/README.md rename to OrderCloud.Catalyst/DataMovement/Throttler/README.md diff --git a/library/OrderCloud.Catalyst/DataMovement/Throttler/Throttler.cs b/OrderCloud.Catalyst/DataMovement/Throttler/Throttler.cs similarity index 100% rename from library/OrderCloud.Catalyst/DataMovement/Throttler/Throttler.cs rename to OrderCloud.Catalyst/DataMovement/Throttler/Throttler.cs diff --git a/library/OrderCloud.Catalyst/Errors/CatalystBaseException.cs b/OrderCloud.Catalyst/Errors/CatalystBaseException.cs similarity index 100% rename from library/OrderCloud.Catalyst/Errors/CatalystBaseException.cs rename to OrderCloud.Catalyst/Errors/CatalystBaseException.cs diff --git a/library/OrderCloud.Catalyst/Errors/ErrorCode.cs b/OrderCloud.Catalyst/Errors/ErrorCode.cs similarity index 100% rename from library/OrderCloud.Catalyst/Errors/ErrorCode.cs rename to OrderCloud.Catalyst/Errors/ErrorCode.cs diff --git a/library/OrderCloud.Catalyst/Errors/Exceptions.cs b/OrderCloud.Catalyst/Errors/Exceptions.cs similarity index 100% rename from library/OrderCloud.Catalyst/Errors/Exceptions.cs rename to OrderCloud.Catalyst/Errors/Exceptions.cs diff --git a/library/OrderCloud.Catalyst/Errors/GlobalExceptionHandler.cs b/OrderCloud.Catalyst/Errors/GlobalExceptionHandler.cs similarity index 100% rename from library/OrderCloud.Catalyst/Errors/GlobalExceptionHandler.cs rename to OrderCloud.Catalyst/Errors/GlobalExceptionHandler.cs diff --git a/library/OrderCloud.Catalyst/Errors/README.md b/OrderCloud.Catalyst/Errors/README.md similarity index 100% rename from library/OrderCloud.Catalyst/Errors/README.md rename to OrderCloud.Catalyst/Errors/README.md diff --git a/library/OrderCloud.Catalyst/Errors/Require.cs b/OrderCloud.Catalyst/Errors/Require.cs similarity index 100% rename from library/OrderCloud.Catalyst/Errors/Require.cs rename to OrderCloud.Catalyst/Errors/Require.cs diff --git a/library/OrderCloud.Catalyst/Errors/ValidateModel.cs b/OrderCloud.Catalyst/Errors/ValidateModel.cs similarity index 100% rename from library/OrderCloud.Catalyst/Errors/ValidateModel.cs rename to OrderCloud.Catalyst/Errors/ValidateModel.cs diff --git a/library/OrderCloud.Catalyst/Extensions/ExtensionMethods.cs b/OrderCloud.Catalyst/Extensions/ExtensionMethods.cs similarity index 100% rename from library/OrderCloud.Catalyst/Extensions/ExtensionMethods.cs rename to OrderCloud.Catalyst/Extensions/ExtensionMethods.cs diff --git a/library/OrderCloud.Catalyst/Generated/ListAllExtensions.cs b/OrderCloud.Catalyst/Generated/ListAllExtensions.cs similarity index 100% rename from library/OrderCloud.Catalyst/Generated/ListAllExtensions.cs rename to OrderCloud.Catalyst/Generated/ListAllExtensions.cs diff --git a/library/OrderCloud.Catalyst/Generated/ListByIDExtensions.cs b/OrderCloud.Catalyst/Generated/ListByIDExtensions.cs similarity index 100% rename from library/OrderCloud.Catalyst/Generated/ListByIDExtensions.cs rename to OrderCloud.Catalyst/Generated/ListByIDExtensions.cs diff --git a/library/OrderCloud.Catalyst/Generated/ListExtensions.cs b/OrderCloud.Catalyst/Generated/ListExtensions.cs similarity index 100% rename from library/OrderCloud.Catalyst/Generated/ListExtensions.cs rename to OrderCloud.Catalyst/Generated/ListExtensions.cs diff --git a/library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md b/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md rename to OrderCloud.Catalyst/Integrations/CONTRIBUTING.md diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs b/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs rename to OrderCloud.Catalyst/Integrations/Exceptions/IntegrationAuthFailedException.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs b/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs rename to OrderCloud.Catalyst/Integrations/Exceptions/IntegrationErrorResponseException.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs b/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs rename to OrderCloud.Catalyst/Integrations/Exceptions/IntegrationMissingConfigsException.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs b/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs rename to OrderCloud.Catalyst/Integrations/Exceptions/IntegrationNoResponseException.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs b/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs rename to OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCodeProvider.cs b/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCodeProvider.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Interfaces/ITaxCodeProvider.cs rename to OrderCloud.Catalyst/Integrations/Interfaces/ITaxCodeProvider.cs diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs b/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs rename to OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs diff --git a/library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs b/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs rename to OrderCloud.Catalyst/Integrations/OCIntegrationConfig.cs diff --git a/library/OrderCloud.Catalyst/Integrations/Implementations/README.md b/OrderCloud.Catalyst/Integrations/README.md similarity index 100% rename from library/OrderCloud.Catalyst/Integrations/Implementations/README.md rename to OrderCloud.Catalyst/Integrations/README.md diff --git a/library/OrderCloud.Catalyst/Jobs/BaseJob.cs b/OrderCloud.Catalyst/Jobs/BaseJob.cs similarity index 100% rename from library/OrderCloud.Catalyst/Jobs/BaseJob.cs rename to OrderCloud.Catalyst/Jobs/BaseJob.cs diff --git a/library/OrderCloud.Catalyst/Models/ListOptions/ListArgs.cs b/OrderCloud.Catalyst/Models/ListOptions/ListArgs.cs similarity index 100% rename from library/OrderCloud.Catalyst/Models/ListOptions/ListArgs.cs rename to OrderCloud.Catalyst/Models/ListOptions/ListArgs.cs diff --git a/library/OrderCloud.Catalyst/Models/ListOptions/ListArgsBinder.cs b/OrderCloud.Catalyst/Models/ListOptions/ListArgsBinder.cs similarity index 100% rename from library/OrderCloud.Catalyst/Models/ListOptions/ListArgsBinder.cs rename to OrderCloud.Catalyst/Models/ListOptions/ListArgsBinder.cs diff --git a/library/OrderCloud.Catalyst/Models/ListOptions/ListArgsPageOnly.cs b/OrderCloud.Catalyst/Models/ListOptions/ListArgsPageOnly.cs similarity index 100% rename from library/OrderCloud.Catalyst/Models/ListOptions/ListArgsPageOnly.cs rename to OrderCloud.Catalyst/Models/ListOptions/ListArgsPageOnly.cs diff --git a/library/OrderCloud.Catalyst/Models/ListOptions/ListFilter.cs b/OrderCloud.Catalyst/Models/ListOptions/ListFilter.cs similarity index 100% rename from library/OrderCloud.Catalyst/Models/ListOptions/ListFilter.cs rename to OrderCloud.Catalyst/Models/ListOptions/ListFilter.cs diff --git a/library/OrderCloud.Catalyst/Models/ListOptions/README.md b/OrderCloud.Catalyst/Models/ListOptions/README.md similarity index 100% rename from library/OrderCloud.Catalyst/Models/ListOptions/README.md rename to OrderCloud.Catalyst/Models/ListOptions/README.md diff --git a/library/OrderCloud.Catalyst/Models/ListOptions/SearchArgs.cs b/OrderCloud.Catalyst/Models/ListOptions/SearchArgs.cs similarity index 100% rename from library/OrderCloud.Catalyst/Models/ListOptions/SearchArgs.cs rename to OrderCloud.Catalyst/Models/ListOptions/SearchArgs.cs diff --git a/library/OrderCloud.Catalyst/Models/Webhooks/OpenIDConnectUserPayload.cs b/OrderCloud.Catalyst/Models/Webhooks/OpenIDConnectUserPayload.cs similarity index 100% rename from library/OrderCloud.Catalyst/Models/Webhooks/OpenIDConnectUserPayload.cs rename to OrderCloud.Catalyst/Models/Webhooks/OpenIDConnectUserPayload.cs diff --git a/library/OrderCloud.Catalyst/Models/Webhooks/OrderCalculatePayload.cs b/OrderCloud.Catalyst/Models/Webhooks/OrderCalculatePayload.cs similarity index 100% rename from library/OrderCloud.Catalyst/Models/Webhooks/OrderCalculatePayload.cs rename to OrderCloud.Catalyst/Models/Webhooks/OrderCalculatePayload.cs diff --git a/library/OrderCloud.Catalyst/Models/Webhooks/PreWebhookResponse.cs b/OrderCloud.Catalyst/Models/Webhooks/PreWebhookResponse.cs similarity index 100% rename from library/OrderCloud.Catalyst/Models/Webhooks/PreWebhookResponse.cs rename to OrderCloud.Catalyst/Models/Webhooks/PreWebhookResponse.cs diff --git a/library/OrderCloud.Catalyst/OrderCloud.Catalyst.csproj b/OrderCloud.Catalyst/OrderCloud.Catalyst.csproj similarity index 96% rename from library/OrderCloud.Catalyst/OrderCloud.Catalyst.csproj rename to OrderCloud.Catalyst/OrderCloud.Catalyst.csproj index 42b919b..1e4dfbf 100644 --- a/library/OrderCloud.Catalyst/OrderCloud.Catalyst.csproj +++ b/OrderCloud.Catalyst/OrderCloud.Catalyst.csproj @@ -21,7 +21,7 @@ MIT - + diff --git a/codegen/package.json b/codegen/package.json index 9f25ebc..3f07958 100644 --- a/codegen/package.json +++ b/codegen/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "codegen": "tsc --p . && oc-codegen -t templates -o ../library/OrderCloud.Catalyst/Generated -i https://sandboxapi.ordercloud.io/v1/openapi/v3 -k hooks.js -c" + "codegen": "tsc --p . && oc-codegen -t templates -o ../OrderCloud.Catalyst/Generated -i https://sandboxapi.ordercloud.io/v1/openapi/v3 -k hooks.js -c" }, "author": "", "license": "ISC", From 59bf2467cb0734b13bf89260c0938deab4ee6b23 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Fri, 11 Feb 2022 13:41:59 -0600 Subject: [PATCH 37/40] updating readmes for project structure --- OrderCloud.Catalyst.Tax.Avalara/README.md | 41 ++++++++++++++++++ OrderCloud.Catalyst.Tax.TaxJar/README.md | 8 ++-- .../Integrations/CONTRIBUTING.md | 43 ++++++++++--------- .../Integrations/OCIntegrationCommand.cs | 2 +- OrderCloud.Catalyst/Integrations/README.md | 6 +-- 5 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 OrderCloud.Catalyst.Tax.Avalara/README.md diff --git a/OrderCloud.Catalyst.Tax.Avalara/README.md b/OrderCloud.Catalyst.Tax.Avalara/README.md new file mode 100644 index 0000000..4fabb38 --- /dev/null +++ b/OrderCloud.Catalyst.Tax.Avalara/README.md @@ -0,0 +1,41 @@ +# Vertex Integration + +## Scope of this integration +This .NET integration calculates sales tax for an Order using the TaxJar API. It can be used during checkout to provide a tax cost to the buyer or after submit to create a vertex transaction for filling. + +Use Cases: +- Sales Tax Estimate +- Finialized Order Forwarding + +## Taxjar Basics +[TaxJar](https://www.taxjar.com/) is reimagining how businesses manage sales tax compliance. Our cloud-based platform automates the entire sales tax life cycle across all of your sales channels — from calculations and nexus tracking to reporting and filing. With innovative technology and award-winning support, we simplify sales tax compliance so you can grow with ease. + +## Sales Tax Estimate +The sales tax cost on an Order is first calculated in checkout after shipping selections are made and before payment. Following that, they are updated whenever the order is changed. + +**Avalara Side -** Get a tax estimate by calling Avalara's [create transaction endpoint](https://developer.avalara.com/api-reference/avatax/rest/v2/methods/Transactions/CreateTransaction) with `type` set to `SalesOrder`. + +**OrderCloud Side -** This integration should be triggered by the **`OrderCalculate`** Checkout Integration Event. Learn more about [checkout integration events](https://ordercloud.io/knowledge-base/order-checkout-integration); + +## Committed Transactions +A taxable transaction is committed to avalara asynchronously shortly following order submit. This enables businesses to easily file sales tax returns. OrderCloud guarantees the submitted order details provided will be unchanged since the most recent tax estimate displayed to the user. + +**Avalara Side -** Commit a transaction in Avalara by calling the [create transaction endpoint](https://developer.avalara.com/api-reference/avatax/rest/v2/methods/Transactions/CreateTransaction) with `type` set to `SalesInvoice`. + +**OrderCloud Side -** This integration should be triggered by the **`PostOrderSubmit`** Checkout Integration Event. Learn more about [checkout integration events](https://ordercloud.io/knowledge-base/order-checkout-integration); + +## Set up steps + +- You should set up a .NET middleware project using the Catalyst library and starter project. [See guide](https://ordercloud.io/knowledge-base/start-dotnet-middleware-from-scratch). +- Using the OrderCloud API Portal, configure an Order Chekout IntegrationEvent object to point to your new middleware. [See guide](https://ordercloud.io/knowledge-base/order-checkout-integration) +- Create an avalara account online and retrieve all the configuration variables required in [AvalaraConfig.cs](./Avalara.cs); + - BaseUrl + - Likely "https://sandbox-rest.avatax.com/api/v2" or "https://rest.avatax.com/api/v2" + - AccountID + - LicenseKey + - CompanyCode +- Within your .NET code project, create an instance of [AvalaraCommand.cs](./AvalaraCommand.cs). Use the method `CalculateEstimateAsync` within the **`OrderCalculate`** Checkout Integration Event. Use the method `CommitTransactionAsync` within the **`PostOrderSubmit`** Checkout Integration Event. + +## Interfaces + +- It conforms to the [ITaxCalculator](../OrderCloud.Catalyst/Integrations/Interfaces/ITaxCalculator.cs) interface. \ No newline at end of file diff --git a/OrderCloud.Catalyst.Tax.TaxJar/README.md b/OrderCloud.Catalyst.Tax.TaxJar/README.md index ad7a953..9336f17 100644 --- a/OrderCloud.Catalyst.Tax.TaxJar/README.md +++ b/OrderCloud.Catalyst.Tax.TaxJar/README.md @@ -1,7 +1,7 @@ -# Vertex Integration +# TaxJar Integration ## Scope of this integration -This .NET integration calculates sales tax for an Order using the TaxJar API. It can be used during checkout to provide a tax cost to the buyer or after submit to create a vertex transaction for filling. +This .NET integration calculates sales tax for an Order using the TaxJar API. It can be used during checkout to provide a tax cost to the buyer or after submit to create a TarJar transaction for filling. Use Cases: - Sales Tax Estimate @@ -28,12 +28,12 @@ A taxable transaction is committed to taxjar asynchronously shortly following or - You should set up a .NET middleware project using the Catalyst library and starter project. [See guide](https://ordercloud.io/knowledge-base/start-dotnet-middleware-from-scratch). - Using the OrderCloud API Portal, configure an Order Chekout IntegrationEvent object to point to your new middleware. [See guide](https://ordercloud.io/knowledge-base/order-checkout-integration) -- Create a taxjar account online and retrieve all the configuration variables required in [VertexOCIntegrationConfig.cs](./VertexOCIntegrationConfig.cs); +- Create a taxjar account online and retrieve all the configuration variables required in [TaxJarConfig.cs](./TaxJarConfig.cs); - BaseUrl - Likely "https://api.sandbox.taxjar.com" or "https://api.taxjar.com") - APIToken - Find at https://app.taxjar.com/ under Account -> TaxJar API -> Generate Token -- Within your .NET code project, create an instance of [VertexOCIntegrationCommand.cs](./VertexOCIntegrationCommand.cs). Use the method `CalculateEstimateAsync` within the **`OrderCalculate`** Checkout Integration Event. Use the method `CommitTransactionAsync` within the **`PostOrderSubmit`** Checkout Integration Event. +- Within your .NET code project, create an instance of [TaxJarCommand.cs](./TaxJarCommand.cs). Use the method `CalculateEstimateAsync` within the **`OrderCalculate`** Checkout Integration Event. Use the method `CommitTransactionAsync` within the **`PostOrderSubmit`** Checkout Integration Event. ## Interfaces diff --git a/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md b/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md index 1b3d7df..cfa646e 100644 --- a/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md +++ b/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md @@ -1,14 +1,14 @@ # Contributing Guide For Integrations -Thank you for investing your time in contributing! These are guidelines for adding a new integration to the Catalyst library. See a list of [existing integrations](./Implementations/README.md). +Thank you for investing your time in contributing! These are guidelines for adding a new C# integration to OrderCloud Catalyst. See a list of [existing integrations](./README.md). ## Basics -Creating an integration in this project means it will be published as part of a [Nuget code library](https://www.nuget.org/packages/ordercloud-dotnet-catalyst/). Each integration should expose functionality to interact with 1 external service and should not depend on any other integrations. There is a natural tension between providing too little "wrapper" functionality (creating a generic API client) and too much "wrapper" (an opinionated solution that limits use cases). The key to this balance are the details of the contract your integration exposes. +Creating an integration in this project means it will be published as its own Nuget code library. Each library should expose functionality to interact with 1 external service and should not depend on any other integrations. All integrations will depend on the [OrderCloud.Catalyst](https://www.nuget.org/packages/ordercloud-dotnet-catalyst/) base library to enforce standard contracts. There is a natural tension between providing too little "wrapper" functionality (creating a generic API client) and too much "wrapper" (an opinionated solution that limits use cases). The key to this balance are the details of the contract your integration exposes. ## Exposed Contracts -All integrations should include two classes designed to be exposed and consumed by solutions - an `OCIntegrationConfig` and an `OCIntegrationCommand`. The config is a POCO which contains `string` properties for all the environment variables needed to authenticate to the service. The command exposes the functionality of your integration through methods. For an example service called "Mississippi" you would create the classes below. +All integrations should include two classes designed to be used by ecommerce solutions - an `OCIntegrationConfig` and an `OCIntegrationCommand`. The config is a POCO which contains `string` properties for all the environment variables needed to authenticate to the service. The command exposes the functionality of your integration through methods. There should be two ways to provide a config to the command. First, a default config which is a constructor parameter. Second, every method should take an optional override config which only applies to that request's scope'. For an example service called "Mississippi" you would create the classes below. ```c# public class MississippiConfig : OCIntegrationConfig @@ -18,25 +18,21 @@ public class MississippiConfig : OCIntegrationConfig [RequiredIntegrationField] public string ApiKey { get; set;} - ... etc. + ... more environment variables. } ``` ```c# public class MississippiCommand : OCIntegrationCommand { - protected readonly MississippiConfig _config; + public MississippiOCIntegrationCommand(MississippiConfig configDefault) : base(configDefault) { } - public MississippiOCIntegrationCommand(MississippiConfig config) : base(config) + public async Task GetRiverLength(OCIntegrationConfig configOverride = null) { - _config = config; // used to auth to service + var configToUse = GetValidatedConfig(configOverride); + // Make request here } - public async Task GetRiverLength() - { - - } - - ... etc. + ... more methods. } ``` @@ -44,7 +40,7 @@ Your integration will likely contain other public classes but these two mandator ## Interfaces -A key goal is *interoperability*. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like [ITaxCalculator](./Interfaces/ITaxCalculator.cs). Please check under [/Integrations/Interfaces](./Interfaces) to see if any apply to your integration's problem domain. If some do, make sure your OCIntegrationCommand implements those interfaces. +A key goal is *interoperability*. In other words, if two services solve roughly the same problem (e.g. calculating tax), they should expose the same contract. To facilitate that, there are interfaces like [ITaxCalculator](./Interfaces/ITaxCalculator.cs). Please check under [./Interfaces](./Interfaces) to see if any apply to your integration's problem domain. If some do, make sure your OCIntegrationCommand implements those interfaces. Feel free open issues recommending changes or additions to the interfaces. @@ -53,11 +49,16 @@ Feel free open issues recommending changes or additions to the interfaces. - General - Keep the number of properties and methods on your exposed contracts to the minimum required. Do a small amount well. - Aim to follow the code patterns of existing integrations. - - Folders and files - - Under [/Integrations/Implementations](./Implementations) create a folder with your service name (e.g. "Mississippi") to contain your files. - - At the root of your new folder include your Command, Config and a README.md. Copy the README format of existing integrations. - - All files and class names should begin with your service name. - - Many of the existing integrations also have a Client class. For these integrations the Client class is a pure API wrapper, handling HTTP requests and exceptions. This leaves mapping as the responsibility of the Command class. + - Project Structure + - Create a new Visual Studio project under /libraries. It should be a .NET Standard 2.0 code library project called OrderCloud.Catalyst.[Category].[ServiceName]. For example OrderCloud.Catalyst.Tax.Avalara. + - Your new project should have a procject dependency on OrderCloud.Catalyst. Also, OrderCloud.Catalyst.TestApi should depend on your new project. + - At the root of your new folder include your Command, Config and a README.md with instructions. Copy the README format of existing integrations. + - All files and class names in the project should begin with your service name to avoid collisions. + - Use the OrderCloud.Catalyst.[Category].[ServiceName] namespace for all classes. + - Publishing on Nuget + - Your .csproj file contains details of how your integration will be published. Make sure the target framework is `netstandard2.0` and the package Id is `OrderCloud.Catalyst.[Category].[ServiceName]`. + - For versioning, refer to https://semver.org/. Specifically, major version changes (API breaking changes) should only be made in response to major version changes in the core Order.Catalyst library. This should only happen when the interfaces in [/Integrations/Interfaces](./Interfaces) are changed. If the two libraries start with the same major version number, they should have matching interface definitions and be compatible. + - For now, the OrderCloud team will handle publishing packages in order to maintain high quality. - Errors - Handle error scenarios within your integration by throwing one of the exceptions in [/Integrations/Exceptions](./Exceptions). - Every integration should handle cases like missing configs, invalid authentication, error response, and no response. @@ -67,7 +68,9 @@ Feel free open issues recommending changes or additions to the interfaces. - Test error scenarios as well. - See [VertexTests](../../../tests.OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs). - Code Style - - Avoid adding a nuget package for your service's SDK. This will lead to bloat as many projects may use this library without using your service. Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. + - For every public method on the Command class use the `GetValidatedConfig()` method to authenticate. It will default to the config provided in the constructor, but safely give priority to any config specified in the method call. This supports different suppliers using different credentials, for example. + - Many of the existing integrations also have a Client class. For these integrations the Client class is a pure API wrapper, handling HTTP requests and exceptions. Avoid code patterns that lead to creating multiple Http Client objects in memory. + - Avoid adding a nuget package for your service's SDK (or nuget packages in general if you can). Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. - Use DateTime.Utc explicitly to keep the project time-zone agnostic. - Always use the `decimal` type for anything money related. diff --git a/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs b/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs index 9e154b6..9f77bd9 100644 --- a/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs +++ b/OrderCloud.Catalyst/Integrations/OCIntegrationCommand.cs @@ -10,7 +10,7 @@ namespace OrderCloud.Catalyst /// public abstract class OCIntegrationCommand { - protected readonly OCIntegrationConfig _configDefault; + private readonly OCIntegrationConfig _configDefault; public OCIntegrationCommand(OCIntegrationConfig configDefault) { diff --git a/OrderCloud.Catalyst/Integrations/README.md b/OrderCloud.Catalyst/Integrations/README.md index ee9b9d3..2da956d 100644 --- a/OrderCloud.Catalyst/Integrations/README.md +++ b/OrderCloud.Catalyst/Integrations/README.md @@ -2,6 +2,6 @@ | Name | Splash Site | Integration Guide | Contributed By | Categories | | ------------- | ------------- | ------------- | ------------- | ------------- | -| **Vertex** | https://www.vertexinc.com/ | [README.md](./Vertex/README.md) | OrderCloud Team | Tax -| **Avalara** | https://www.avalara.com/us/en/index.html | [README.md](./Avalara/README.md) | OrderCloud Team | Tax -| **TaxJar** | https://www.taxjar.com/ | [README.md](./TaxJar/README.md) | OrderCloud Team | Tax +| **Vertex** | https://www.vertexinc.com/ | [README.md](../../OrderCloud.Catalyst.Tax.Vertex/README.md) | OrderCloud Team | Tax +| **Avalara** | https://www.avalara.com/us/en/index.html | [README.md](../../OrderCloud.Catalyst.Tax.Avalara/README.md) | OrderCloud Team | Tax +| **TaxJar** | https://www.taxjar.com/ | [README.md](../../OrderCloud.Catalyst.Tax.TaxJar/README.md) | OrderCloud Team | Tax From aa73883df3feadfdc0d8e96261ddd6b130949d5f Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Fri, 11 Feb 2022 13:54:53 -0600 Subject: [PATCH 38/40] remove this as were not ready to be that public --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index f793baa..aef9f2e 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,6 @@ If you're building solutions for OrderCloud using .NET and find a particular tas ## Features -### [Commerce Integration List](./library/OrderCLoud.Catalyst/Integrations/Implementations/README.md) - -Interact with popular 3rd party APIs that provide functionality useful for commerce. Integrations within a category are made interoperable with an interface. - -[Guide to adding an Integration](./library/OrderCLoud.Catalyst/Integrations/CONTRIBUTING.md) - ### [User Authentication](https://github.com/ordercloud-api/ordercloud-dotnet-catalyst/tree/dev/library/OrderCloud.Catalyst/Auth/UserAuth) Use Ordercloud's authentication scheme in your own APIs. From 094d5cfd94c8709ef3a891ec59281dd37a803a4e Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Fri, 11 Feb 2022 13:59:22 -0600 Subject: [PATCH 39/40] readme updates --- OrderCloud.Catalyst/Integrations/CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md b/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md index cfa646e..bcb5b8c 100644 --- a/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md +++ b/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md @@ -50,8 +50,8 @@ Feel free open issues recommending changes or additions to the interfaces. - Keep the number of properties and methods on your exposed contracts to the minimum required. Do a small amount well. - Aim to follow the code patterns of existing integrations. - Project Structure - - Create a new Visual Studio project under /libraries. It should be a .NET Standard 2.0 code library project called OrderCloud.Catalyst.[Category].[ServiceName]. For example OrderCloud.Catalyst.Tax.Avalara. - - Your new project should have a procject dependency on OrderCloud.Catalyst. Also, OrderCloud.Catalyst.TestApi should depend on your new project. + - Create a new Visual Studio project under /libraries. It should be a .NET Standard 2.0 code library project called `OrderCloud.Catalyst.[Category].[ServiceName]`. For example, `OrderCloud.Catalyst.Tax.Avalara`. + - Your new project should have a project dependency on OrderCloud.Catalyst. Also, OrderCloud.Catalyst.TestApi should depend on your new project. - At the root of your new folder include your Command, Config and a README.md with instructions. Copy the README format of existing integrations. - All files and class names in the project should begin with your service name to avoid collisions. - Use the OrderCloud.Catalyst.[Category].[ServiceName] namespace for all classes. @@ -68,11 +68,11 @@ Feel free open issues recommending changes or additions to the interfaces. - Test error scenarios as well. - See [VertexTests](../../../tests.OrderCloud.Catalyst.Tests/IntegrationTests/Vertex/VertexTests.cs). - Code Style - - For every public method on the Command class use the `GetValidatedConfig()` method to authenticate. It will default to the config provided in the constructor, but safely give priority to any config specified in the method call. This supports different suppliers using different credentials, for example. + - For every public method on the Command class use the `GetValidatedConfig()` method to authenticate. It will default to the config provided in the constructor, but safely give priority to any config specified in the method call. This supports use cases like different suppliers using different credentials. - Many of the existing integrations also have a Client class. For these integrations the Client class is a pure API wrapper, handling HTTP requests and exceptions. Avoid code patterns that lead to creating multiple Http Client objects in memory. - - Avoid adding a nuget package for your service's SDK (or nuget packages in general if you can). Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. + - Avoid adding a nuget package for your service's SDK (or nuget packages in general). Instead, use the Flurl library for RESTful requests. This will also keep testing consistient. - When you want to make methods or properties `private`, consider using `protected` instead so that client projects can extend your functionality. - - Use DateTime.Utc explicitly to keep the project time-zone agnostic. + - Use `DateTime.Utc` explicitly to avoid the confusion of local timezones. - Always use the `decimal` type for anything money related. ## Approval From 4146a6e426d3333503cf3e3c15ffe65cf7928c56 Mon Sep 17 00:00:00 2001 From: Oliver Heywood Date: Mon, 14 Feb 2022 10:38:10 -0600 Subject: [PATCH 40/40] readme and package description updates --- .../OrderCloud.Catalyst.Tax.Avalara.csproj | 10 +++++----- .../OrderCloud.Catalyst.Tax.TaxJar.csproj | 8 ++++---- .../OrderCloud.Catalyst.Tax.Vertex.csproj | 8 ++++---- OrderCloud.Catalyst/Integrations/CONTRIBUTING.md | 6 +++++- OrderCloud.Catalyst/Integrations/README.md | 8 ++++---- OrderCloud.Catalyst/OrderCloud.Catalyst.csproj | 6 +++--- 6 files changed, 25 insertions(+), 21 deletions(-) diff --git a/OrderCloud.Catalyst.Tax.Avalara/OrderCloud.Catalyst.Tax.Avalara.csproj b/OrderCloud.Catalyst.Tax.Avalara/OrderCloud.Catalyst.Tax.Avalara.csproj index d1700eb..fe2c5a6 100644 --- a/OrderCloud.Catalyst.Tax.Avalara/OrderCloud.Catalyst.Tax.Avalara.csproj +++ b/OrderCloud.Catalyst.Tax.Avalara/OrderCloud.Catalyst.Tax.Avalara.csproj @@ -1,22 +1,22 @@ - + netstandard2.0 True - 1.5.1 + 1.0.1-alpha OrderCloud.Catalyst.Tax.Avalara OrderCloud Tax Integration with Avalara Oliver Heywood - Integrate the OrderCloud ecommerce platform with Avalara for tax calculation. + Integrate the OrderCloud ecommerce platform with Avalara for tax calculation. Alpha version, not recommended for production. Copyright 2021 Four51, Inc. https://github.com/ordercloud-api/ordercloud-dotnet-catalyst ordercloud-logo-blue.png - ecommerce b2b azure aspnetcore webjobs four51 ordercloud + ordercloud ecommerce tax webjobs sitecore avalara https://github.com/ordercloud-api/ordercloud-dotnet-catalyst git - Four51 Inc + Sitecore OrderCloud MIT diff --git a/OrderCloud.Catalyst.Tax.TaxJar/OrderCloud.Catalyst.Tax.TaxJar.csproj b/OrderCloud.Catalyst.Tax.TaxJar/OrderCloud.Catalyst.Tax.TaxJar.csproj index 3880675..7f9a9cf 100644 --- a/OrderCloud.Catalyst.Tax.TaxJar/OrderCloud.Catalyst.Tax.TaxJar.csproj +++ b/OrderCloud.Catalyst.Tax.TaxJar/OrderCloud.Catalyst.Tax.TaxJar.csproj @@ -3,20 +3,20 @@ netstandard2.0 True - 1.5.1 + 1.0.1-alpha OrderCloud.Catalyst.Tax.TaxJar OrderCloud Tax Integration with TaxJar Oliver Heywood - Integrate the OrderCloud ecommerce platform with TaxJar for tax calculation. + Integrate the OrderCloud ecommerce platform with TaxJar for tax calculation. Alpha version, not recommended for production. Copyright 2021 Four51, Inc. https://github.com/ordercloud-api/ordercloud-dotnet-catalyst ordercloud-logo-blue.png - ecommerce b2b azure aspnetcore webjobs four51 ordercloud + ordercloud ecommerce tax webjobs sitecore taxjar https://github.com/ordercloud-api/ordercloud-dotnet-catalyst git - Four51 Inc + Sitecore OrderCloud MIT diff --git a/OrderCloud.Catalyst.Tax.Vertex/OrderCloud.Catalyst.Tax.Vertex.csproj b/OrderCloud.Catalyst.Tax.Vertex/OrderCloud.Catalyst.Tax.Vertex.csproj index 93d1783..f78e6e8 100644 --- a/OrderCloud.Catalyst.Tax.Vertex/OrderCloud.Catalyst.Tax.Vertex.csproj +++ b/OrderCloud.Catalyst.Tax.Vertex/OrderCloud.Catalyst.Tax.Vertex.csproj @@ -3,20 +3,20 @@ netstandard2.0 True - 1.5.1 + 1.0.1-alpha OrderCloud.Catalyst.Tax.Vertex OrderCloud Tax Integration with Vertex Oliver Heywood - Integrate the OrderCloud ecommerce platform with Vertex for tax calculation. + Integrate the OrderCloud ecommerce platform with Vertex for tax calculation. Alpha version, not recommended for production. Copyright 2021 Four51, Inc. https://github.com/ordercloud-api/ordercloud-dotnet-catalyst ordercloud-logo-blue.png - ecommerce b2b azure aspnetcore webjobs four51 ordercloud + ordercloud ecommerce tax webjobs sitecore vertex https://github.com/ordercloud-api/ordercloud-dotnet-catalyst git - Four51 Inc + Sitecore OrderCloud MIT diff --git a/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md b/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md index bcb5b8c..e42b6b2 100644 --- a/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md +++ b/OrderCloud.Catalyst/Integrations/CONTRIBUTING.md @@ -57,8 +57,12 @@ Feel free open issues recommending changes or additions to the interfaces. - Use the OrderCloud.Catalyst.[Category].[ServiceName] namespace for all classes. - Publishing on Nuget - Your .csproj file contains details of how your integration will be published. Make sure the target framework is `netstandard2.0` and the package Id is `OrderCloud.Catalyst.[Category].[ServiceName]`. - - For versioning, refer to https://semver.org/. Specifically, major version changes (API breaking changes) should only be made in response to major version changes in the core Order.Catalyst library. This should only happen when the interfaces in [/Integrations/Interfaces](./Interfaces) are changed. If the two libraries start with the same major version number, they should have matching interface definitions and be compatible. + - Versioning + - Refer to https://semver.org/. + - Start at version x.0.1 where x is the most recent Major version of OrderCloud.Catalyst. Increment the Patch version for bug fixes and Minor version for new functionality. + - Major version changes (API breaking changes) should only be made in response to major version changes in the core OrderCloud.Catalyst library. This should only happen when the interfaces in [/Integrations/Interfaces](./Interfaces) are changed. If the two libraries start with the same major version number, they should have matching interface definitions and be compatible. - For now, the OrderCloud team will handle publishing packages in order to maintain high quality. + - Versions marked `alpha` are early releases that are not ready for production. Use them only to provide feedback or get a sneak peak. - Errors - Handle error scenarios within your integration by throwing one of the exceptions in [/Integrations/Exceptions](./Exceptions). - Every integration should handle cases like missing configs, invalid authentication, error response, and no response. diff --git a/OrderCloud.Catalyst/Integrations/README.md b/OrderCloud.Catalyst/Integrations/README.md index 2da956d..17cd531 100644 --- a/OrderCloud.Catalyst/Integrations/README.md +++ b/OrderCloud.Catalyst/Integrations/README.md @@ -1,7 +1,7 @@ # List of Integrations -| Name | Splash Site | Integration Guide | Contributed By | Categories | +| Name | Integration Guide | Contributed By | Categories | | ------------- | ------------- | ------------- | ------------- | ------------- | -| **Vertex** | https://www.vertexinc.com/ | [README.md](../../OrderCloud.Catalyst.Tax.Vertex/README.md) | OrderCloud Team | Tax -| **Avalara** | https://www.avalara.com/us/en/index.html | [README.md](../../OrderCloud.Catalyst.Tax.Avalara/README.md) | OrderCloud Team | Tax -| **TaxJar** | https://www.taxjar.com/ | [README.md](../../OrderCloud.Catalyst.Tax.TaxJar/README.md) | OrderCloud Team | Tax +| **Vertex** | [README.md](../../OrderCloud.Catalyst.Tax.Vertex/README.md) | OrderCloud Team | Tax +| **Avalara** | [README.md](../../OrderCloud.Catalyst.Tax.Avalara/README.md) | OrderCloud Team | Tax +| **TaxJar** | [README.md](../../OrderCloud.Catalyst.Tax.TaxJar/README.md) | OrderCloud Team | Tax diff --git a/OrderCloud.Catalyst/OrderCloud.Catalyst.csproj b/OrderCloud.Catalyst/OrderCloud.Catalyst.csproj index 1e4dfbf..84d3fff 100644 --- a/OrderCloud.Catalyst/OrderCloud.Catalyst.csproj +++ b/OrderCloud.Catalyst/OrderCloud.Catalyst.csproj @@ -3,7 +3,7 @@ netstandard2.0 True - 1.5.1 + 1.6.0 ordercloud-dotnet-catalyst OrderCloud SDK Extensions for Azure App Services Oliver Heywood @@ -12,11 +12,11 @@ https://github.com/ordercloud-api/ordercloud-dotnet-catalyst ordercloud-logo-blue.png - ecommerce b2b azure aspnetcore webjobs four51 ordercloud + ecommerce b2b azure aspnetcore webjobs sitecore ordercloud https://github.com/ordercloud-api/ordercloud-dotnet-catalyst git - Four51 Inc + Sitecore OrderCloud MIT