Skip to content
Snippets Groups Projects

Tax Calculator for CiviCRM

Calculates the taxes based on the place of supply for the purposes of tax calculation.

The main use-case is, for example, memberships or events in Canada, which must be taxed differently depending on the location of the organisation, the event and the member.

Formerly biz.jmaconsulting.cdntaxcalculator

Requirements

  • PHP 7.2 or later
  • CiviCRM 5.latest

Configuration

Navigate to: Administration > CiviContribute > Tax Calculator.

See notes below about configuring tax rates (formerly civicrm_constants.php).

Installation

Install like any CiviCRM extension.

Upgrading from cdntaxcalculator

You may have settings that need to be converted from cdntaxcalculator to taxcalculator.

The simplest option is to enable both extensions, compare the settings and copy-paste from one screen to the other. Alternatively, you could use the API to copy the settings over programmatically or use the following SQL query:

update civicrm_setting set name = replace(name, 'cdntaxcalculator', 'taxcalculator') where name like 'cdn%';

Converting civicrm_constants.php to database settings

Currently there is no interface for handling these settings. Here is an example for Canada:

-- GST for non-HST provinces/territories
insert into civicrm_taxcalculator (name, label, rate, country_id, state_province_id) VALUES
('gst_al', 'GST', 5, 1039, 1100),
('gst_bc', 'GST', 5, 1039, 1101),
('gst_mb', 'GST', 5, 1039, 1102),
('gst_nt', 'GST', 5, 1039, 1105),
('gst_nu', 'GST', 5, 1039, 1107),
('gst_qc', 'GST', 5, 1039, 1110),
('gst_sk', 'GST', 5, 1039, 1111),
('gst_ty', 'GST', 5, 1039, 1112);

-- HST provinces
insert into civicrm_taxcalculator (name, label, rate, country_id, state_province_id) VALUES
('hst_nb', 'HST-NB', 15, 1039, 1103),
('hst_nl', 'HST-NFL', 15, 1039, 1104),
('hst_ns', 'HST-NS', 15, 1039, 1106),
('hst_on', 'HST-ON', 13, 1039, 1108),
('hst_pe', 'HST-PEI', 15, 1039, 1109);

-- PST provinces
-- Typically an organization only collects PST for the province where they are registered
-- i.e. only one of the taxes below, not all.
-- Examples below are posted as separate queries for easier copy-pasting.
insert into civicrm_taxcalculator (name, label, rate, country_id, state_province_id) VALUES
('pst_bc', 'PST-BC', 7, 1039, 1101);

insert into civicrm_taxcalculator (name, label, rate, country_id, state_province_id) VALUES
('pst_mb', 'PST-MB', 7, 1039, 1102);

insert into civicrm_taxcalculator (name, label, rate, country_id, state_province_id) VALUES
('pst_qc', 'PST-QC', 9.975, 1039, 1110);

insert into civicrm_taxcalculator (name, label, rate, country_id, state_province_id) VALUES
('pst_sk', 'PST-SK', 6, 1039, 1111);

Displaying taxes in Contribution Invoices

Edit the invoice message-template with the following code:

{* towards the top *}
{taxcalculatorTaxTotals contribution_id=$id}

{* After the line items *}

        {* TOTAL TAXES *}
        {if $taxcalculator_tax_totals}
          <tr>
            <td colspan="3"></td>
            <td colspan="2" class="division"><hr style="border: 1px solid #000" /></td>
          </tr>
          {foreach from=$taxcalculator_tax_totals item=tax}
          <tr>
            <th colspan="4" align="right"><nobr style="font-size: small;">{$tax.label}</nobr></th>
	    <td align="right" class="money" style="padding-left: 10px;"><nobr>{$tax.amount|crmMoney:$currency}</nobr></td>
          </tr>
          {/foreach}
        {/if}

Displaying taxes in Contribution Email Receipts

Edit the contribution message-template (online and offline) with the following code:

{* towards the top - note the contribution_id variable is not the same as the PDF invoice *}
{taxcalculatorTaxTotals contribution_id=$contributionID}

{* Replace the 'if $totalTaxAmount' block - here to different, td instead of th *}
        {* TOTAL TAXES *}
        {if $taxcalculator_tax_totals}
          {foreach from=$taxcalculator_tax_totals item=tax}
          <tr>
            <td {$labelStyle}>{$tax.label}</td>
            <td align="right" {$valueStyle}>{$tax.amount|crmMoney:$currency}</td>
          </tr>
          {/foreach}
        {/if}

You may also want to adjust the alignment on the Sub-Total and some other fields.

Displaying taxes in Event Email Receipts

Edit the event message-template (online and offline) with the following code:

{* towards the top - note the contribution_id variable is not the same as the PDF invoice *}
{taxcalculatorTaxTotals participant_id=$participantID}

{* Replace the 'if $totalTaxAmount' block - here to different, td instead of th *}
        {* TOTAL TAXES *}
        {if $taxcalculator_tax_totals}
          {foreach from=$taxcalculator_tax_totals item=tax}
          <tr>
            <td {$labelStyle}>{$tax.label}</th>
            <td align="right" {$valueStyle}>{$tax.amount|crmMoney:$currency}</td>
          </tr>
          {/foreach}
        {/if}

You may also want to add align="right" to the other rows in this area, so that they all align in the same direction (or you can remove the align="right" from the tax amounts, but it looks odd).

Special tax rules

Taxcalculator also supports "override rules" per Financial Type (in the admin interface, edit a Financial Type), to alter the behaviour of tax calculations (for situations such as webinars).

The only tax rule which is implemented out of the box is the rule for "Online Events", which will apply the tax of the state/province of the contact, rather than the tax of the location of the event.

Other tax rules are not implemented out of the box. They are there mostly to help with customizations (see example below).

Altering tax rates with a hook

Example:

/**
 * Implements hook_taxcalculator_alter_lineitems().
 */
function mycrm_taxcalculator_alter_lineitems(&$line_items) {
  foreach ($line_items as &$item) {
    // Apply the federal tax only
    $rule = Civi::settings()->get('taxcalculator_rule_' . $item['financial_type_id']);
    if ($rule == 'federalonly' && !empty($item['tax_rate'])) {
      $session = CRM_Core_Session::singleton();
      $province_id = $session->get('cdntax_province_id');

      if (!$province_id) {
        $contact_id = CRM_Core_Session::getLoggedInContactID();
        $province_id = CRM_Taxcalculator_BAO_CDNTaxes::getStateProvince($contact_id);
      }

      $item['tax_rate'] = CRM_Core_DAO::singleValueQuery("SELECT rate FROM civicrm_taxcalculator WHERE name like 'gst_%' and state_province_id = %1", [
        1 => [$province_id, 'Positive'],
      ]);

      $item['tax_amount'] = round($item['amount'] * $item['tax_rate']/100, 2);
    }
  }
}

Of course this is a just an example (for an organization based in Quebec). Double-check with your accountant.

Running the tests

The phpunit tests can be run on top of a buildkit site:

cd /path/to/files/civicrm/ext/taxcalculator
env CIVICRM_UF=UnitTests phpunit5

The tests will enable the CiviCRM debug mode, which will make taxcalculator keep a trace of all tax calculations in the ConfigAndLog. This can be useful to debug failing tests.

The Puppeteer tests are only a proof of concept and depend on a (not very maintained) test site.

Support

Please post bug reports in the issue tracker of this project:
https://lab.civicrm.org/extensions/taxcalculator/issues

Commercial support is available from: