To illustrate how annoying it could be to have to collect out of state tax if you are fully exposed to that state's sales tax structure, here is an example of what you are supposed to do in Washington when charging Washington sales tax for an online purchase by a Washington customer.

Imagine this x50, with possibly each of the 50 having their data formatted differently and distributed differently.

The tax rate is the tax rate at the buyer's address.

You can get a CSV file from the state for each Washington county that gives address information. Here are the first few lines of the file for King County (the county that contains Seattle):

ADDR_LOW,ADDR_HIGH,ODD_EVEN,STREET,STATE,ZIP,PLUS4,PERIOD,CODE,RTA,PTBA_NAME,CEZ_NAME
301,399,O,10TH AVE N,WA,98001,6519,Q12018,1701,Y,King PTBA,
1500,1598,E,10TH CT NW,WA,98001,3868,Q12018,1702,Y,King PTBA,
1501,1599,O,10TH CT NW,WA,98001,3868,Q12018,1702,Y,King PTBA,
1,99,O,11TH AVE N,WA,98001,6518,Q12018,1701,Y,King PTBA,
200,298,E,11TH AVE N,WA,98001,6516,Q12018,1701,Y,King PTBA,
300,398,E,11TH AVE N,WA,98001,6514,Q12018,1701,Y,King PTBA,
301,399,O,11TH AVE N,WA,98001,6556,Q12018,1701,Y,King PTBA,

The first entry,

301,399,O,10TH AVE N,WA,98001,6519,Q12018,1701,Y,King PTBA,

tells you that it covers addresses from 301-399 on the odd side of 10th Ave N, in WA. These addresses all have zip code 98001-6519. The data is for Q1 2018. The location code for these addresses is 1701. The "Regional Sound Transit" indicator is 'Y' (this is a boolean). The "Public Transportation Benefit Area" is named "King PTBA". The name of the "Community Empowerment Zone" is blank.

The location code is the most important part as far as sales tax goes, because it is the location code that you use to look up the tax.

The most correct way, therefore, to figure out the tax is to get the customer street address, look it up in the address CSV for their county to get the location code and then look up the tax.

That is very annoying. People often spell their streets in ways that will not match the database. Consider:

10000,10098,E,MARTIN LUTHER KING JR WAY S,WA,98178,2045,Q12018,1726,Y,King PTBA,Duwamish
10462,10498,E,MARTIN LUTHER KING JR WAY S,WA,98178,2046,Q12018,1726,Y,King PTBA,Duwamish
12801,12899,O,MARTIN LUTHER KING JR WAY S,WA,98178,3513,Q12018,1700,Y,King PTBA,
12901,12999,O,MARTIN LUTHER KING JR WAY S,WA,98178,4611,Q12018,1700,Y,King PTBA,

Plenty of people are going to write that street as "MLK Way" instead of spelling it out. You need to deal with that.

You could go through all the address files, and make a mapping of zip+4 to location codes. For the vast majority of zip+4 codes, I believe that all covered addresses would map to the same location code.

The tax for each location code is in a CSV file from the state. Here is a sample:

Name,Code,State,Local,RTA,Rate,Effective Date,Expiration Date
KING COUNTY RTA,1700,0.065,0.035,0,0.1,20180101,20180331
ALGONA,1701,0.065,0.035,0,0.1,20180101,20180331
AUBURN/KING RTA,1702,0.065,0.035,0,0.1,20180101,20180331

The entry

KING COUNTY RTA,1700,0.065,0.035,0,0.1,20180101,20180331

says that location 1701, named "KING COUNTY RTA", has a state sales tax rate of 0.065, a local sales tax rate of 0.035, an RTA rate of 0, and an overall effective rate if 0.1. This entry is valid starting on 2018-01-01 and is value through 2018-03-31.

You can also get the rate data organized by zip. Here's a sample of that file:

98001,0000,1732,0.06500,0.03500,0.10000,20180101,20180331
98001,0001,1702,0.06500,0.03500,0.10000,20180101,20180331
98001,0006,1701,0.06500,0.03500,0.10000,20180101,20180331

The columns for that are 5 digit zip, 4 digit zip extension, location code, state tax rate, local tax rate, effective tax rate, and valid dates.

There's also a short form version of that CSV available:

98001,0000,0000,1732,0.06500,0.03500,0.10000,20180101,20180331
98001,0001,0001,1702,0.06500,0.03500,0.10000,20180101,20180331
98001,0002,0005,2720,0.06500,0.03400,0.09900,20180101,20180331

It's essentially the same, except that instead of each line covering exactly one zip+4, each line covers a range, so the first 3 columns are 5 digit zip, low 4 digit zip extension, high 4 digit zip extension.

It's important to note that locations have no inherent relationship with zip codes. There is no reason that a location could not include addresses in different zip codes, or that a given zip+4 could not include more than one location.

Fortunately, it turns out that no zip+4 crosses a location boundary, so in practice you can forget all that address file crap and just work from the zip files. BUT THERE IS NO GUARANTEE THAT THIS WILL ALWAYS APPLY, as far as I can tell.

Going with zip you still have the issue that many people don't know their full zip+4 offhand. If you require it you will induce some people to abandon the purchase.

What we did is use the long form zip to location data, and we take as the tax the maximum tax from all rows that match the zip the customer provides. If they just provide a 5 digit zip, they get the max rate for that 5 digit zip. If they specify zip+4, they get the correct rate for their location.

With the simplification of just going by zip this is not actually much of a pain to deal with. It does mean that every quarter I have to grab the updated rates for the next quarter and merge them into our tax rate database.

But that's because it is only one state. If I had to do it for 50 states, that would be a problem.

EDIT: Actually, there are zip+4's that cross location boundaries. Here is an example from the address file:

9100,9198,E,RENTON ISSAQUAH RD SE,WA,98027,5444,Q12018,4000,N,King PTBA,
9101,9199,O,RENTON ISSAQUAH RD SE,WA,98027,5444,Q12018,1700,Y,King PTBA,

The odd site of the street is in 1700, and the even side is in 4000. These two locs have different tax rates. From the rate CSV:

KING COUNTY RTA,1700,0.065,0.035,0,0.1,20180101,20180331
KING COUNTY NON-RTA,4000,0.065,0.021,0,0.086,20180101,20180331

So, if you just go by zip+4, you won't handle those people right.