You are here

Avatax.php in Drupal Commerce Connector for AvaTax 7.5

Defines a class for consuming the Avatax API.

File

lib/Avatax.php
View source
<?php

/**
 * @file
 * Defines a class for consuming the Avatax API.
 */

/**
 * Defines the Avatax class.
 *
 * A modern PHP library would namespace its classes under a package name, which
 * in this case would mean using the Percolate namespace and instantiating new
 * objects of this class via:
 *
 * $avatax = new Avalara\Avatax(...);
 *
 * Unfortunately, Drupal 7 does not support namespaces in its autoloader, as it
 * maintains compatibility with previous versions of PHP that did not support
 * namespaces. Thus this library does not currently use a namespace.
 */
class Avatax {

  // Define properties for storing API credentials.
  protected $apiKey;

  // The API mode (dev|prod).
  protected $apiMode;

  // Reference the logger callable.
  protected $logger;

  // Manage a single cURL handle used to submit API requests.
  protected $ch;

  // Stores the HTTP headers.
  protected $headers;

  /**
   * Initializes the API credential properties and cURL handle.
   * @param string $apiKey
   *   The API key defined that is used to authenticate against the API.
   * @param string $apiMode
   *   The API mode (dev|prod), used to determine the endpoint to call.
   * @param string $logger
   *   A callable used to log API request / response messages. Leave empty if
   *   logging is not needed.
   * @param array $headers
   *   Allow specifying additional HTTP headers that are going to be sent.
   */
  public function __construct($apiKey, $apiMode = 'dev', $logger = NULL, $headers = array()) {

    // Initialize the API credential properties.
    $this->apiKey = $apiKey;
    $this->apiMode = $apiMode;
    $this->logger = $logger;
    $this->headers = array_merge($headers, array(
      'Authorization' => 'Basic ' . $apiKey,
      'Content-Type' => 'application/json',
      'x-Avalara-UID' => 'a0o33000003waOC',
    ));

    // Initialize the cURL handle.
    $this->ch = curl_init();
    $this
      ->setDefaultCurlOptions();
  }

  /**
   * Returns the HTTP headers.
   *
   * @return array
   *   The HTTP headers used when submitting API requests.
   */
  public function httpHeaders() {
    return $this->headers;
  }

  /**
   * Gets the API mode.
   *
   * @return string
   *   The API mode (dev|prod).
   */
  public function getApiMode() {
    return $this->apiMode;
  }

  /**
   * Gets the API url.
   *
   * @return string
   *   The API url.
   */
  public function getApiUrl() {
    if ($this
      ->getApiMode() == 'dev') {
      return 'https://sandbox-rest.avatax.com/api/v2/';
    }
    else {
      return 'https://rest.avatax.com/api/v2/';
    }
  }

  /**
   * Returns the object's API key.
   *
   * @return string
   *   The API key.
   */
  public function getApiKey() {
    return $this->apiKey;
  }

  /**
   * Closes the cURL handle when the object is destroyed.
   */
  public function __destruct() {
    if (is_resource($this->ch)) {
      curl_close($this->ch);
    }
  }

  /**
   * Tests connectivity and version of the service.
   */
  public function ping() {
    return $this
      ->doRequest('GET', 'utilities/ping');
  }

  /**
   * Retrieve geolocation information for a specified address.
   *
   * @param array $parameters
   *   An associate array of POST body parameters containing the address to
   *   geolocate.
   * @return array
   *   See https://developer.avalara.com/api-reference/avatax/rest/v2/models/AddressResolutionModel/
   */
  public function addressesResolve(array $parameters) {
    $parameters += array(
      'textCase' => 'Mixed',
    );
    return $this
      ->doRequest('POST', 'addresses/resolve', $parameters);
  }

  /**
   * Create a new transaction.
   *
   * @param string[] $parameters
   *   An associative array of POST body parameters to be sent that should at
   *   least contain the companycode, the code, the date, and the customerCode.
   */
  public function transactionsCreate($parameters) {
    return $this
      ->doRequest('POST', "transactions/create", $parameters);
  }

  /**
   * Correct a previously created transaction.
   *
   * @param string $companyCode
   *   The company code of the company that recorded these transactions.
   *
   * @param string $transactionCode
   *   The transaction code to adjust.
   *
   * @param string[] $parameters
   *   An associative array of POST body parameters that should contain the
   *   adjustmentReason & adjustmentDescription.
   *
   * @return array
   */
  public function transactionsAdjust($companyCode, $transactionCode, $parameters) {
    return $this
      ->doRequest('POST', "companies/{$companyCode}/transactions/{$transactionCode}/adjust", $parameters);
  }

  /**
   * Void a transaction
   *
   * @param string $companyCode
   *   The company code of the company that recorded these transactions.
   *
   * @param string $transactionCode
   *   The transaction code to void.
   *
   * @param string[] $parameters
   *   An associative array of POST body parameters that should contain the
   *   code (the reason for voiding or cancelling this transaction).
   *
   * @return array
   *
   */
  public function transactionsVoid($companyCode, $transactionCode, $parameters) {
    return $this
      ->doRequest('POST', "companies/{$companyCode}/transactions/{$transactionCode}/void", $parameters);
  }

  /**
   * Commit a transaction for reporting.
   *
   * @param string $companyCode
   *   The company code of the company that recorded these transactions.
   *
   * @param string $transactionCode
   *   The transaction code to commit.
   *
   * @return array
   *   An associative array containing the id number of the transaction, the
   *   code, the companyId, date etc.
   */
  public function transactionsCommit($companyCode, $transactionCode) {
    return $this
      ->doRequest('POST', "companies/{$companyCode}/transactions/{$transactionCode}/commit", array(
      'commit' => TRUE,
    ));
  }

  /**
   * Sets the default cURL options.
   */
  public function setDefaultCurlOptions() {
    $headers = array();
    foreach ($this
      ->httpHeaders() as $key => $value) {
      $headers[] = "{$key}: {$value}";
    }
    curl_setopt($this->ch, CURLOPT_HEADER, FALSE);
    curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, TRUE);
    curl_setopt($this->ch, CURLOPT_VERBOSE, FALSE);
    curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, 30);
    curl_setopt($this->ch, CURLOPT_TIMEOUT, 180);
  }

  /**
   * Send a message to the logger.
   *
   * @param string $message
   *   The message to log.
   * @param $variables
   *   Array of variables to replace in the message on display or
   *   NULL if message is already translated or not possible to
   *   translate.
   * @param int $severity
   *   The severity of the message; one of the following values:
   *   - WATCHDOG_EMERGENCY: Emergency, system is unusable.
   *   - WATCHDOG_ALERT: Alert, action must be taken immediately.
   *   - WATCHDOG_CRITICAL: Critical conditions.
   *   - WATCHDOG_ERROR: Error conditions.
   *   - WATCHDOG_WARNING: Warning conditions.
   *   - WATCHDOG_NOTICE: (default) Normal but significant conditions.
   *   - WATCHDOG_INFO: Informational messages.
   *   - WATCHDOG_DEBUG: Debug-level messages.
   *
   * @see http://www.faqs.org/rfcs/rfc3164.html
   */
  public function logMessage($message, $variables = array(), $severity = WATCHDOG_NOTICE) {
    if (is_callable($this->logger)) {
      call_user_func_array($this->logger, array(
        'commerce_avatax',
        $message,
        $variables,
        $severity,
      ));
    }
  }

  /**
   * Performs a request.
   *
   * @param string $method
   *   The HTTP method to use. One of: 'GET', 'POST', 'PUT', 'DELETE'.
   * @param string $path
   *   The remote path. The base URL will be automatically appended.
   * @param array $fields
   *   An array of fields to include with the request. Optional.
   *
   * @return array
   *   An array with the 'success' boolean and the result. If 'success' is FALSE
   *   the result will be an error message. Otherwise it will be an array
   *   of returned data.
   */
  protected function doRequest($method, $path, array $fields = array()) {
    $return = array();
    $url = $this
      ->getApiUrl() . $path;

    // Set the request URL and method.
    curl_setopt($this->ch, CURLOPT_URL, $url);
    curl_setopt($this->ch, CURLINFO_HEADER_OUT, TRUE);
    curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $method);
    if (!empty($fields)) {

      // JSON encode the fields and set them to the request body.
      $fields = json_encode($fields);
      curl_setopt($this->ch, CURLOPT_POSTFIELDS, $fields);

      // Log the API request with the JSON encoded fields.
    }
    $result = curl_exec($this->ch);
    $response_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
    $success = in_array($response_code, array(
      200,
      201,
    ));

    // Log information about the request.
    $this
      ->logMessage('Request info: !url !headers !response !meta', array(
      '!url' => "<pre>URL : {$method} {$url}</pre>",
      '!headers' => "<pre>Request Headers:\n" . var_export(curl_getinfo($this->ch, CURLINFO_HEADER_OUT), TRUE) . '</pre>',
      '!response' => "<pre>Response:\n" . var_export($result, TRUE) . '</pre>',
      '!meta' => "<pre>Response Meta:\n" . var_export(curl_getinfo($this->ch), TRUE) . '</pre>',
    ));
    if (!$success) {

      // Return the error message if it exists.
      if (!empty($result)) {
        $decoded_result = json_decode($result, TRUE);

        // Return the error message if it's there.
        if (isset($decoded_result['error'])) {
          $return['error'] = $decoded_result['error'];
        }
      }
      $result = 'Error ' . $response_code;
    }
    elseif ($success && !empty($result)) {
      $result = json_decode($result, TRUE);
    }
    $return += array(
      'success' => $success,
      'result' => $result,
      'response_code' => $response_code,
    );
    return $return;
  }

}

Classes

Namesort descending Description
Avatax Defines the Avatax class.