Integrate with Avalara Sales Tax API
|Assignee:||Pavan Rikhi||% Done:|
|Target version:||v1.03.00 - Avalara & Server Improvements|
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:
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:
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:
Avalara module to the server, with an
avalaraRequest function in the
Server module which is responsible for running the requests. Use
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).
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?
[#1525] Add Initial Structuring for AvaTax Integration
Add the basic scaffolding for our AvaTax API integration to a new
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
[#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
[#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.
[#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.
[#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.
[#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.
[#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
Add teh AvalaraCustomerCode & AvalaraTransactionCode types for wrapping
the raw Avalara types and adding Persistent instances.
[#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
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.
[#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
- 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.
[#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.
#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.