You are here

commerce_avatax.module in Drupal Commerce Connector for AvaTax 7.3

Calculate Sales Tax using AvaTax Calc service from Avalara, Inc.

Copyright (C) adTumbler, Inc, adTumbler.com

File

commerce_avatax.module
View source
<?php

/**
 * @file
 * Calculate Sales Tax using AvaTax Calc service from Avalara, Inc.
 *
 * Copyright (C) adTumbler, Inc, adTumbler.com
 *
 */

/**
 * Conditional includes for AvaTax Calc configuration options.
 */
if (variable_get('commerce_avatax_product_version', '') == 'basic') {
  require_once 'commerce_avatax_basic.inc';
}
if (variable_get('commerce_avatax_product_version', '') == 'pro') {
  require_once 'commerce_avatax_pro.inc';
}
if (variable_get('commerce_avatax_erp_status', '')) {
  require_once 'commerce_avatax_erp.inc';
}

/**
 * Implements hook_libraries_info().
 */
function commerce_avatax_libraries_info() {
  $libraries = array();
  $libraries['avalara_avatax'] = array(
    'name' => 'Avalara Avatax',
    'vendor url' => 'http://avalara.com/',
    'download url' => 'http://developer.avalara.com/',
    'version' => array(
      'file' => 'classes/ATConfig.class.php',
      'pattern' => '/@copyright .+ - (\\d+) Avalara, Inc.  All rights reserved.',
    ),
    'files' => array(
      'php' => array(
        'ava_tax.php',
        'credentials.php',
      ),
    ),
  );
  return $libraries;
}

/**
 * Implements hook_permission().
 */
function commerce_avatax_permission() {
  return array(
    'administer avatax' => array(
      'title' => t('Administer AvaTax'),
      'description' => t('Allows users to configure AvaTax'),
      'restrict access' => TRUE,
    ),
  );
}

/**
 * Implements hook_commerce_line_item_type_info().
 */
function commerce_avatax_commerce_line_item_type_info() {
  $line_item_types = array();
  $line_item_types['avatax'] = array(
    'name' => t('AvaTax'),
    'description' => t('Calculate AvaTax'),
    'add_form_submit_value' => t('Add tax line'),
    'base' => 'commerce_avatax_line_item',
    'callbacks' => array(
      'configuration' => 'commerce_avatax_configure_line_item',
    ),
  );
  return $line_item_types;
}

/**
 * Line item callback: configures the AvaTax line item type on module enable.
 */
function commerce_avatax_configure_line_item() {
  $field_name = 'avatax';
  $type = 'avatax';
  $field = field_info_field($field_name);
  $instance = field_info_instance('commerce_line_item', $field_name, $type);
  if (empty($field)) {
    $field = array(
      'field_name' => $field_name,
      'type' => 'list_text',
      'cardinality' => 1,
      'entity_types' => array(
        'commerce_line_item',
      ),
      'translatable' => FALSE,
      'locked' => TRUE,
    );
    $field = field_create_field($field);
  }
  if (empty($instance)) {
    $instance = array(
      'field_name' => $field_name,
      'entity_type' => 'commerce_line_item',
      'bundle' => $type,
      'label' => t('AvaTax Calc'),
      'required' => TRUE,
      'settings' => array(),
      'widget' => array(
        'type' => 'options_select',
        'weight' => 0,
      ),
      'display' => array(
        'display' => array(
          'label' => 'hidden',
          'weight' => 0,
        ),
      ),
    );
    field_create_instance($instance);
  }
}

/**
 * Returns the title of an avatax line item.
 */
function commerce_avatax_line_item_title($line_item) {
  $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
  if (!empty($line_item->data['avatax']['display_title'])) {
    return $line_item->data['avatax']['display_title'];
  }
}

/**
 * Implements hook_commerce_price_component_type_info().
 */
function commerce_avatax_commerce_price_component_type_info() {

  // Get Sales Tax description
  $tax_title = variable_get('commerce_avatax_tax_description', '');
  $components = array();

  // Define a generic avatax price component type.
  $components['avatax'] = array(
    'title' => t('AvaTax sales tax'),
    'display_title' => $tax_title,
    'weight' => 25,
  );
  return $components;
}

/**
 * Implements hook_commerce_order_update().
 */
function commerce_avatax_commerce_order_update($order) {

  // Calculate sales tax between shipping module and review checkout.
  if (!variable_get('commerce_avatax_checkout_form', '') && variable_get('commerce_avatax_tax_address', '') == 'Shipping') {
    if ($order->original->status == 'checkout_shipping' && $order->status == 'checkout_review' && !isset($order->avatax)) {
      commerce_avatax_calculate_sales_tax($order);
    }
  }

  // Delete sales tax line item if cart is updated
  if ($order->status == 'cart') {
    if ($order->original->commerce_order_total['und'][0]['amount'] != $order->commerce_order_total['und'][0]['amount']) {
      commerce_avatax_delete_avatax_line_items($order);
    }
  }
}

/**
 * Calculate sales tax using regular web site checkout.
 */
function commerce_avatax_calculate_sales_tax($order) {
  $sales_tax = commerce_avatax_retrieve_sales_tax($order);

  // If we have a sales tax amount.
  if ($sales_tax) {

    // Create a new AvaTax line item.
    $line_item = commerce_avatax_line_item_create($sales_tax, $order->order_id);

    // Add the line item data as a property of the order.
    $order->avatax['avatax'] = $line_item;
    commerce_avatax_delete_avatax_line_items($order);
    commerce_avatax_add_avatax_line_item($line_item, $order);
  }
}

/**
 * Calculate sales tax for manual order entry.
 */
function commerce_avatax_manual_calculate_sales_tax($order) {
  $sales_tax = commerce_avatax_retrieve_sales_tax($order);

  // If we have a sales tax amount.
  if ($sales_tax) {

    // Create a new AvaTax line item.
    $line_item = commerce_avatax_line_item_create($sales_tax, $order->order_id);

    // Add the line item data as a property of the order.
    $order->avatax['avatax'] = $line_item;
  }
}

/**
 * Creates a sales tax line item
 *
 * @param $sales_tax
 *   A price array used to establish the base unit price for the AvaTax.
 * @param $order
 *   If available, the order to which the AvaTax line item will belong.
 *
 * @return
 *   The sales tax line item.
 */
function commerce_avatax_line_item_create($sales_tax, $order_id = 0) {
  $avatax_service = commerce_avatax_service_load();

  // Create the new line item for AvaTax.
  $line_item = commerce_avatax_line_item_new($sales_tax, $order_id);

  // Set the price component of the unit price if it hasn't already been done.
  $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
  $data = $line_item_wrapper->commerce_unit_price->data
    ->value();
  if (empty($data['components'])) {
    $line_item_wrapper->commerce_unit_price->data = commerce_price_component_add($line_item_wrapper->commerce_unit_price
      ->value(), $avatax_service['price_component'], $line_item_wrapper->commerce_unit_price
      ->value(), TRUE, FALSE);
  }
  return $line_item;
}

/**
 * Creates a new AvaTax line item populated with the sales tax values.
 *
 * @param $sales_tax
 *   A price array used to initialize the value of the line item's unit price.
 * @param $order_id
 *   The ID of the order the line item belongs to.
 * @param $data
 *   An array value to initialize the line item's data array with.
 * @param $type
 *   The name of the line item type being created; defaults to 'avatax'.
 *
 * @return
 *   The avatax line item initialized to the given
 *   unit price.
 */
function commerce_avatax_line_item_new($sales_tax, $order_id = 0, $data = array(), $type = 'avatax') {

  // Ensure a default product line item type.
  if (empty($type)) {
    $type = 'avatax';
  }

  // Create the new line item.
  $line_item = entity_create('commerce_line_item', array(
    'type' => $type,
    'order_id' => $order_id,
    'quantity' => 1,
    'data' => $data,
  ));

  // Populate line item with the sales tax unit price data.
  commerce_avatax_line_item_populate($line_item, $sales_tax);
  return $line_item;
}

/**
 * Populates a sales tax line item with the specified values.
 *
 * @param $sales_tax
 *   A sales tax array to be added to the value of the line item's unit price.
 */
function commerce_avatax_line_item_populate($line_item, $sales_tax) {
  $avatax_service = commerce_avatax_service_load();

  // Use the label to store the display title of the avatax service.
  $line_item->line_item_label = 'AvaTax Calc';
  $line_item->quantity = 1;
  $line_item->data['avatax'] = $avatax_service;
  $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);

  // Set the unit price.
  $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
  $line_item_wrapper->commerce_unit_price = $sales_tax;
}

/**
 * Adds a avatax line item to an order, and saves the order.
 *
 * @param $line_item
 *   An unsaved tax saas line item that should be added to the order.
 * @param $order
 *   The order to add the tax saas line item to.
 * @param $skip_save
 *   Boolean indicating whether or not to skip saving the order in this function.
 *
 * @return
 *   The saved tax saas line item object or FALSE on failure.
 */
function commerce_avatax_add_avatax_line_item($line_item, $order) {

  // Do not proceed without a valid order.
  if (empty($order)) {
    return FALSE;
  }

  // Save the incoming line item now so we get its ID.
  commerce_line_item_save($line_item);

  // Add it to the order's line item reference value.
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  $order_wrapper->commerce_line_items[] = $line_item;
  commerce_order_calculate_total($order);
  commerce_order_save($order);
}

/**
 * Returns a form with the elements required to add an AvaTax line item through the line
 * item manager widget.
 */
function commerce_avatax_line_item_add_form($form, &$form_state) {

  // Calculate the sales tax amount for this order.
  $order = $form_state['commerce_order'];
  commerce_avatax_manual_calculate_sales_tax($order);

  // Store the available rates in the form.
  $form = array();
  $form['#attached']['css'][] = drupal_get_path('module', 'commerce_avatax') . '/theme/commerce_avatax.admin.css';
  $form['avatax_rate'] = array(
    '#type' => 'value',
    '#value' => $order->avatax,
  );

  // Create an options array for the sales tax amount
  $options = commerce_avatax_options($order);
  $form['avatax'] = array(
    '#type' => 'radios',
    '#title' => t('AvaTax Calc'),
    '#options' => $options,
    '#default_value' => key($options),
  );
  return $form;
}

/**
 * Adds the selected tax saas information to a new tax saas line item.
 *
 * @param $line_item
 *   The newly created line item object.
 * @param $element
 *   The array representing the widget form element.
 * @param $form_state
 *   The present state of the form upon the latest submission.
 * @param $form
 *   The actual form array.
 *
 * @return
 *   NULL if all is well or an error message if something goes wrong.
 */
function commerce_avatax_line_item_add_form_submit($line_item, $element, &$form_state, $form) {
  $order = $form_state['commerce_order'];

  // Ensure a quantity of 1.
  $line_item->quantity = 1;

  // Use the values for avatax
  $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $element['actions']['avatax_rate']['#value']['avatax']);
  $sales_tax = $line_item_wrapper->commerce_unit_price
    ->value();

  // Populate the line item with the appropriate data.
  commerce_avatax_line_item_populate($line_item, $sales_tax);
}

/**
 * Turns the sales tax amount into a form element options array.
 *
 * @param $order
 *   An order object with an avatax property defined as an array of
 *   sales tax values.
 *
 * @return
 *   An options array of calculated tax saas rates labeled using the display
 *   title of the tax saas services.
 */
function commerce_avatax_options($order) {
  $options = array();
  $line_item = $order->avatax['avatax'];
  $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
  $options['avatax'] = t('!avatax: !price', array(
    '!avatax' => 'Sales tax',
    '!price' => commerce_currency_format($line_item_wrapper->commerce_unit_price->amount
      ->value(), $line_item_wrapper->commerce_unit_price->currency_code
      ->value()),
  ));
  return $options;
}

/**
 * Deletes all AvaTax line items on an order.
 *
 * @param $order
 *  The order object to delete the avatax line items from.
 */
function commerce_avatax_delete_avatax_line_items($order) {
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);

  // When deleting more than one line item, metadata_wrapper will give problems
  // if deleting while looping through the line items. So first remove from order
  // and then delete the line items.
  $line_item_ids = array();
  foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {

    // If this line item is an avatax saas line item...
    if ($line_item_wrapper->type
      ->value() == 'avatax') {

      // Store its ID for later deletion and remove the reference from the line
      // item reference field.
      $line_item_ids[] = $line_item_wrapper->line_item_id
        ->value();
      $order_wrapper->commerce_line_items
        ->offsetUnset($delta);
    }
  }

  // If we found any avatax line items...
  if (!empty($line_item_ids)) {

    // First save the order to update the line item reference field value.
    commerce_order_save($order);

    // Then delete the line items.
    commerce_line_item_delete_multiple($line_item_ids);
  }
}

/**
 * Returns the avatax service array.
 *
 * @return
 *   An array with the AvaTax Calc service details.
 */
function commerce_avatax_service_load() {
  $avatax_service = array(
    'name' => 'avatax',
    'base' => 'avatax',
    'display_title' => 'Sales tax',
    'description' => 'The calculated sales tax amount',
    'price_component' => 'avatax',
    'weight' => 0,
    'module' => 'commerce_avatax',
    'title' => 'Drupal Commerce Connector for AvaTax Calc',
    'admin_list' => TRUE,
  );
  return $avatax_service;
}

/**
 * AvaTax service: returns the sales tax amount as an array.
 *
 * @param $order
 *  The order object to delete the avatax line items from.
 *
 * @return
 *   The AvaTax Calc sales tax values as an array.
 */
function commerce_avatax_retrieve_sales_tax($order) {

  // Get Company Code, Company Use Mode and Pro Mode.
  $company_code = variable_get('commerce_avatax_company_code', '');
  $use_mode = variable_get('commerce_avatax_use_mode', '');
  $pro_mode = variable_get('commerce_avatax_pro_mode', '');

  // Sales Tax Shipping code
  $shipcode = variable_get('commerce_avatax_shipcode', '');
  $sales_tax = array(
    'amount' => 0,
    'currency_code' => 'USD',
    'data' => array(),
  );

  // Build order wrapper
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);

  // Exit if there are no line items in the order wrapper
  if (count($order_wrapper->commerce_line_items) == 0) {
    drupal_set_message(t('There are no line items for this order. Please Cancel this entry!'), 'error');
    return $sales_tax;
  }

  // Add logic - integrate with physical goods module - to use billing address if only digital products with the order
  // Get address to be used for sales tax
  if (variable_get('commerce_avatax_tax_address', '') == 'Billing') {
    if (isset($order_wrapper->commerce_customer_billing->commerce_customer_address)) {
      $billing_address = $order_wrapper->commerce_customer_billing->commerce_customer_address
        ->value();
      $street1 = $billing_address['thoroughfare'];
      $street2 = $billing_address['premise'];
      $city = $billing_address['locality'];
      $state = $billing_address['administrative_area'];
      $country = $billing_address['country'];
      $zip = $billing_address['postal_code'];
    }
  }
  if (variable_get('commerce_avatax_tax_address', '') == 'Shipping') {
    if (isset($order_wrapper->commerce_customer_shipping->commerce_customer_address)) {
      $shipping_address = $order_wrapper->commerce_customer_shipping->commerce_customer_address
        ->value();
      $street1 = $shipping_address['thoroughfare'];
      $street2 = $shipping_address['premise'];
      $city = $shipping_address['locality'];
      $state = $shipping_address['administrative_area'];
      $country = $shipping_address['country'];
      $zip = $shipping_address['postal_code'];
    }
  }

  // exit if address is incomplete
  if (!$street1 || !$city || !$state || !$zip) {
    drupal_set_message(t('Sales tax not calculated as shipping address incomplete. Please select "Cancel" and try again.'), 'error');
    return $sales_tax;
  }

  // exit if delivery address state is not in list of active states
  $avatax_states = array();
  $avatax_states_str = variable_get('commerce_avatax_select_states', '');
  if ($avatax_states_str) {
    $avatax_states = explode(", ", $avatax_states_str);
    if (!in_array($state, $avatax_states)) {
      return $sales_tax;
    }
  }

  // Get primary business location
  $primary_street1 = variable_get('commerce_avatax_primary_street1', '');
  $primary_street2 = variable_get('commerce_avatax_primary_street2', '');
  $primary_city = variable_get('commerce_avatax_primary_city', '');
  $primary_state = variable_get('commerce_avatax_primary_state', '');
  $primary_zip = variable_get('commerce_avatax_primary_zip', '');

  // Get User name or e-mail address
  if ($order->uid == 0) {
    if (arg(0) == 'system' && $order->order_id == 0) {
      drupal_set_message(t('Order # has not been allocated. Please select "Cancel". You must FIRST save the order to allocate an order # - then add sales tax. '), 'error');
      return $sales_tax;
    }
    elseif ($order->order_id != 0 && $order->mail == '') {
      $user_id = 'administrator';
    }
    else {
      $user_email = $order->mail;
      $user_id = commerce_avatax_email_to_username($user_email);
    }
  }
  else {
    $user_data = user_load($order->uid);
    $user_id = $user_data->name;
  }

  // Construct arguments for AvaTax functions
  $ava_args = compact('company_code', 'pro_mode', 'user_id', 'shipcode', 'use_mode', 'street1', 'street2', 'city', 'state', 'country', 'zip', 'primary_street1', 'primary_street2', 'primary_city', 'primary_state', 'primary_zip');

  // Get tax amount - conditional on version
  $avatax_result = commerce_avatax_get_tax($order, $order_wrapper, $ava_args);

  // Check that there was a return from the tax request.
  if (!$avatax_result) {

    // AvaTax will return an error code - Integrator to determine if the checkout is to be blocked!
    return $sales_tax;
  }
  $sales_tax = array(
    'amount' => $avatax_result['tax_amount'] * 100,
    'currency_code' => 'USD',
    'data' => array(),
    'tax_details' => $avatax_result['tax_details'],
  );
  return $sales_tax;
}

// Generate AvaTax user name as approximation of e-mail address
function commerce_avatax_email_to_username($user_email) {

  // Default to the first part of the e-mail address.
  $name = substr($user_email, 0, strpos($user_email, '@'));

  // Remove possible illegal characters.
  $name = preg_replace('/[^A-Za-z0-9_.-]/', '', $name);

  // Trim that value for spaces and length.
  $name = trim(substr($name, 0, USERNAME_MAX_LENGTH - 4));
  return $name;
}

Functions

Namesort descending Description
commerce_avatax_add_avatax_line_item Adds a avatax line item to an order, and saves the order.
commerce_avatax_calculate_sales_tax Calculate sales tax using regular web site checkout.
commerce_avatax_commerce_line_item_type_info Implements hook_commerce_line_item_type_info().
commerce_avatax_commerce_order_update Implements hook_commerce_order_update().
commerce_avatax_commerce_price_component_type_info Implements hook_commerce_price_component_type_info().
commerce_avatax_configure_line_item Line item callback: configures the AvaTax line item type on module enable.
commerce_avatax_delete_avatax_line_items Deletes all AvaTax line items on an order.
commerce_avatax_email_to_username
commerce_avatax_libraries_info Implements hook_libraries_info().
commerce_avatax_line_item_add_form Returns a form with the elements required to add an AvaTax line item through the line item manager widget.
commerce_avatax_line_item_add_form_submit Adds the selected tax saas information to a new tax saas line item.
commerce_avatax_line_item_create Creates a sales tax line item
commerce_avatax_line_item_new Creates a new AvaTax line item populated with the sales tax values.
commerce_avatax_line_item_populate Populates a sales tax line item with the specified values.
commerce_avatax_line_item_title Returns the title of an avatax line item.
commerce_avatax_manual_calculate_sales_tax Calculate sales tax for manual order entry.
commerce_avatax_options Turns the sales tax amount into a form element options array.
commerce_avatax_permission Implements hook_permission().
commerce_avatax_retrieve_sales_tax AvaTax service: returns the sales tax amount as an array.
commerce_avatax_service_load Returns the avatax service array.