TaxEngineAdapter Interface
Namespace
TaxEngineAdapter Methods
The TaxEngineAdapter class includes these methods.
processRequest(requestType)
Signature
global commercetax.TaxEngineResponse processRequest(commercetax.TaxEngineContext var1)
Parameters
- var1
- Type: TaxEngineContext
- Wrapper class that stores information about the type of a tax calculation request.
Return Value
Type: TaxEngineResponse
Generic interface representing a response from a tax engine.
TaxEngineAdapter Example Implementation
Namespace
Usage
The TaxEngineAdapter interface accepts information from the tax engine through the TaxEngineContext class. The interface evaluates the information to define tax in the response with details, such as tax amount and addresses. The response is used to update and create entities in the Salesforce org.
Use these steps to build a sample tax adapter implementation. Each tax adapter implementation varies based on your implementation requirements. Customize this example to suit your business requirements.
Example
-
The custom adapter class implements the TaxEngineAdapter interface. The processRequest method takes an instance of TaxEngineContext class and returns a response with the calculated tax details through the TaxDetailsResponse class or an error response through the ErrorResponse class.
1global virtual class AvalaraAdapter implements commercetax.TaxEngineAdapter { 2 global commercetax.TaxEngineResponse processRequest(commercetax.TaxEngineContext taxEngineContext) { 3 commercetax.RequestType requestType = taxEngineContext.getRequestType(); 4 if(requestType == commercetax.RequestType.CalculateTax){ 5 return CalculateTaxService.getTax(taxEngineContext); 6 } 7 else 8 return null; 9 } 10} -
This example shows the CalculateTaxService class.
1global class CalculateTaxService { 2 // ============================================================================ 3 // CONSTANT 4 // ============================================================================ 5 private static final String AVALARA_ENDPOINT_URL_SANDBOX = 'https://sandbox-rest.avatax.com/api/v2'; 6 // Avalara Endpoint URL Production 7 private static final String AVALARA_ENDPOINT_URL_PRODUCTION = 'https://rest.avatax.com/api/v2'; 8 private static final String TEST_REQUEST_BODY = '{ "id": -1, "code": "00000131", "companyId": -1, "date": "2017-02-03T00:00:00", "taxDate": "2017-02-03T00:00:00", "status": "Temporary", "type": "SalesOrder", "reconciled": false, "totalAmount": 4000, "totalExempt": 0, "totalTax": 290, "totalTaxable": 4000, "totalTaxCalculated": 290, "adjustmentReason": "NotAdjusted", "locked": false, "version": 1, "modifiedDate": "2017-02-03T12:18:18.7347388Z", "modifiedUserId": 53894, "lines": [ { "id": -1, "transactionId": -1, "lineNumber": "80241000000jNDCAA2", "discountAmount": 0, "exemptAmount": 0, "exemptCertId": 0, "isItemTaxable": true, "lineAmount": 1000, "reportingDate": "2017-02-03T00:00:00", "tax": 72.5, "taxableAmount": 1000, "taxCalculated": 72.5, "taxCode": "P0000000", "taxDate": "2017-02-03T00:00:00", "taxIncluded": false, "details": [ { "id": -1, "transactionLineId": -1, "transactionId": -1, "country": "US", "region": "CA", "exemptAmount": 0, "jurisCode": "06", "jurisName": "CALIFORNIA", "stateAssignedNo": "", "jurisType": "STA", "nonTaxableAmount": 0, "rate": 0.06, "tax": 60, "taxableAmount": 1000, "taxType": "Sales", "taxName": "CA STATE TAX", "taxAuthorityTypeId": 45, "taxCalculated": 60, "rateType": "General" }, { "id": -1, "transactionLineId": -1, "transactionId": -1, "country": "US", "region": "CA", "exemptAmount": 0, "jurisCode": "075", "jurisName": "SAN FRANCISCO", "stateAssignedNo": "", "jurisType": "CTY", "nonTaxableAmount": 0, "rate": 0.0025, "tax": 2.5, "taxableAmount": 1000, "taxType": "Sales", "taxName": "CA COUNTY TAX", "taxAuthorityTypeId": 45, "taxCalculated": 2.5, "rateType": "General" }, { "id": -1, "transactionLineId": -1, "transactionId": -1, "country": "US", "region": "CA", "exemptAmount": 0, "jurisCode": "EMTV0", "jurisName": "SAN FRANCISCO CO LOCAL TAX SL", "stateAssignedNo": "38", "jurisType": "STJ", "nonTaxableAmount": 0, "rate": 0.01, "tax": 10, "taxableAmount": 1000, "taxType": "Sales", "taxName": "CA SPECIAL TAX", "taxAuthorityTypeId": 45, "taxCalculated": 10, "rateType": "General" } ] } ]}'; 9 10 private static String getTestResponseString(){ 11 12 List<String> jsonResponse = new List<String> { 13 '"id": 0', 14 '"code": "testDocCode1231245984"', 15 '"companyId": 468039', 16 '"date": "2020-07-15"', 17 '"paymentDate": "2020-07-15"', 18 '"status": "Temporary"', 19 '"type": "SalesOrder"', 20 '"customerVendorCode": "testDocCode1234"', 21 '"customerCode": "testDocCode1234"', 22 '"reconciled": false', 23 '"totalAmount": 232', 24 '"totalExempt": 0', 25 '"totalDiscount": 0', 26 '"totalTax": 23.43', 27 '"totalTaxable": 232', 28 '"totalTaxCalculated": 23.43', 29 '"adjustmentReason": "NotAdjusted"', 30 '"locked": false', 31 '"version": 1', 32 '"exchangeRateEffectiveDate": "2020-07-15"', 33 '"exchangeRate": 1', 34 '"modifiedDate": "2020-08-13T11:19:20.4836636Z"', 35 '"modifiedUserId": 53894', 36 '"taxDate": "2020-07-15T00:00:00"', 37 '"lines": [{"id": 0,"transactionId": 0,"lineNumber": "1","discountAmount": 0,"exemptAmount": 0,"exemptCertId": 0,"isItemTaxable": true,"itemCode": "","lineAmount": 232,"quantity": 1,"reportingDate": "2020-07-15","tax": 23.43,"taxableAmount": 232,"taxCalculated": 23.43,"taxCode": "P0000000","taxCodeId": 8087,"taxDate": "2020-07-15","taxOverrideType": "None","taxOverrideAmount": 0,"taxIncluded": false,"details": [{"id": 0,"transactionLineId": 0,"transactionId": 0,"country": "US","region": "WA","exemptAmount": 0,"jurisCode": "53","jurisName": "WASHINGTON","stateAssignedNo": "","jurisType": "STA","jurisdictionType": "State","nonTaxableAmount": 0,"rate": 0.065,"tax": 15.08,"taxableAmount": 232,"taxType": "Sales","taxSubTypeId": "S","taxName": "WA STATE TAX","taxAuthorityTypeId": 45,"taxCalculated": 15.08,"rateType": "General","rateTypeCode": "G","unitOfBasis": "PerCurrencyUnit","isNonPassThru": false,"isFee": false},{"id": 0,"transactionLineId": 0,"transactionId": 0,"country": "US","region": "WA","exemptAmount": 0,"jurisCode": "033","jurisName": "KING","stateAssignedNo": "1700","jurisType": "CTY","jurisdictionType": "County","nonTaxableAmount": 0,"rate": 0,"tax": 0,"taxableAmount": 232,"taxType": "Sales","taxSubTypeId": "S","taxName": "WA COUNTY TAX","taxAuthorityTypeId": 45,"taxCalculated": 0,"rateType": "General","rateTypeCode": "G","unitOfBasis": "PerCurrencyUnit","isNonPassThru": false,"isFee": false}],"nonPassthroughDetails": [],"hsCode": "","costInsuranceFreight": 0,"vatCode": "","vatNumberTypeId": 0}]', 38 '"addresses": [{"id": 0,"transactionId": 0,"boundaryLevel": "Address","line1": "255 S. King Street","line2": "","line3": "","city": "Seattle","region": "WA","postalCode": "98104","country": "US","taxRegionId": 2109700,"latitude": "47.59821","longitude": "-122.33108"}]', 39 '"summary": [{"country": "US","region": "WA","jurisType": "State","jurisCode": "53","jurisName": "WASHINGTON","taxAuthorityType": 45,"stateAssignedNo": "","taxType": "Sales","taxSubType": "S","taxName": "WA STATE TAX","rateType": "General","taxable": 232,"rate": 0.065,"tax": 15.08,"taxCalculated": 15.08,"nonTaxable": 0,"exemption": 0},{"country": "US","region": "WA","jurisType": "County","jurisCode": "033","jurisName": "KING","taxAuthorityType": 45,"stateAssignedNo": "1700","taxType": "Sales","taxSubType": "S","taxName": "WA COUNTY TAX","rateType": "General","taxable": 232,"rate": 0,"tax": 0,"taxCalculated": 0,"nonTaxable": 0,"exemption": 0}]' 40 }; 41 return '{' + String.join(jsonResponse, ',') + '}'; 42 } 43 44 public static commercetax.TaxEngineResponse getTax(commercetax.TaxEngineContext taxEngineContext) 45 { 46 commercetax.CalculateTaxRequest request = (commercetax.CalculateTaxRequest)taxEngineContext.getRequest(); 47 commercetax.calculatetaxtype requestType = request.taxtype; 48 string referenceEntity = request.ReferenceEntityId; 49 try{ 50 List<commercetax.TaxLineItemRequest> listOfLines = request.lineItems; 51 if(!listOfLines.isEmpty()){ 52 HttpService sendHttpRequest = new HttpService(); 53 sendHttpRequest.addHeader('Content-type', 'application/json'); 54 String requestBody = AvalaraJSONBuilder.getInstance().frameJsonForGetTaxOrderItem(request); 55 sendHttpRequest.post('/transactions/create',requestBody); 56 //system.debug('Request '+requestBody); 57 String responseString = ''; 58 if(Test.isRunningTest()){ 59 responseString = getTestResponseString(); 60 } else{ 61 responseString = sendHttpRequest.getResponse().getBody(); 62 } 63 //system.debug(sendHttpRequest.getResponse()); 64 //system.debug('response'+responseString); 65 //responseString = TEST_REQUEST_BODY; 66 system.debug('Heap size used ' +Limits.getHeapSize()); 67 68 if(!responseString.contains('error')) 69 { 70 commercetax.CalculateTaxResponse response = new commercetax.CalculateTaxResponse(); 71 JsonSuccessParser jsonSuccessParserClass = JsonSuccessParser.parse(responseString); 72 response.setTaxTransactionType(request.taxTransactionType); 73 response.setDocumentCode(jsonSuccessParserClass.code); 74 response.setReferenceDocumentCode(jsonSuccessParserClass.referenceCode); 75 if(jsonSuccessParserClass.status == 'Temporary') { 76 response.setStatus(commercetax.TaxTransactionStatus.Uncommitted); 77 } 78 if(jsonSuccessParserClass.status == 'Committed') { 79 response.setStatus(commercetax.TaxTransactionStatus.Committed); 80 } 81 response.setTaxType(requestType); 82 commercetax.AmountDetailsResponse headerAmountResponse = new commercetax.AmountDetailsResponse(); 83 headerAmountResponse.setTotalAmountWithTax(jsonSuccessParserClass.totalAmount + jsonSuccessParserClass.totaltax); 84 headerAmountResponse.setExemptAmount(jsonSuccessParserClass.totalExempt); 85 headerAmountResponse.setTotalAmount(jsonSuccessParserClass.totalAmount); 86 headerAmountResponse.setTaxAmount(jsonSuccessParserClass.totalTax); 87 response.setAmountDetails(headerAmountResponse); 88 response.setStatusDescription(jsonSuccessParserClass.adjustmentReason); 89 response.setEffectiveDate(date.valueof(jsonSuccessParserClass.taxDate)); 90 response.setTransactionDate(date.valueof(jsonSuccessParserClass.transactionDate)); 91 response.setReferenceEntityId(referenceEntity); 92 response.setTaxTransactionId(jsonSuccessParserClass.id); 93 response.setCurrencyIsoCode(request.currencyIsoCode); 94 List<commercetax.LineItemResponse> lineItemResponses = new List<commercetax.LineItemResponse>(); 95 for(JsonSuccessParser.Lines linesToProcess: jsonSuccessParserClass.lines) 96 { 97 commercetax.LineItemResponse lineItemResponse = new commercetax.LineItemResponse(); 98 Double rateCalculated = 0.0; 99 List<commercetax.TaxDetailsResponse> taxDetailsResponses = new List<commercetax.TaxDetailsResponse>(); 100 for(JsonSuccessParser.details linesDetails : linesToProcess.details) 101 { 102 commercetax.TaxDetailsResponse taxDetailsResponse = new commercetax.TaxDetailsResponse(); 103 if(linesDetails.exemptAmount != 0){ 104 taxDetailsResponse.setExemptAmount(linesDetails.exemptAmount); 105 taxDetailsResponse.setExemptReason('Some reason we dont know'); 106 } 107 commercetax.ImpositionResponse imposition = new commercetax.ImpositionResponse(); 108 imposition.setSubType(linesDetails.taxName); 109 imposition.setType(linesDetails.ratetype); 110 imposition.setSubType(linesDetails.taxName); 111 taxDetailsResponse.setImposition(imposition); 112 commercetax.JurisdictionResponse jurisdiction = new commercetax.JurisdictionResponse(); 113 jurisdiction.setCountry(linesDetails.country); 114 jurisdiction.setRegion(linesDetails.region); 115 jurisdiction.setName(linesDetails.jurisName); 116 jurisdiction.setStateAssignedNumber(linesDetails.stateAssignedNo); 117 jurisdiction.setId(linesDetails.jurisCode); 118 jurisdiction.setLevel(linesDetails.jurisType); 119 taxDetailsResponse.setJurisdiction(jurisdiction); 120 rateCalculated += linesDetails.rate; 121 taxDetailsResponse.setRate(rateCalculated); 122 taxDetailsResponse.setTax(linesDetails.taxCalculated); 123 taxDetailsResponse.setTaxableAmount(linesDetails.taxableAmount); 124 taxDetailsResponse.setTaxAuthorityTypeId(String.valueOf(linesDetails.taxAuthorityTypeId)); 125 taxDetailsResponse.setTaxId(linesDetails.id); 126 taxDetailsResponse.setTaxRegionId(linesDetails.region); 127 taxDetailsResponses.add(taxDetailsResponse); 128 129 } 130 lineItemResponse.setTaxes(taxDetailsResponses); 131 lineItemResponse.setEffectiveDate(date.valueof(linesToProcess.taxDate)); 132 lineItemResponse.setIsTaxable(true); 133 commercetax.AmountDetailsResponse amountResponse = new commercetax.AmountDetailsResponse(); 134 amountResponse.setTaxAmount(linesToProcess.taxCalculated); 135 amountResponse.setTotalAmount(linesToProcess.lineAmount); 136 amountResponse.setTotalAmountWithTax(linesToProcess.lineAmount+linesToProcess.taxCalculated); 137 amountResponse.setExemptAmount(linesToProcess.exemptAmount); 138 lineItemResponse.setAmountDetails(amountResponse); 139 lineItemResponse.setIsTaxable(linesToProcess.isItemTaxable); 140 lineItemResponse.setProductCode(linesToProcess.itemCode); 141 lineItemResponse.setTaxCode(linesToProcess.taxCode); 142 lineItemResponse.setLineNumber(linesToProcess.lineNumber); 143 lineItemResponse.setQuantity(linesToProcess.quantity); 144 lineItemResponses.add(lineItemResponse); 145 } 146 response.setLineItems(lineItemResponses); 147 return response; 148 } 149 else 150 { 151 JsonErrorParser jsonErrorParserClass = JsonErrorParser.parse(responseString); 152 String message = null; 153 if(String.isNotBlank(jsonErrorParserClass.error.message)) 154 { 155 message=jsonErrorParserClass.error.message; 156 }else{ 157 String errorMessage = ''; 158 for(JsonErrorParser.cls_details messageString : jsonErrorParserClass.error.details) 159 { 160 if(String.isNotBlank(messageString.message) ) 161 { 162 errorMessage = messageString.message; 163 } 164 } 165 message = errorMessage; 166 } 167 return new commercetax.ErrorResponse(commercetax.resultcode.TaxEngineError, '501', message); 168 169 } 170 }else return null; 171 } 172 catch (Exception e) 173 { 174 throw e; 175 } 176 } 177} -
In the HttpService class, replace the test value in the endpoint variable with the name of the TaxTypedNamedCredential record. This class contains the credentials that are required to access your Avalara account through Salesforce.
1public with sharing class HttpService 2{ 3 // Attribute to implement singleton pattern for Order Product Service class 4 private static HttpService httpServiceInstance; 5 6 // VARIABLES 7 8 private HttpResponse httpResponse; 9 private Map<String,String> mapOfHeaderParameter = new Map<String,String>(); 10 private enum Method {GET, POST} 11 12 /** 13 * @name getInstance 14 * @description get an Instance of Service class 15 * @params NA 16 * @return Http Service Class Instance 17 */ 18 public static HttpService getInstance() 19 { 20 if (NULL == httpServiceInstance) 21 { 22 httpServiceInstance = new HttpService(); 23 } 24 return httpServiceInstance; 25 } 26 27 /** 28 * @name get 29 * @description Get Method to get a HTTP request 30 */ 31 public void get(String endPoint) 32 { 33 send(newRequest(Method.GET, endPoint)); 34 } 35 36 /** 37 * @name post 38 * @description Post Method to Post a HTTP request 39 */ 40 public void post(String path, String requestBody) 41 { 42 String endPoint = 'callout:commerce.tax.TaxTypedNamedCredential:test'+path; 43 send(newRequest(Method.POST, endPoint, requestBody)); 44 } 45 46 /** 47 * @name addHeader 48 * @description addHeader Methods to add all the defualt Header's required fo rthe request 49 */ 50 public void addHeader(String name, String value) 51 { 52 mapOfHeaderParameter.put(name, value); 53 } 54 55 /** 56 * @name setHeader 57 * @description setHeader Methods to set setHeader for the request 58 */ 59 private void setHeader(HttpRequest request) 60 { 61 for(String headerValue : mapOfHeaderParameter.keySet()) 62 { 63 request.setHeader(headerValue, mapOfHeaderParameter.get(headerValue)); 64 } 65 } 66 /** 67 * @name newRequest 68 * @description newRequest Methods to make a new request 69 */ 70 private HttpRequest newRequest(Method method, String endPoint) 71 { 72 return newRequest(method, endPoint, NULL); 73 } 74 75 /** 76 * @name newRequest 77 * @description newRequest Methods to make a new request 78 */ 79 private HttpRequest newRequest(Method method, String endPoint, String requestBody) 80 { 81 HttpRequest request = new HttpRequest(); 82 request.setMethod(Method.name()); 83 setHeader(request); 84 request.setEndpoint(endPoint); 85 if (String.isNotBlank(requestBody)) 86 { 87 request.setBody(requestBody); 88 } 89 request.setTimeout(120000); 90 return request; 91 } 92 93 /** 94 * @name send 95 * @description send Methods to send a request 96 */ 97 private void send(HttpRequest request) 98 { 99 try 100 { 101 Http http = new Http(); 102 httpResponse = http.send(request); 103 } 104 catch(System.CalloutException e) 105 { 106 system.debug('callout exception happened' + e.getMessage()); 107 } 108 catch(Exception e) 109 { 110 system.debug('callout did not happen' + e.getMessage()); 111 } 112 } 113 114 /** 115 * @name getResponse 116 * @description getResponse Method to get the Response 117 */ 118 public HttpResponse getResponse() 119 { 120 return httpResponse; 121 } 122 123 /** 124 * @name getResponseToString 125 * @description getResponse Method to get the Responses 126 */ 127 public String getResponseToString() 128 { 129 return getResponse().toString(); 130 } 131} -
Parse the JsonSuccessParser response object by using the AvalaraJSONBuilder class to build the response for your adapter.
This example shows the JsonSuccessParser class.
1global with sharing class JsonSuccessParser 2{ 3 public static void consumeObject(JSONParser parser) 4 { 5 Integer depth = 0; 6 do { 7 JSONToken curr = parser.getCurrentToken(); 8 if (curr == JSONToken.START_OBJECT || 9 curr == JSONToken.START_ARRAY) { 10 depth++; 11 } else if (curr == JSONToken.END_OBJECT || 12 curr == JSONToken.END_ARRAY) { 13 depth--; 14 } 15 } while (depth > 0 && parser.nextToken() != null); 16 } 17 18 public class Addresses { 19 public String id {get;set;} 20 public String transactionId {get;set;} 21 public String boundaryLevel {get;set;} 22 public String line1 {get;set;} 23 public String city {get;set;} 24 public String region {get;set;} 25 public String postalCode {get;set;} 26 public String country {get;set;} 27 public Integer taxRegionId {get;set;} 28 29 public Addresses(JSONParser parser) { 30 while (parser.nextToken() != JSONToken.END_OBJECT) { 31 if (parser.getCurrentToken() == JSONToken.FIELD_NAME) { 32 String text = parser.getText(); 33 if (parser.nextToken() != JSONToken.VALUE_NULL) { 34 if (text == 'id') { 35 id = parser.getText(); 36 } else if (text == 'transactionId') { 37 transactionId = parser.getText(); 38 } else if (text == 'boundaryLevel') { 39 boundaryLevel = parser.getText(); 40 } else if (text == 'line1') { 41 line1 = parser.getText(); 42 } else if (text == 'city') { 43 city = parser.getText(); 44 } else if (text == 'region') { 45 region = parser.getText(); 46 } else if (text == 'postalCode') { 47 postalCode = parser.getText(); 48 } else if (text == 'country') { 49 country = parser.getText(); 50 } else if (text == 'taxRegionId') { 51 taxRegionId = parser.getIntegerValue(); 52 } else { 53 consumeObject(parser); 54 } 55 } 56 } 57 } 58 } 59 } 60 61 public class Details { 62 public String id {get;set;} 63 public String transactionLineId {get;set;} 64 public String transactionId {get;set;} 65 public String country {get;set;} 66 public String region {get;set;} 67 public Integer exemptAmount {get;set;} 68 public String jurisCode {get;set;} 69 public String jurisName {get;set;} 70 public String stateAssignedNo {get;set;} 71 public String jurisType {get;set;} 72 public Integer nonTaxableAmount {get;set;} 73 public Double rate {get;set;} 74 public Double tax {get;set;} 75 public Integer taxableAmount {get;set;} 76 public String taxType {get;set;} 77 public String taxName {get;set;} 78 public Integer taxAuthorityTypeId {get;set;} 79 public Double taxCalculated {get;set;} 80 public String rateType {get;set;} 81 82 public Details(JSONParser parser) { 83 while (parser.nextToken() != JSONToken.END_OBJECT) { 84 if (parser.getCurrentToken() == JSONToken.FIELD_NAME) { 85 String text = parser.getText(); 86 if (parser.nextToken() != JSONToken.VALUE_NULL) { 87 if (text == 'id') { 88 id = parser.getText(); 89 } else if (text == 'transactionLineId') { 90 transactionLineId = parser.getText(); 91 } else if (text == 'transactionId') { 92 transactionId = parser.getText(); 93 } else if (text == 'country') { 94 country = parser.getText(); 95 } else if (text == 'region') { 96 region = parser.getText(); 97 } else if (text == 'exemptAmount') { 98 exemptAmount = parser.getIntegerValue(); 99 } else if (text == 'jurisCode') { 100 jurisCode = parser.getText(); 101 } else if (text == 'jurisName') { 102 jurisName = parser.getText(); 103 } else if (text == 'stateAssignedNo') { 104 stateAssignedNo = parser.getText(); 105 } else if (text == 'jurisType') { 106 jurisType = parser.getText(); 107 } else if (text == 'nonTaxableAmount') { 108 nonTaxableAmount = parser.getIntegerValue(); 109 } else if (text == 'rate') { 110 rate = parser.getDoubleValue(); 111 } else if (text == 'tax') { 112 tax = parser.getDoubleValue(); 113 } else if (text == 'taxableAmount') { 114 taxableAmount = parser.getIntegerValue(); 115 } else if (text == 'taxType') { 116 taxType = parser.getText(); 117 } else if (text == 'taxName') { 118 taxName = parser.getText(); 119 } else if (text == 'taxAuthorityTypeId') { 120 taxAuthorityTypeId = parser.getIntegerValue(); 121 } else if (text == 'taxCalculated') { 122 taxCalculated = parser.getDoubleValue(); 123 } else if (text == 'rateType') { 124 rateType = parser.getText(); 125 } else { 126 consumeObject(parser); 127 } 128 } 129 } 130 } 131 } 132 } 133 134 public class Messages { 135 public String summary {get;set;} 136 public String details {get;set;} 137 public String refersTo {get;set;} 138 public String severity {get;set;} 139 public String source {get;set;} 140 141 public Messages(JSONParser parser) { 142 while (parser.nextToken() != JSONToken.END_OBJECT) { 143 if (parser.getCurrentToken() == JSONToken.FIELD_NAME) { 144 String text = parser.getText(); 145 if (parser.nextToken() != JSONToken.VALUE_NULL) { 146 if (text == 'summary') { 147 summary = parser.getText(); 148 } else if (text == 'details') { 149 details = parser.getText(); 150 } else if (text == 'refersTo') { 151 refersTo = parser.getText(); 152 } else if (text == 'severity') { 153 severity = parser.getText(); 154 } else if (text == 'source') { 155 source = parser.getText(); 156 } else { 157 consumeObject(parser); 158 } 159 } 160 } 161 } 162 } 163 } 164 165 public String id {get;set;} 166 public String code {get;set;} 167 public String referenceCode {get;set;} 168 public Integer companyId {get;set;} 169 public String taxDate {get;set;} 170 public String transactionDate {get;set;} 171 public String status {get;set;} 172 public String type_Z {get;set;} // in json: type 173 public Boolean reconciled {get;set;} 174 public Integer totalAmount {get;set;} 175 public Integer totalExempt {get;set;} 176 public Double totalTax {get;set;} 177 public Integer totalTaxable {get;set;} 178 public Double totalTaxCalculated {get;set;} 179 public String adjustmentReason {get;set;} 180 public Boolean locked {get;set;} 181 public Integer version {get;set;} 182 public String modifiedDate {get;set;} 183 public Integer modifiedUserId {get;set;} 184 public List<Lines> lines {get;set;} 185 public List<Addresses> addresses {get;set;} 186 public List<Summary> summary {get;set;} 187 public List<Messages> messages {get;set;} 188 189 public JsonSuccessParser(JSONParser parser) { 190 while (parser.nextToken() != JSONToken.END_OBJECT) { 191 if (parser.getCurrentToken() == JSONToken.FIELD_NAME) { 192 String text = parser.getText(); 193 if (parser.nextToken() != JSONToken.VALUE_NULL) { 194 if (text == 'id') { 195 id = parser.getText(); 196 } else if (text == 'code') { 197 code = parser.getText(); 198 } else if (text == 'referenceCode'){ 199 referenceCode = parser.getText(); 200 } else if (text == 'companyId') { 201 companyId = parser.getIntegerValue(); 202 } else if (text == 'taxDate') { 203 taxDate = parser.getText(); 204 } else if (text == 'date') { 205 transactionDate = parser.getText(); 206 } else if (text == 'status') { 207 status = parser.getText(); 208 } else if (text == 'type') { 209 type_Z = parser.getText(); 210 } else if (text == 'reconciled') { 211 reconciled = parser.getBooleanValue(); 212 } else if (text == 'totalAmount') { 213 totalAmount = parser.getIntegerValue(); 214 } else if (text == 'totalExempt') { 215 totalExempt = parser.getIntegerValue(); 216 } else if (text == 'totalTax') { 217 totalTax = parser.getDoubleValue(); 218 } else if (text == 'totalTaxable') { 219 totalTaxable = parser.getIntegerValue(); 220 } else if (text == 'totalTaxCalculated') { 221 totalTaxCalculated = parser.getDoubleValue(); 222 } else if (text == 'adjustmentReason') { 223 adjustmentReason = parser.getText(); 224 } else if (text == 'locked') { 225 locked = parser.getBooleanValue(); 226 } else if (text == 'version') { 227 version = parser.getIntegerValue(); 228 } else if (text == 'modifiedDate') { 229 modifiedDate = parser.getText(); 230 } else if (text == 'modifiedUserId') { 231 modifiedUserId = parser.getIntegerValue(); 232 } else if (text == 'lines') { 233 lines = new List<Lines>(); 234 while (parser.nextToken() != JSONToken.END_ARRAY) { 235 lines.add(new Lines(parser)); 236 } 237 } else if (text == 'addresses') { 238 addresses = new List<Addresses>(); 239 while (parser.nextToken() != JSONToken.END_ARRAY) { 240 addresses.add(new Addresses(parser)); 241 } 242 } else if (text == 'summary') { 243 summary = new List<Summary>(); 244 while (parser.nextToken() != JSONToken.END_ARRAY) { 245 summary.add(new Summary(parser)); 246 } 247 } else if (text == 'messages') { 248 messages = new List<Messages>(); 249 while (parser.nextToken() != JSONToken.END_ARRAY) { 250 messages.add(new Messages(parser)); 251 } 252 } else { 253 consumeObject(parser); 254 } 255 } 256 } 257 } 258 } 259 260 public class Summary { 261 public String country {get;set;} 262 public String region {get;set;} 263 public String jurisType {get;set;} 264 public String jurisCode {get;set;} 265 public String jurisName {get;set;} 266 public Integer taxAuthorityType {get;set;} 267 public String stateAssignedNo {get;set;} 268 public String taxType {get;set;} 269 public String taxName {get;set;} 270 public String taxGroup {get;set;} 271 public String rateType {get;set;} 272 public Integer taxable {get;set;} 273 public Double rate {get;set;} 274 public Double tax {get;set;} 275 public Double taxCalculated {get;set;} 276 public Integer nonTaxable {get;set;} 277 public Integer exemption {get;set;} 278 279 public Summary(JSONParser parser) { 280 while (parser.nextToken() != JSONToken.END_OBJECT) { 281 if (parser.getCurrentToken() == JSONToken.FIELD_NAME) { 282 String text = parser.getText(); 283 if (parser.nextToken() != JSONToken.VALUE_NULL) { 284 if (text == 'country') { 285 country = parser.getText(); 286 } else if (text == 'region') { 287 region = parser.getText(); 288 } else if (text == 'jurisType') { 289 jurisType = parser.getText(); 290 } else if (text == 'jurisCode') { 291 jurisCode = parser.getText(); 292 } else if (text == 'jurisName') { 293 jurisName = parser.getText(); 294 } else if (text == 'taxAuthorityType') { 295 taxAuthorityType = parser.getIntegerValue(); 296 } else if (text == 'stateAssignedNo') { 297 stateAssignedNo = parser.getText(); 298 } else if (text == 'taxType') { 299 taxType = parser.getText(); 300 } else if (text == 'taxName') { 301 taxName = parser.getText(); 302 } else if (text == 'taxGroup') { 303 taxGroup = parser.getText(); 304 } else if (text == 'rateType') { 305 rateType = parser.getText(); 306 } else if (text == 'taxable') { 307 taxable = parser.getIntegerValue(); 308 } else if (text == 'rate') { 309 rate = parser.getDoubleValue(); 310 } else if (text == 'tax') { 311 tax = parser.getDoubleValue(); 312 } else if (text == 'taxCalculated') { 313 taxCalculated = parser.getDoubleValue(); 314 } else if (text == 'nonTaxable') { 315 nonTaxable = parser.getIntegerValue(); 316 } else if (text == 'exemption') { 317 exemption = parser.getIntegerValue(); 318 } else { 319 consumeObject(parser); 320 } 321 } 322 } 323 } 324 } 325 } 326 327 public class Lines { 328 public String id {get;set;} 329 public String transactionId {get;set;} 330 public String lineNumber {get;set;} 331 public Integer discountAmount {get;set;} 332 public Integer exemptAmount {get;set;} 333 public Integer exemptCertId {get;set;} 334 public Boolean isItemTaxable {get;set;} 335 public Integer lineAmount {get;set;} 336 public Double quantity {get;set;} 337 public String reportingDate {get;set;} 338 public Double tax {get;set;} 339 public Integer taxableAmount {get;set;} 340 public Double taxCalculated {get;set;} 341 public String taxCode {get;set;} 342 public String taxDate {get;set;} 343 public Boolean taxIncluded {get;set;} 344 public List<Details> details {get;set;} 345 public String itemCode {get;set;} 346 public Lines(JSONParser parser) { 347 while (parser.nextToken() != JSONToken.END_OBJECT) { 348 if (parser.getCurrentToken() == JSONToken.FIELD_NAME) { 349 String text = parser.getText(); 350 if (parser.nextToken() != JSONToken.VALUE_NULL) { 351 if (text == 'id') { 352 id = parser.getText(); 353 } else if (text == 'transactionId') { 354 transactionId = parser.getText(); 355 }else if (text == 'itemCode') { 356 itemCode = parser.getText(); 357 }else if (text == 'lineNumber') { 358 lineNumber = parser.getText(); 359 } else if (text == 'discountAmount') { 360 discountAmount = parser.getIntegerValue(); 361 } else if (text == 'exemptAmount') { 362 exemptAmount = parser.getIntegerValue(); 363 } else if (text == 'exemptCertId') { 364 exemptCertId = parser.getIntegerValue(); 365 } else if (text == 'isItemTaxable') { 366 isItemTaxable = parser.getBooleanValue(); 367 } else if (text == 'lineAmount') { 368 lineAmount = parser.getIntegerValue(); 369 } else if (text == 'quantity') { 370 quantity = parser.getDoubleValue(); 371 } else if (text == 'reportingDate') { 372 reportingDate = parser.getText(); 373 } else if (text == 'tax') { 374 tax = parser.getDoubleValue(); 375 } else if (text == 'taxableAmount') { 376 taxableAmount = parser.getIntegerValue(); 377 } else if (text == 'taxCalculated') { 378 taxCalculated = parser.getDoubleValue(); 379 } else if (text == 'taxCode') { 380 taxCode = parser.getText(); 381 } else if (text == 'taxDate') { 382 taxDate = parser.getText(); 383 } else if (text == 'taxIncluded') { 384 taxIncluded = parser.getBooleanValue(); 385 } else if (text == 'details') { 386 details = new List<Details>(); 387 while (parser.nextToken() != JSONToken.END_ARRAY) { 388 details.add(new Details(parser)); 389 } 390 } else { 391 consumeObject(parser); 392 } 393 } 394 } 395 } 396 } 397 } 398 399 400 public static JsonSuccessParser parse(String json) 401 { 402 return new JsonSuccessParser(System.JSON.createParser(json)); 403 } 404}Prepare your JSON request to call the Avalara endpoint by using the AvalaraJSONBuilder class.
1public with sharing class AvalaraJSONBuilder 2{ 3 private static AvalaraJSONBuilder avalaraJSONBuilderInstance; 4 5 public static AvalaraJSONBuilder getInstance() 6 { 7 if (NULL == avalaraJSONBuilderInstance) 8 { 9 avalaraJSONBuilderInstance = new AvalaraJSONBuilder(); 10 } 11 return avalaraJSONBuilderInstance; 12 } 13 14 public String frameJsonForGetTaxOrderItem(commercetax.CalculateTaxRequest calculateTaxRequest) 15 { 16 try 17 { 18 Id accountid = null; 19 if(calculateTaxRequest.CustomerDetails.AccountId != null && calculateTaxRequest.CustomerDetails.AccountId != '') 20 accountid = Id.valueof(calculateTaxRequest.CustomerDetails.AccountId); 21 JSONGenerator jsonGeneratorInstance = JSON.createGenerator(true); 22 jsonGeneratorInstance.writeStartObject(); 23 String type = null; 24 if(calculateTaxRequest.taxtype == commercetax.CalculateTaxType.Actual) 25 type ='SalesInvoice'; 26 else type = 'SalesOrder'; 27 jsonGeneratorInstance.writeStringField('type', type); 28 if(calculateTaxRequest.SellerDetails != null) 29 jsonGeneratorInstance.writeStringField('companyCode', calculateTaxRequest.SellerDetails.code); 30 else 31 jsonGeneratorInstance.writeStringField('companyCode', 'billing2'); 32 if(calculateTaxRequest.isCommit != null) { 33 jsonGeneratorInstance.writeBooleanField('commit', calculateTaxRequest.isCommit); 34 } 35 if(calculateTaxRequest.documentcode != null){ 36 jsonGeneratorInstance.writeStringField('code', calculateTaxRequest.documentcode); 37 }else if(calculateTaxRequest.referenceEntityId != null) { 38 jsonGeneratorInstance.writeStringField('code', calculateTaxRequest.referenceEntityId); 39 } 40 if(calculateTaxRequest.CustomerDetails.code == null && accountid !=null) { 41 Account acc = [select id, name from account where id=:accountid]; 42 jsonGeneratorInstance.writeStringField('customerCode', acc.name); 43 } else { 44 jsonGeneratorInstance.writeStringField('customerCode', calculateTaxRequest.CustomerDetails.code); 45 } 46 if(calculateTaxRequest.EffectiveDate == null) 47 jsonGeneratorInstance.writeDateField('date', system.today()); 48 else 49 jsonGeneratorInstance.writeDateTimeField('date', calculateTaxRequest.EffectiveDate); 50 51 jsonGeneratorInstance.writeFieldName('lines'); 52 jsonGeneratorInstance.writeStartArray(); 53 for(integer i=0;i<1;i++){ 54 for(Commercetax.TaxLineItemRequest lineItem : calculateTaxRequest.LineItems) 55 { 56 jsonGeneratorInstance.writeStartObject(); 57 if(lineItem.linenumber != null){ 58 jsonGeneratorInstance.writeStringField('number', lineItem.linenumber); 59 } 60 jsonGeneratorInstance.writeNumberField('quantity', lineItem.Quantity); 61 jsonGeneratorInstance.writeNumberField('amount', (lineItem.Amount)); 62 jsonGeneratorInstance.writeStringField('taxCode',lineItem.taxCode); 63 64 jsonGeneratorInstance.writeFieldName('addresses'); 65 jsonGeneratorInstance.writeStartObject(); 66 jsonGeneratorInstance.writeFieldName('ShipFrom'); 67 jsonGeneratorInstance.writeStartObject(); 68 jsonGeneratorInstance.writeStringField('line1', lineItem.addresses.shipfrom.street); 69 jsonGeneratorInstance.writeStringField('line2', lineItem.addresses.shipfrom.street); 70 jsonGeneratorInstance.writeStringField('city', lineItem.addresses.shipfrom.city); 71 jsonGeneratorInstance.writeStringField('region', lineItem.addresses.shipfrom.state); 72 jsonGeneratorInstance.writeStringField('country', lineItem.addresses.shipfrom.country); 73 jsonGeneratorInstance.writeStringField('postalCode',lineItem.addresses.shipfrom.postalcode); 74 jsonGeneratorInstance.writeEndObject(); 75 76 jsonGeneratorInstance.writeFieldName('ShipTo'); 77 jsonGeneratorInstance.writeStartObject(); 78 jsonGeneratorInstance.writeStringField('line1', lineItem.addresses.shipto.street); 79 jsonGeneratorInstance.writeStringField('line2', lineItem.addresses.shipto.street); 80 jsonGeneratorInstance.writeStringField('city', lineItem.addresses.shipto.city); 81 jsonGeneratorInstance.writeStringField('region', lineItem.addresses.shipto.state); 82 jsonGeneratorInstance.writeStringField('country', lineItem.addresses.shipto.country); 83 jsonGeneratorInstance.writeStringField('postalCode',lineItem.addresses.shipto.postalcode); 84 jsonGeneratorInstance.writeEndObject(); 85 86 jsonGeneratorInstance.writeFieldName('pointOfOrderOrigin'); 87 jsonGeneratorInstance.writeStartObject(); 88 jsonGeneratorInstance.writeStringField('line1', lineItem.addresses.soldto.street); 89 jsonGeneratorInstance.writeStringField('line2', lineItem.addresses.soldto.street); 90 jsonGeneratorInstance.writeStringField('city', lineItem.addresses.soldto.city); 91 jsonGeneratorInstance.writeStringField('region', lineItem.addresses.soldto.state); 92 jsonGeneratorInstance.writeStringField('country', lineItem.addresses.soldto.country); 93 jsonGeneratorInstance.writeStringField('postalCode',lineItem.addresses.soldto.postalcode); 94 jsonGeneratorInstance.writeEndObject(); 95 96 97 if(lineItem.effectiveDate != null) 98 { 99 jsonGeneratorInstance.writeFieldName('taxOverride'); 100 jsonGeneratorInstance.writeStartObject(); 101 jsonGeneratorInstance.writeDateTimeField('taxDate', lineItem.effectiveDate); 102 jsonGeneratorInstance.writeEndObject(); 103 } 104 jsonGeneratorInstance.writeEndObject(); 105 jsonGeneratorInstance.writeEndObject(); 106 } 107 } 108 jsonGeneratorInstance.writeEndArray(); 109 jsonGeneratorInstance.writeEndObject(); 110 return jsonGeneratorInstance.getAsString(); 111 } 112 catch (Exception e) 113 { 114 throw e; 115 } 116 } 117} -
Use the JsonErrorParser class to extract the error details, if any.
1global with sharing class JsonErrorParser 2{ 3 public cls_error error; 4 5 public class cls_error 6 { 7 public String code; 8 public String message; 9 public String target; 10 public cls_details[] details; 11 } 12 13 public class cls_details 14 { 15 public String code; 16 public String message; 17 public String description; 18 public String faultCode; 19 public String helpLink; 20 public String severity; 21 } 22 public static JsonErrorParser parse(String json) 23 { 24 return (JsonErrorParser) System.JSON.deserialize(json, JsonErrorParser.class); 25 } 26}
Tax Mappings for Quotes and Orders
Tax callout extensions are supported for the Quote, QuoteLineItem, Order, and OrderItem objects to include additional fields to tax requests. You must manually write back tax response extensions to the objects. See custom metadata types to specify all your tax mapping definitions.
Request Mappings for Header Attributes
This table defines the request mappings between the header attributes of a tax callout and fields of applicable quote and order objects.
| Header Attributes | Quote Mapping | Order Mapping |
|---|---|---|
| currencyIsoCode | If multi-currency is enabled, then this value is Quote.CurrencyISOCode. Otherwise, this value is NULL. | If multi-currency is enabled, then this value is Order.CurrencyISOCode. Otherwise, this value is NULL. |
| isCommit | False | False |
| referenceEntityId | Quote.ID | Order.ID |
| taxEngineId | TaxTreatment.TaxEngine.ID | TaxTreatment.TaxEngine.ID |
| transactionDate | Current System Date | System Date |
| sellerDetails | NULL | |
| code | TaxEngine.SellerCode | |
| customerDetails | ||
| accountId | Quote.AccountId | Order.AccountId |
| code | NULL | NULL |
| exemptionNo | NULL | NULL |
| exemptionReason | NULL | NULL |
| taxType | Estimated | Estimated |
| taxTransactionType | NULL | NULL |
| effectiveDate | NULL | NULL |
| addresses | ||
| billTo | NULL | NULL |
| shipTo | NULL | NULL |
| shipFrom | NULL | NULL |
| soldTo | NULL | NULL |
| taxEngineAddress | TaxEngine.Address | TaxEngine.Address |
| referenceDocumentCode | NULL | NULL |
| description | NULL | NULL |
| documentCode | Quote.ID-TaxEngineId | Order.ID-TaxEngineId |
| shouldVoid | FALSE | FALSE |
| lineItems | Refer to the next line attributes section. | Refer to the next line attributes section. |
Request Mappings for Line Attributes
This table defines the request mappings between the line attributes of a tax callout and fields of applicable quote line items and order products.
| Line Attributes | Quote Line Item Mapping | Order Product Mapping |
|---|---|---|
| taxCode | TaxTreatment.TaxCode | TaxTreatment.TaxCode |
| productCode | TaxTreatment.ProductCode | TaxTreatment.ProductCode |
| productId | QuoteLineItem.Product2.Id | OrderItem.Product2.Id |
| amount | QuoteLineItem.TotalPrice | OrderItem.TotalPrice |
| effectiveDate | Current System Date | Current System Date |
| lineNumber | QuoteLineItem.Id | OrderItem.Id |
| description | NULL | NULL |
| quantity | QuoteLineItem.Quantity | OrderItem.Quantity |
| addresses | ||
| billTo | Quote.BillingAddress. If Quote.BillingAddress is null, then this value is Quote.Account.BillingAddress. | Order.BillingAddress |
| shipTo | Quote.ShippingAddress. If Quote.ShippingAddress is null, then this value is Quote.Account.ShippingAddress. | Order.ShippingAddress |
| shipFrom | NULL | NULL |
| soldTo | NULL | NULL |
| productsku | QuoteLineItem.Product2.ProductCode | OrderItem.Product2.ProductCode |
| referenceDocumentCode | NULL | NULL |
Response Mappings for Header Attributes
This table defines the response mappings between the header attributes of a tax callout and fields of applicable objects. Most response data is used for tax calculation and isn’t persisted on quote or order records.
| Header Attributes | Quote Mapping | Order Mapping |
|---|---|---|
| currencyIsoCode | Quote.CurrencyISOCode | Order.CurrencyISOCode |
| isCommit | Not returned. | Not returned. |
| referenceEntityId | Quote.ID | Order.ID |
| taxEngineId | TaxTreatment.TaxEngine.ID | TaxTreatment.TaxEngine.ID |
| transactionDate | System Date | System Date |
| sellerDetails | Not returned. | Not returned. |
| code | Not returned. | Not returned. |
| customerDetails | Not returned. | Not returned. |
| accountId | Not returned. | Not returned. |
| code | Not returned. | Not returned. |
| exemptionNo | Not returned. | Not returned. |
| exemptionReason | Not returned. | Not returned. |
| taxType | Estimated | Estimated |
| taxTransactionType | Not returned. | Not returned. |
| effectiveDate | System Date | System Date |
| addresses | ||
| billTo | Not returned. | Not returned. |
| shipTo | locationCode -> locationCode | locationCode -> locationCode |
| shipFrom | Not returned. | Not returned. |
| soldTo | Not returned. | Not returned. |
| taxEngineAddress | Not returned. | Not returned. |
| referenceDocumentCode | Not returned. | Not returned. |
| description | Not returned. | Not returned. |
| documentCode | Quote.ID-TaxEngineId | Order.ID-TaxEngineId |
| status | Uncommitted | Uncommitted |
| taxEngineLogs | Not returned. | Not returned. |
| resultCode | Not returned. | Not returned. |
| transactionDate | System Date | System Date |
| amountDetails | ||
| exemptAmount | Actual exemptAmount from response. | Actual exemptAmount from response. |
| taxAmount | Actual taxAmount from response. | Actual taxAmount from response. |
| totalAmount | Quote.Subtotal | Order.Subtotal |
| totalAmountWithTax | TaxAmount + TotalAmount | TaxAmount + TotalAmount |
| lineItems | Refer to the next line attributes section. | Refer to the next line attributes section. |
Response Mappings for Line Attributes
This table defines the response mappings between the line attributes of a tax callout and fields of applicable objects.
| Line Attributes | Quote Line Item Mapping | Order Product Mapping |
|---|---|---|
| taxCode | TaxTreatment.TaxCode | TaxTreatment.TaxCode |
| productCode | TaxTreatment.ProductCode | TaxTreatment.ProductCode |
| productId | Not returned. | Not returned. |
| amountDetails | ||
| exemptAmount | Actual exemptAmount from response | Actual exemptAmount from response |
| taxAmount | Actual taxAmount from response | Actual taxAmount from response |
| totalAmount | QuoteLineItem.Subtotal | OrderItem.Subtotal |
| totalAmountWithTax | TaxAmount + TotalAmount | TaxAmount + TotalAmount |
| effectiveDate | System Date | System Date |
| lineNumber | QuoteLineItem.Id | OrderItem.Id |
| description | Not returned. | Not returned. |
| quantity | Not returned. | Not returned. |
| addresses | ||
| billTo | Not persisted. | Not persisted. |
| shipTo | locationCode -> locationCode | locationCode -> locationCode |
| shipFrom | Not returned. | Not returned. |
| soldTo | Not returned. | Not returned. |
| productsku | Not returned. | Not returned. |
| referenceDocumentCode | Not returned. | Not returned. |
| taxes | Refer to the next tax attributes section. | Refer to the next tax attributes section. |
Response Mappings for Tax Attributes
This table defines the response mappings between the tax attributes of a tax callout and fields of applicable objects.
| Tax Attributes | Quote Mapping | Order Mapping |
|---|---|---|
| exemptAmount | Not returned. | Not returned. |
| exemptReason | Not returned. | Not returned. |
| imposition | ||
| type | Not returned. | Not returned. |
| Name | Not returned. | Not returned. |
| jurisdiction | ||
| country | Not returned. | Not returned. |
| id | Not returned. | Not returned. |
| level | Not returned. | Not returned. |
| name | Not returned. | Not returned. |
| region | Not returned. | Not returned. |
| stateAssignedNo | Not returned. | Not returned. |
| rate | QuoteItemTaxItem.Rate | OrderItemTaxItem.Rate |
| tax | QuoteItemTaxItem.amount | OrderItemTaxItem.amount |
| taxId | Not returned. | Not returned. |
| taxableAmount | Not returned. | Not returned. |