You can communicate with a Tax Engine of your choice with the help of a Tax Callback class. For this, you should know the Request Fields that are sent to Callback and further to the Tax Engine. The following diagram gives an overview of how information is sent from Product to the Tax Engine and received back after processing.


Tax input is a container which holds the following fields:

  • Item: Contains an Invoice Line Item or a Credit Memo Line Item
  • Handback: A generic wrapper class that can be used to pass an additional field value. Set the value for this field to TaxInputRelatedObjects. This class will contain the parent Invoice or Credit Memo based on whether the item contains an Invoice Line Item or a Credit Memo Line Item.

  • Tax Address: The address specified as the Shipping Address of the Ship To account. If there is no Shipping Address mentioned in the Ship To account, then the Billing Address of the Ship To account is used.

    Note

    If both the address of the Ship To account are missing, you need to cancel the invoice. Recreate the invoice after setting the proper address in the Ship To account.

  • Tax Code : This value is taken from the product PLI. 
  • Taxable Amount: This is the amount to apply the tax to.

You can use Invoice Number as a Document Id when calling a Tax Engine. After processing, the Tax Results are sent to Conga Billing. Handback field holds the line item and error message (if any).
 

Note

If there is any error during the tax calculation because of which the Invoice is not approved, check out Error Status and Error Message fields on the Invoice to find out the reason behind the failure.


When communicating with a Tax Engine, you must note that:

  • The implementation of the Tax Callback must return a Apttus_Billing.CustomClass.TaxResultHandback object in the Handback field of a Tax Result.
  • The implementation of the Tax Callback must determine commit mode by checking if the status of the Invoice is Approved Pending.

You must register a Tax Callback class which is called for tax calculation on Invoice generation from Custom Settings. 

To add the custom class

  1. Go to Setup > App Setup > Develop > Apex Classes.
  2. Click New.
  3. Enter the sample callback class code.

  4.  Click Save.


This is just a sample callback class. You may change the code per your requirements.

/**
*  Apttus Billing
*  TestTaxCallback
*   
 *  @2013-2018 Apttus Inc. All rights reserved.
*/
global with sharing class TestTaxCallback 
      implements Apttus_Config2.CustomClass.ITaxCallback, Apttus_Config2.CustomClass.ITaxCallback2 {

      public static final Decimal TAX_RATE_DEFAULT = 0.10;
      public static final Decimal TAX_RATE_CA = 0.0875;
      public static final Decimal TAX_RATE_NV = 0.0;
      public static final Decimal TAX_RATE_NY = 0.0925;
      public static final Decimal TAX_RATE_TX = 0.0;
      public static final Decimal TAX_RATE_WA = 0.075;

      //Note :: If we Introduce any new state in this callback then we need to update
      //            the "AbstractInvoiceSupportTest" class as well.
      //            We are validating the Line Item Tax Breakups so for new State values we
      //            need to add logic and assert values in "validateInvoiceLineItemTaxBreakups" method. 
      public static final String STATE_CA = 'CA';
      public static final String STATE_NV = 'NV';
      public static final String STATE_NY = 'NY';
      public static final String STATE_TX = 'TX';
      public static final String STATE_WA = 'WA';

      public static final String STATE_FL = 'FL';
      public static final String STATE_OK = 'OK';

      public static final Decimal OK_COUNTY_PERCENTAGE = 0.70;
      public static final Decimal OK_DISTRICT_PERCENTAGE = 0.30;

      public static final Decimal FL_CITY_PERCENTAGE = 0.10;
      public static final Decimal FL_COUNTY_PERCENTAGE = 0.20;
      public static final Decimal FL_DISTRICT_PERCENTAGE = 0.20;
      public static final Decimal FL_STATE_PERCENTAGE = 0.50;

      private static final Map<String, Decimal> stateTaxRateMap =
            new Map<String, Decimal> {
                  STATE_CA => TAX_RATE_CA,
                  STATE_NV => TAX_RATE_NV,
                  STATE_NY => TAX_RATE_NY,
                  STATE_TX => TAX_RATE_TX,
                  STATE_WA => TAX_RATE_WA
            };

      /**
      * Callback invoked to compute tax based on the given input
      * @param input the tax input 
       * @return the tax result
      */
      global Apttus_Config2.CustomClass.TaxResult computeTax(Apttus_Config2.CustomClass.TaxInput input) {

            List<Apttus_Config2.CustomClass.TaxInput> inputs = new List<Apttus_Config2.CustomClass.TaxInput>{input};
            List<Apttus_Config2.CustomClass.TaxResult> results = computeTaxMultiple(inputs);
            return (null == results || 1 > results.size()) ? null : results[0];
      }
      
      /**
      * Callback invoked to compute tax based on the given list of inputs
      * @param inputs the list of tax inputs
      * @return the list of tax results
      */
      global List<Apttus_Config2.CustomClass.TaxResult> computeTaxMultiple(
            List<Apttus_Config2.CustomClass.TaxInput> inputs) {
        //System.debug('From compute Multiple Tax>>>');
            // Create list of Tax Results
            List<Apttus_Config2.CustomClass.TaxResult> results = new List<Apttus_Config2.CustomClass.TaxResult>();
                  
            // create mock result
            for (Integer i = 0; i < inputs.size(); i++) {

                  Apttus_Config2.CustomClass.TaxInput input = inputs[i];

                  System.assertNotEquals(null, input, 'A Tax Input cannot be null!');
                  System.assertNotEquals(null, input.item, 'The Item contained in a Tax Input cannot be null!');
                  System.assertNotEquals(null, input.item.Id, 'The Item Id contained in a Tax Input cannot be null!');
                  // Create a Tax Result and populate
                  Decimal taxRate = getTaxRate(input);
                  Apttus_Config2.CustomClass.TaxResult result = new Apttus_Config2.CustomClass.TaxResult();
                  CustomClass.TaxResulthandBack taxResulthandBackObj = new CustomClass.TaxResulthandBack();
                  if(null != input.item) {
                        taxResulthandBackObj.lineItem = input.item;
                        taxResulthandBackObj.errorMessage = null;
                        
                  }
                  result.Handback = taxResulthandBackObj;
                  //System.debug('From Multiple Tax>>> taxabeamount' + input.TaxableAmount + '#taxRate' + taxRate);
                  result.TaxAmount = input.TaxableAmount * taxRate;
                  // Create Test Tax Breakups
                  addTestTaxBreakups(input, result, taxRate);
                  Decimal aggregateTaxAmount = 0.00;
                  
                  Integer currencyDecimalPlacesToSet =CurrencyTypeSupport.getCurrencyDecimalPlaces(input.item);
                                                                        
                  //Integer currencyDecimalPlacesToSet = SystemUtil.getCurrencyDecimalPlaces();
                  for(integer iBreakupCount=0;iBreakupCount<result.TaxBreakups.size();iBreakupCount++)
                  {
                    
                    Apttus_Config2__TaxBreakup__c taxBreakup = result.TaxBreakups.get(iBreakupCount);
                    aggregateTaxAmount +=taxBreakup.Apttus_Config2__TaxAmount__c.setScale(currencyDecimalPlacesToSet);
                  }
                  //System.debug('TaxAmount aggregateTaxAmount' + aggregateTaxAmount);
                  result.TaxAmount =aggregateTaxAmount;
                  
                  //BIL-1080 :: Check the Tax Address state, If it contains value from (V1,V2,V3,V4) then null the expected properties from tax Result
                  String state = (null != input && null != input.TaxAddress && null != input.TaxAddress.State)
                  ? input.TaxAddress.State.trim().toUppercase() : null;
                  

                  //    result.TaxAmount = aggregateTaxAmountFromTestTaxBreakups(result);
                  // Add the Tax Result to the return list
                  results.add(result);
         
            }
            
            //System.debug('From compute Multiple Tax>>>' + results);
      return results;
    }

      
      /** Get a tax rate based on the "address" in the specified Tax input. If
        * there is a rate for the "state code" then return that rate, otherwise
        * return the "default rate" (10%).
        * 10%.
        *
        * @param input The Tax input to extract State Code from.
        * @return A rate that pertains to the State Code, or the "default rate"
        *  if there is there no such rate exists.
        */
      public Decimal getTaxRate(Apttus_Config2.CustomClass.TaxInput input) {

            String state = (null != input && null != input.TaxAddress && null != input.TaxAddress.State)
                  ? input.TaxAddress.State.trim().toUppercase() : null;
            Decimal rate = stateTaxRateMap.get(state);

            return (null == rate) ? TAX_RATE_DEFAULT : rate;
      }

      private void addTestTaxBreakups(
            Apttus_Config2.CustomClass.TaxInput input,
            Apttus_Config2.CustomClass.TaxResult result,
            Decimal taxRate) {
            
            String state = (null != input.TaxAddress && null != input.TaxAddress.State)
                  ? input.TaxAddress.State : '';
                  
            //System.debug('From compute Multiple Tax State>>>' + state);     
            
            if (STATE_OK.equals(state)) {

                  // Create/add two Tax Breakups (County and District)
                  result.TaxBreakups.add(
                        new Apttus_Config2__TaxBreakup__c(
                              Apttus_Config2__Sequence__c = 1,
                              Apttus_Config2__LineItemId__c = input.Item.Id,
                              Apttus_Config2__BreakupType__c = InvoiceLineItemTaxBreakup.BREAKUP_TYPE_DETAIL,
                              Apttus_Config2__TaxType__c = InvoiceLineItemTaxBreakup.BREAKUP_TAXTYPE_COUNTY_TAX,
                              Apttus_Config2__TaxRate__c = taxRate,
                              Apttus_Config2__TaxAppliesTo__c = InvoiceLineItemTaxBreakup.TAX_APPLIESTO_NET_PRICE,
                              Apttus_Config2__TaxAmount__c = (result.TaxAmount * OK_COUNTY_PERCENTAGE)
                        )
                  );
                  
                  result.TaxBreakups.add(
                        new Apttus_Config2__TaxBreakup__c(
                              Apttus_Config2__Sequence__c = 2,
                              Apttus_Config2__LineItemId__c = input.Item.Id,
                              Apttus_Config2__BreakupType__c = InvoiceLineItemTaxBreakup.BREAKUP_TYPE_DETAIL,
                              Apttus_Config2__TaxType__c = InvoiceLineItemTaxBreakup.BREAKUP_TAXTYPE_DISTRICT_TAX,
                              Apttus_Config2__TaxRate__c = taxRate,
                              Apttus_Config2__TaxAppliesTo__c = InvoiceLineItemTaxBreakup.TAX_APPLIESTO_NET_PRICE,
                              Apttus_Config2__TaxAmount__c = (result.TaxAmount * OK_DISTRICT_PERCENTAGE)
                        )
                  );

            } else if (STATE_FL.equals(state)) {

                  // Create/add four Tax Breakups (City, County, District, and State)
                  result.TaxBreakups.add(
                        new Apttus_Config2__TaxBreakup__c(
                              Apttus_Config2__Sequence__c = 1,
                              Apttus_Config2__LineItemId__c = input.Item.Id,
                              Apttus_Config2__BreakupType__c = InvoiceLineItemTaxBreakup.BREAKUP_TYPE_DETAIL,
                              Apttus_Config2__TaxType__c = InvoiceLineItemTaxBreakup.BREAKUP_TAXTYPE_CITY_TAX,
                              Apttus_Config2__TaxRate__c = taxRate,
                              Apttus_Config2__TaxAppliesTo__c = InvoiceLineItemTaxBreakup.TAX_APPLIESTO_NET_PRICE,
                              Apttus_Config2__TaxAmount__c = (result.TaxAmount * FL_CITY_PERCENTAGE)
                        )
                  );

                  result.TaxBreakups.add(
                        new Apttus_Config2__TaxBreakup__c(
                              Apttus_Config2__Sequence__c = 2,
                              Apttus_Config2__LineItemId__c = input.Item.Id,
                              Apttus_Config2__BreakupType__c = InvoiceLineItemTaxBreakup.BREAKUP_TYPE_DETAIL,
                              Apttus_Config2__TaxType__c = InvoiceLineItemTaxBreakup.BREAKUP_TAXTYPE_COUNTY_TAX,
                              Apttus_Config2__TaxRate__c = taxRate,
                              Apttus_Config2__TaxAppliesTo__c = InvoiceLineItemTaxBreakup.TAX_APPLIESTO_NET_PRICE,
                              Apttus_Config2__TaxAmount__c = (result.TaxAmount * FL_COUNTY_PERCENTAGE)
                        )
                  );
                  
                  result.TaxBreakups.add(
                        new Apttus_Config2__TaxBreakup__c(
                              Apttus_Config2__Sequence__c = 3,
                              Apttus_Config2__LineItemId__c = input.Item.Id,
                              Apttus_Config2__BreakupType__c = InvoiceLineItemTaxBreakup.BREAKUP_TYPE_DETAIL,
                              Apttus_Config2__TaxType__c = InvoiceLineItemTaxBreakup.BREAKUP_TAXTYPE_DISTRICT_TAX,
                              Apttus_Config2__TaxRate__c = taxRate,
                              Apttus_Config2__TaxAppliesTo__c = InvoiceLineItemTaxBreakup.TAX_APPLIESTO_NET_PRICE,
                              Apttus_Config2__TaxAmount__c = (result.TaxAmount * FL_DISTRICT_PERCENTAGE)
                        )
                  );
                  
                  result.TaxBreakups.add(
                        new Apttus_Config2__TaxBreakup__c(
                              Apttus_Config2__Sequence__c = 4,
                              Apttus_Config2__LineItemId__c = input.Item.Id,
                              Apttus_Config2__BreakupType__c = InvoiceLineItemTaxBreakup.BREAKUP_TYPE_DETAIL,
                              Apttus_Config2__TaxType__c = InvoiceLineItemTaxBreakup.BREAKUP_TAXTYPE_STATE_TAX,
                              Apttus_Config2__TaxRate__c = taxRate,
                              Apttus_Config2__TaxAppliesTo__c = InvoiceLineItemTaxBreakup.TAX_APPLIESTO_NET_PRICE,
                              Apttus_Config2__TaxAmount__c = (result.TaxAmount * FL_STATE_PERCENTAGE)
                        )
                  );
                  
            } else {

                  // Create/add one Tax Breakup
                  result.TaxBreakups.add(
                        new Apttus_Config2__TaxBreakup__c(
                              Apttus_Config2__Sequence__c = 1,
                              Apttus_Config2__LineItemId__c = input.Item.Id,
                              Apttus_Config2__BreakupType__c = InvoiceLineItemTaxBreakup.BREAKUP_TYPE_DETAIL,
                              Apttus_Config2__TaxType__c = InvoiceLineItemTaxBreakup.BREAKUP_TAXTYPE_STATE_TAX,
                              Apttus_Config2__TaxRate__c = taxRate,
                              Apttus_Config2__TaxAppliesTo__c = InvoiceLineItemTaxBreakup.TAX_APPLIESTO_NET_PRICE,
                              Apttus_Config2__TaxAmount__c = result.TaxAmount
                        )
                  );
            }
      }

}
    



CODE

Registering the Tax Callback class

From Setup, enter Custom Settings in the Quick Find box and look for Config System Classes. Click Manage to see System properties. Edit System Properties to add a Tax Callback Class.