Setting up a custom VAT filter

Introduction

WP Full Stripe – starting with v3.12.0 – supports charging VAT on top of subscription payments.

You can use the following VAT calculation strategies:

  1. No VAT
  2. Fixed rate
  3. Custom rate

When selecting “Custom rate”, you have to provide your own implementation in the form of a WordPress filter for returning the VAT rate to be used.
The implementation can take into consideration form data like the billing address, custom fields, etc.

This article is about:

  • Documenting how to set up a custom filter
  • Documenting the VAT filter interface
  • Showing an example implementation that businesses around the world can use as a template.

How to set up a custom VAT filter

Setting up your custom VAT filter is composed of the following steps:

  1. Set the options on the “Finance” tab of the subscription form:

    1. VAT Rate: Custom rate
    2. Collect Billing Address: Show
    3. Default Billing Country: Select the country you’ll issue the invoices from.
      (This country is passed as the “From country” parameter to the VAT filter)

  2. Add the filter code to the functions.php of your active theme.
    This ensures that the code will not be overwritten when WP Full Stripe is updated, but still, you have to reinsert the code to functions.php of the theme when the theme is updated.

Custom VAT filter function signature

Your custom VAT filter must conform to the following function signature:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * Determine the VAT rate based on default country, billing address, and custom fields
 *
 * @param float  $initialValue           Currently not used, always 0.
 * @param string $fromCountry            The default billing country, also the supplier's billing country
 * @param string $toCountry              The country of the billing address
 * @param array  $additionalArguments    A lot of additional info about the form, see below:
 *        The associative array has the following indexes:
 *                form_id
 *                form_type
 *                plan_id
 *                plan_currency
 *                plan_amount
 *                plan_setup_fee
 *                billing_address
 *                custom_inputs
 * @return float  The VAT rate, returned as a percent [0.0 .. 100.0]
 */
function determine_custom_vat_percent($initialValue, $fromCountry, $toCountry, $additionalArguments) {

Please note that you have to add your filter to the WordPress filter chain, otherwise it will not be called:

1
2
 
add_filter( 'fullstripe_get_vat_percent', 'determine_custom_vat_percent', 10, 4);

Example implementation

The example code can be used as a template for international businesses to create a custom VAT filter for WP Full Stripe.

The example code takes into account the supplier’s and the buyer’s country to determine the VAT rate for e-goods.
We are emphasizing e-goods because VAT rules are different for e-goods and physical goods in the European Union.

The example calculates the VAT rate based on the following table:

The implementation uses two custom fields for the company name and the VAT ID.
It considers the entered data as a business if either the company name or the VAT ID is filled in.
The code can be refined by adding verifications for EU VAT IDs.

Here is how the two custom fields are set up on the subscription form:

Here is the example filter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/**
 * Determine the EU VAT rate based on country code
 *
 * @param string $country 2-letter ISO country code in uppercase
 * @return float  The VAT rate (percent) for the country
 */
 
function lookup_vat_rate_by_country($country) {
    switch ($country) {
        case 'LU':
            $vatPercent = 17.00;
            break;
 
        case 'MT':
            $vatPercent = 18.00;
            break;
 
        case 'CY':
        case 'DE':
            $vatPercent = 19.00;
            break;
 
        case 'GB':
        case 'AT':
        case 'EE':
        case 'FR':
        case 'RO':
        case 'SK':
        case 'SE':
            $vatPercent = 20.00;
            break;
 
        case 'BE':
        case 'BG':
        case 'CZ':
        case 'ES':
        case 'NL':
        case 'LT':
        case 'LV':
            $vatPercent = 21.00;
            break;
 
        case 'IT':
        case 'PT':
        case 'SI':
            $vatPercent = 22.00;
            break;
 
        case 'IE':
        case 'PL':
            $vatPercent = 23.00;
            break;
 
        case 'FI':
        case 'GR':
            $vatPercent = 24.00;
            break;
 
        case 'HR':
        case 'DK':
            $vatPercent = 25.00;
            break;
 
        case 'HU':
            $vatPercent = 27.00;
            break;
 
        default:
            $vatPercent = 0;
            break;
    }
 
    return $vatPercent;
}
 
/**
 * Determine the VAT rate based on default country, billing address, and custom fields
 *
 * @param float  $initialValue           Currently not used, always 0.
 * @param string $fromCountry            The default billing country, also the supplier's billing country
 * @param string $toCountry              The country of the billing address
 * @param array  $additionalArguments    A lot of additional info about the form, see below:
 *        The associative array has the following indexes:
 *                form_id
 *                form_type
 *                plan_id
 *                plan_currency
 *                plan_amount
 *                plan_setup_fee
 *                billing_address
 *                custom_inputs
 * @return float  The VAT rate, returned as a percent [0.0 .. 100.0]
 */
function determine_custom_vat_percent($initialValue, $fromCountry, $toCountry, $additionalArguments) {
    $euCountries = array('GB', 'LU', 'MT', 'CY', 'DE', 'RO', 'AT', 'EE', 'FR', 'SK', 'SE', 'BE', 'BG', 'CZ', 'ES', 'NL',
        'LT', 'LV', 'IT', 'PT', 'SI', 'IE', 'PL', 'FI', 'GR', 'HR', 'DK', 'HU');
 
 
    $customInputValues = array_key_exists('custom_inputs', $additionalArguments) ?
        $additionalArguments['custom_inputs'] : null;
 
    $b2bCustomer = isset($customInputValues) && is_array($customInputValues) &&
        (!empty($customInputValues['Company Name']) || !empty($customInputValues['VAT ID']));
 
 
    if (in_array($fromCountry, $euCountries)) {
        //-- Supplier is in the EU
        if (in_array($toCountry, $euCountries)) {
            // EU countries
            $domestic = $fromCountry === $toCountry;
 
            if ($b2bCustomer && !$domestic) {
                //-- B2B customer in EU
                $vatPercent = 0;
            } else {
                // B2C customer or domestic B2B in EU
                $vatPercent = lookup_vat_rate_by_country($toCountry);
            }
        } else {
            // Countries outside EU
            // B2B and B2C customers outside EU
            $vatPercent = 0;
        }
    } else {
        //-- Supplier is not in the EU
        if (in_array($toCountry, $euCountries)) {
            if ($b2bCustomer) {
                //-- B2B customer in EU
                $vatPercent = 0;
            } else {
                //-- B2C customer in EU
                $vatPercent = lookup_vat_rate_by_country($toCountry);
            }
        } else {
            //-- Outside EU, both B2B and B2C are 0%
            $vatPercent = 0;
        }
    }
 
    error_log("VAT percent: $vatPercent, from country: $fromCountry, to country: $toCountry");
 
    return $vatPercent;
}

Disclaimer

This custom VAT filter example is provided as is, only for the purpose of demonstrating the possibilities of custom VAT filters that can be written for WP Full Stripe. You are granted the permission to use it, modify it, and redistribute it as you wish. Neither Mammothology nor Infinea Consulting can be held accountable for any inconsistencies, omissions, or bugs in the example code.

in For developers