Feature #1525

Integrate with Avalara Sales Tax API

Added by Pavan Rikhi 12 months ago. Updated 7 months ago.

Status:ClosedStart date:
Priority:NormalDue date:
Assignee:Pavan Rikhi% Done:

100%

Category:OrdersSpent time:-
Target version:v1.03.00 - Avalara & Server Improvements
Easy Pickings:

Description

Now that we have to pay sales tax per state instead of just for VA, we'll pick a sales tax software to do the calculations & filing for us.

Avalara has nice guide & API docs. We'll probably just need the CreateCustomer, CreateTransaction & RefundTransaction routes at first:

https://developer.avalara.com/avatax/dev-guide/

https://developer.avalara.com/api-reference/avatax/rest/v2/

And they use swagger to document this so we can use swagger-codegen to automatically generate a client using Haskell: https://sandbox-rest.avatax.com/swagger/ui/index.html

TaxJar seems simpler:

https://developers.taxjar.com/api/reference/

Need to wait til the office decides which to pick, if any at all. If we don't pick one, we need a more complex tax calculation that discriminates by county.


We picked Avalara:

Add an Avalara module to the server, with an avalaraRequest function in the Server module which is responsible for running the requests. Use req or hreq haskell libraries for making the requests.

Hook into the CartDetails, Checkout, & Refund routes, hitting avalara for sales tax quotes & collection recording for orders with non-zero sub-totals.

For now, we can rely on avalara website for assigning tax codes to products. If easy or we have extra time, we should add dropdowns to the Add/Edit product page, allowing us to pick from a limited set of tax codes that are applicable to our products(the 3 categories of seeds, cds, dvds, books, tangible property).


From Sam:

I just spoke with Avalara's product taxability bro. He thinks we should
use FR020800 for our standard shipping charge, FR030000 for the priority
shipping and handling surcharge, and OH010000 for the seasonal item
surcharge. Can you add this to the Avalara integration?

Associated revisions

Revision f3418a5b
Added by Pavan Rikhi 8 months ago

[#1525] Add Initial Structuring for AvaTax Integration

Add the basic scaffolding for our AvaTax API integration to a new
`Avalara` module.

The module currently supports client configuration(environment,
application name/version, & authorization), error handling, and the
simplest request type - the `ping` endpoint. The `req` library is used
to make the HTTP calls to AvaTax's API & helper types/functions are
included to ease the addition of new endpoints.

Tests are included for ensuring the proper decoding of the Error & Ping
responses.

Refs #1525

Revision b94617b9
Added by Pavan Rikhi 8 months ago

[#1525] Add Support for Avalara CreateTransaction Route

Add a `createTransaction` request to the Avalara module, enabling us to
receive sales tax estimates & record tax collections for Orders. Add
related types for the Request & Response data.

Refs #1525

Revision 22490416
Added by Pavan Rikhi 8 months ago

[#1525] Add Avalara Request for the CreateCustomers API Route

Add a `createCustomers` request function to the Avalara module for
creating new Customers associated with an Avalara Company. A Customer is
required for recording a sales tax Transaction.

Refs #1525

Revision 6e09ec60
Added by Pavan Rikhi 8 months ago

[#1525] Add CommitTransaction Request to the Avalara Integration

Add a `commitTransaction` request function to the Avalara module for
marking a Transaction as committed, allowing a saved Transaction to be
used for sales tax reporting calculations.

Fix a spelling error in the CreateTransactionRequest type name.

Add CompanyCode & TransactionCode types.

Use the CustomerCode type in the cCustomerCode field of the Customer
type.

Refs #1525

Revision 3dbdbf59
Added by Pavan Rikhi 8 months ago

[#1525] Add RefundTransaction Request to Avalara Integration

Add a `refundTransaction` request to the Avalara module, allowing us to
record simple refunds to our sales tax calculation service without
having to use a complex `createTransaction` request.

Refs #1525

Revision ef3a6f7f
Added by Pavan Rikhi 8 months ago

[#1525] Integrate Avalara Configuration into Server Configuration

Add a `getAvalaraConfig` field to the server's main `Config` type
containing the configuration data for the AvaTax API and initialize it
when starting the API Server.

Add an `avalaraRequest` function to the Server module, allowing us to
run Avalara requests from our route handlers.

Refs #1525

Revision d0c2c187
Added by Pavan Rikhi 8 months ago

[#1525] Remove Product Tax, Tax Rates, & Tax Description

Remove the Tax column from the OrderProduct table, the TaxDescription
column from the Order table, and the TaxRate table. These values will be
calculated by Avalara, so we will simply sum up the total tax and add it
in as OrderLineItem with the new TaxLine LineItemType.

Refs #1525

Revision 6a811187
Added by Pavan Rikhi 8 months ago

[#1525] Add Request for Voiding Avalara Transactions

Add a `voidTransaction` request to the Avalara integration module,
allowing us to mark a sales tax Transaction as void.

Since we need to create an uncommitted Transaction to obtain the final
order total for charging a Customer via Stripe, it's possible for a
route handler to fail after creating a Transaction but before committing
it(e.g., the Stripe request fails). In these situations, we can void the
uncommitted Transaction and it will be removed from the Avalara history.

Refs #1525

Revision c7cf6624
Added by Pavan Rikhi 8 months ago

[#1525] Add Avalara TaxCode for Handling Charges

Add a handlingOnlyTaxCode value to the Avalara module, representing the
standard Avalara Tax Code for our Handling surcharges.

Refs #1525

Revision 27ab215f
Added by Pavan Rikhi 8 months ago

[#1525] Add TaxIncluded Field to Avalara Line Items

Add a liTaxIncluded field to the LineItem type in the Avalara module for
indicating that a line item's liTotalAmount field already includes the
tax amounts. This will be useful when tax transaction creation fails
during the Place Order process and we need to record tax after the
Customer has already been charged.

Refs #1525

Revision 067618b7
Added by Pavan Rikhi 8 months ago

[#1525] Properly Handle Error Responses from Avalara

Modify the makeRequest function in the Avalara module so that responses
with 4XX status codes have their body parsed instead of throwing
Req.HttpExceptions.

Refs #1525

Revision 662c43d8
Added by Pavan Rikhi 8 months ago

[#1525] Add Additonal Avalara Fields to the Config

Expand the Config type to include fields for the Avalara CompanyId,
CompanyCode, & the LocationCode to use in the `shipFrom` fields of
Transactions. Grab these settings from environmental variables when the
server is started.

Refs #1525

Revision cd12e66a
Added by Pavan Rikhi 8 months ago

[#1525] Add Database Fields for Avalara Codes

Add an avalaraCode field to the Customer model & an
avalaraTransactionCode field to the Order model for storing Avalara
identifiers.

Add teh AvalaraCustomerCode & AvalaraTransactionCode types for wrapping
the raw Avalara types and adding Persistent instances.

Refs #1525

Revision 0e929e1f
Added by Pavan Rikhi 8 months ago

[#1525] Add Helper Functions for Avalara Integration

Add a Avalara.addressFromLocation function for generating a minimal
AddressInfo from only a LocationCode.

Add toDollars & fromDollars functions for Cents conversion to the
Models.Fields module for converting Cents into a decimal-based dollar
representation.

Add lineItemToTaxCode & avalaraRegion functions to the Models.Utils
module for classifying tax status for Line Items & for converting
Database Regions into formats for Avalara.

Add an addressToAvalara function to the Route.CommonData module for
converting an AddressData type into an Avalara.AddressInfo.

Add a renderAvalaraError function to the Routes.Utils module for
generating prettified error text from an Avalara.ErrorInfo.

Refs #1525

Revision c2406a8e
Added by Pavan Rikhi 7 months ago

[#1525] Add Avalara Integration For Tax Calculations

This batch of changes adds the Avalara integration we previously
developed to the Checkout & Admin.Orders route handlers.

  • When the client requests/refreshes the details for the Checkout page,
    it now sends the entire address instead of just the state/country.
    This allows us to generate a tax quote based on their exact location
    and includes smaller tax jurisdictions like counties & cities.
  • When an Order is placed with a total above $0(before discounts), we
    create a new Avalara transaction for the order. At first we create an
    uncommitted transaction to get the tax amount, then charge the
    customer, and then commit the transaction. If charging fails, we void
    the transaction. If any of the Avalara requests fail, we enqueue the
    action as a Job for our worker pool.
  • When an admin refunds an order, we send a RefundTransaction to
    Avalara, enqueue the refund if the request fails.
  • Add a Routes.AvalaraUtils module for avalara functions that are shared
    between the routes & the Workers module.
  • Modify getCharges function to query avalara for a tax quote. It now
    takes the entire addressdata instead of just the shipping country &
    creates a SalesOrder tax transaction to determine the tax amount. This
    functionality is based on the current AvalaraStatus and a new argument
    passed to the function. The argument is used in the place order
    routes, since they create a transaction to get the tax quote.
  • Rework the Avalara module's request functions so they catch any
    exceptions and wrap them in the HttpException value of the WithError
    type.
  • Add FromJSON, ToJSON, Enum, & Bounded typeclass instances for various
    types in the Avalara module. The Aeson instances allow us to serialize
    the types for the action field of the Job table while the Enum &
    Bounded classes allow for easier testing. Add tests to ensure the
    FromJSON & ToJSON instances mirror each other.
  • Fix a bug checkouts for non-zero orders that did not meet Stripe's
    minimum amount($0.50). We eat the cost of these orders instead of
    losing money from processing fees.

Closes #1525

Revision 4919986d
Added by Pavan Rikhi 7 months ago

[#1525] Refactor Refreshing of Checkout Details

Avalara requires us to submit at least a Street Address, Zipcode, &
State in order to get a sales tax quote. This commit changes the message
raising & details refreshing code to reduce our AvaTax API calls &
ensure we have the required address fields before making an API request.

We add a FieldMsg type to the Address module, allowing us to specify if
an input field should update the model in an onChange or onInput event.
This allows us to not make an Avalara API call on every keystroke in the
Street or ZipCode fields.

We also modify the refreshDetails function in the Checkout module to
check for changes to the street or zipCode fields and to ensure the
fields are not blank.

Refs #1525

History

#1 Updated by Pavan Rikhi 12 months ago

  • Description updated (diff)

#2 Updated by Pavan Rikhi 8 months ago

  • Description updated (diff)
  • Subject changed from Integrate with a Sales Tax API to Integrate with Avalara Sales Tax API

#3 Updated by Pavan Rikhi 8 months ago

  • % Done changed from 0 to 70
  • Status changed from New to In Progress

Put on hold until the sandbox account gets configured enough to actually accept our requests.

#4 Updated by Pavan Rikhi 8 months ago

  • Target version changed from v0.10.0 - Integrations to v0.11.0 - Pre-Migration Requirements

#5 Updated by Pavan Rikhi 8 months ago

  • Target version changed from v0.11.0 - Pre-Migration Requirements to v2.0.0 - Post-Migration Issues

We won't be able to use Avalara until Jan 1st so we can push this back.

#6 Updated by Pavan Rikhi 7 months ago

  • Target version changed from v2.0.0 - Post-Migration Issues to v1.03.00 - Avalara & Server Improvements

#7 Updated by Pavan Rikhi 7 months ago

  • Description updated (diff)

#8 Updated by Pavan Rikhi 7 months ago

I added a Testing mode since we need to run Avalara for 2 weeks before using it for actual calculations. I need to void transactions after creating them in testing mode though.

Need to ask if I should commit them first, and what voiding reason I give. Ask on forums, along w the other questions I sent sam.

#9 Updated by Pavan Rikhi 7 months ago

Need to not hit the taxquote endpoint during the Place Order route since we hit it in withAvalaraTransaction. Also make sure the pretaxtotal doesn't include tax!

#10 Updated by Pavan Rikhi 7 months ago

  • % Done changed from 70 to 100
  • Status changed from In Progress to Closed

Also available in: Atom PDF