You are here

commerce_square.module in Commerce Square Connect 7

Same filename and directory in other branches
  1. 8 commerce_square.module

Module file for Commerce Square.

File

commerce_square.module
View source
<?php

/**
 * @file
 * Module file for Commerce Square.
 */
use SquareConnect\Api\LocationsApi;
use SquareConnect\Api\OAuthApi;
use SquareConnect\Api\OrdersApi;
use SquareConnect\Api\TransactionsApi;
use SquareConnect\ApiException;
use SquareConnect\Model\ChargeRequest;
use SquareConnect\Model\CreateOrderRequest;
use SquareConnect\Model\Money;
use SquareConnect\Model\ObtainTokenRequest;
use SquareConnect\Model\OrderLineItem;

/**
 * Implements hook_libraries_info().
 */
function commerce_square_libraries_info() {
  $libraries['square'] = array(
    'name' => 'Square connect SDK library',
    'vendor url' => 'https://squareup.com',
    'download url' => 'https://github.com/square/connect-php-sdk',
    'version arguments' => array(
      'file' => 'composer.json',
      'pattern' => '/"version": "(\\d+\\.\\d+\\.\\d+)"/',
    ),
    'files' => array(
      'php' => array(
        'autoload.php',
      ),
    ),
  );
  return $libraries;
}

/**
 * Implements hook_permission().
 */
function commerce_square_permission() {
  return array(
    'administer commerce_square' => array(
      'title' => t('Administer Commerce Square'),
      'description' => t('Allows configuring Commerce Square.'),
    ),
  );
}

/**
 * Implements hook_commerce_payment_method_info().
 */
function commerce_square_commerce_payment_method_info() {
  $payment_methods['commerce_square'] = array(
    'base' => 'commerce_square_payment_method',
    'title' => t('Square Connect'),
    'short_title' => t('Square'),
    'description' => t('Square Connect Payment Method'),
    'terminal' => TRUE,
    'cardonfile' => array(
      'create callback' => 'commerce_square_cardonfile_create',
      'charge callback' => 'commerce_square_cardonfile_charge',
      'update callback' => 'commerce_square_cardonfile_update',
      'delete callback' => 'commerce_square_cardonfile_delete',
    ),
  );
  return $payment_methods;
}

/**
 * Returns the default settings for the Square module.
 */
function commerce_square_default_settings() {
  return array(
    'app_name' => '',
    'app_secret' => '',
    'test_app_id' => '',
    'test_access_token' => '',
    'live_app_id' => '',
    'live_access_token' => '',
    'live_access_token_expiry' => '',
    'live_access_refresh_token' => '',
  );
}

/**
 * Returns the default settings for the Square payment method.
 */
function commerce_square_payment_method_default_settings() {
  return array(
    'mode' => 'test',
    'type' => COMMERCE_CREDIT_AUTH_CAPTURE,
    'cardonfile' => FALSE,
    'test_location_id' => '',
    'live_location_id' => '',
  );
}

/**
 * Payment method form callback.
 */
function commerce_square_payment_method_settings_form($settings) {
  $square_settings = variable_get('commerce_square_settings', commerce_square_default_settings()) + commerce_square_default_settings();
  $settings = $settings + commerce_square_payment_method_default_settings();
  libraries_load('square');
  $form['mode'] = array(
    '#type' => 'radios',
    '#title' => t('Mode'),
    '#options' => _commerce_square_get_supported_modes(),
    '#default_value' => empty($settings['mode']) ? 'test' : $settings['mode'],
    '#required' => TRUE,
  );
  $form['type'] = array(
    '#type' => 'radios',
    '#title' => t('Default credit card transaction type'),
    '#description' => t('The default will be used to process transactions during checkout.'),
    '#options' => array(
      COMMERCE_CREDIT_AUTH_CAPTURE => t('Authorization and capture'),
      COMMERCE_CREDIT_AUTH_ONLY => t('Authorization only (requires manual or automated capture after checkout)'),
    ),
    '#default_value' => $settings['type'],
  );
  foreach (_commerce_square_get_supported_modes() as $mode => $name) {
    $form[$mode . '_location_id'] = array(
      '#type' => 'select',
      '#title' => t('@mode Location', array(
        '@mode' => $name,
      )),
      '#description' => t('The location for the transactions.'),
      '#default_value' => $settings[$mode . '_location_id'],
      '#required' => TRUE,
    );
    $access_token = $square_settings[$mode . '_access_token'];
    if (!empty($access_token)) {
      $square_api = new SquareApi($access_token, $mode);
      $location_api = new LocationsApi($square_api
        ->getClient());
      try {
        $locations = $location_api
          ->listLocations();
        $location_options = $locations
          ->getLocations();
        $options = array();
        foreach ($location_options as $location_option) {
          $options[$location_option
            ->getId()] = $location_option
            ->getName();
        }
        $form[$mode . '_location_id']['#options'] = $options;
      } catch (\Exception $e) {
        drupal_set_message($e
          ->getMessage(), 'error');
      }
    }
    else {
      $form[$mode][$mode . '_location_id']['#disabled'] = TRUE;
      $form[$mode][$mode . '_location_id']['#options'] = array(
        '_none' => 'Not configured',
      );
    }
  }
  if (module_exists('commerce_cardonfile')) {
    $form['cardonfile'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable Card on File functionality with this payment method.'),
      '#default_value' => $settings['cardonfile'],
    );
  }
  return $form;
}

/**
 * Returns the payment method modes.
 */
function _commerce_square_get_supported_modes() {
  return array(
    'test' => t('Sandbox'),
    'live' => t('Production'),
  );
}

/**
 * Renew access token.
 *
 * Triggered by cron via commerce_square_cron().
 */
function commerce_square_renew_access_token() {
  $settings = variable_get('commerce_square_settings', commerce_square_default_settings()) + commerce_square_default_settings();
  libraries_load('square');

  // If site has not yet had Square app configured, do nothing.
  if (empty($settings['live_app_id']) || empty($settings['live_access_token'])) {
    return FALSE;
  }

  // OAuth access tokens expire after 30 days. renew a token before it expires.
  // Begin attempting to renew on the 29th day.
  // https://developer.squareup.com/docs/oauth-api/cookbook/renew-oauth-tokens.
  if (!empty($settings['live_access_token_expiry']) && $settings['live_access_token_expiry'] - 86400 < time()) {

    // Use OAuth API and ObtainToken endpoint if SDK version accommodates.
    if (class_exists('SquareConnect\\Api\\OAuthApi') && method_exists('SquareConnect\\Model\\ObtainTokenRequest', 'setRefreshToken')) {
      $oauth_api = new OAuthApi();
      $request_body = new ObtainTokenRequest();

      // Use refresh token, if present, and make request via OAuth API.
      if (!empty($settings['live_access_refresh_token'])) {
        $request_body
          ->setClientId($settings['live_app_id']);
        $request_body
          ->setClientSecret($settings['app_secret']);
        $request_body
          ->setGrantType('refresh_token');
        $request_body
          ->setRefreshToken($settings['live_access_refresh_token']);
        try {
          $result = $oauth_api
            ->obtainToken($request_body);
        } catch (Exception $e) {
          watchdog('commerce_square', 'Square token refresh failed. Message: @message', array(
            '@message' => $e
              ->getMessage(),
          ), WATCHDOG_ERROR);
          return FALSE;
        }
        $access_token = !empty($result) ? $result
          ->getAccessToken() : FALSE;
        if (!empty($access_token)) {
          $settings['live_access_token'] = $access_token;
          $settings['live_access_token_expiry'] = strtotime($result
            ->getExpiresAt());
          $settings['live_access_refresh_token'] = $result
            ->getRefreshToken();
          variable_set('commerce_square_settings', $settings);
          watchdog('commerce_square', 'Square token refresh successful.', array(), WATCHDOG_INFO);
        }
        else {
          watchdog('commerce_square', 'Square token refresh failed: no access token returned by ObtainToken endpoint when using refresh_token.', array(), WATCHDOG_ERROR);
        }
      }
      else {
        $request_body
          ->setClientId($settings['live_app_id']);
        $request_body
          ->setClientSecret($settings['app_secret']);
        $request_body
          ->setGrantType('migration_token');
        $request_body
          ->setMigrationToken($settings['live_access_token']);
        try {
          $result = $oauth_api
            ->obtainToken($request_body);
        } catch (Exception $e) {
          watchdog('commerce_square', 'Square token refresh via migration_token failed. Message: @message', array(
            '@message' => $e
              ->getMessage(),
          ), WATCHDOG_ERROR);
          return FALSE;
        }
        $access_token = !empty($result) ? $result
          ->getAccessToken() : FALSE;
        if (!empty($access_token)) {
          $settings['live_access_token'] = $access_token;
          $settings['live_access_token_expiry'] = strtotime($result
            ->getExpiresAt());
          $settings['live_access_refresh_token'] = $result
            ->getRefreshToken();
          variable_set('commerce_square_settings', $settings);
          watchdog('commerce_square', 'Square token refresh via migration_token successful.', array(), WATCHDOG_INFO);
        }
        else {
          watchdog('commerce_square', 'Square token migration failed: no access token returned by ObtainToken endpoint when using migration_token.', array(), WATCHDOG_ERROR);
        }
      }
    }
    else {
      $data = array(
        'access_token' => $settings['live_access_token'],
      );
      $data_string = json_encode($data);
      $ch = curl_init('https://connect.squareup.com/oauth2/clients/' . $settings['live_app_id'] . '/access-token/renew');
      curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
      curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
      curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json',
        'Content-Length: ' . strlen($data_string),
        'Authorization: Client ' . $settings['app_secret'],
      ));
      $response = curl_exec($ch);
      $response_body = drupal_json_decode($response);
      if (!empty($response_body['access_token'])) {
        $settings['live_access_token'] = $response_body['access_token'];
        $settings['live_access_token_expiry'] = strtotime($response_body['expires_at']);
        variable_set('commerce_square_settings', $settings);
        watchdog('commerce_square', 'Square token refresh via deprecated RenewToken endpoint successful. Please consider upgrading the Square SDK.', array(), WATCHDOG_INFO);
      }
      else {
        watchdog('commerce_square', 'Square token refresh via deprecated RenewToken endpoint failed. Please consider upgrading the Square SDK.', array(), WATCHDOG_ERROR);
      }
    }
  }
}

/**
 * Implements hook_menu().
 */
function commerce_square_menu() {
  $items = array();
  $items['admin/commerce/config/square'] = array(
    'title' => 'Square settings',
    'description' => 'Configure Square.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_square_settings_form',
    ),
    'access arguments' => array(
      'administer commerce_square',
    ),
    'file' => 'includes/commerce_square.admin.inc',
  );
  $items['admin/commerce_square/oauth/obtain'] = array(
    'title' => 'Obtain Square Merchant Token',
    'page callback' => 'commerce_square_obtain_token',
    'access callback' => 'commerce_square_obtain_token_access',
    'type' => MENU_CALLBACK,
  );
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/square-capture'] = array(
    'title' => 'Capture',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_square_capture_form',
      3,
      5,
    ),
    'access callback' => 'commerce_square_payment_transaction_access',
    'access arguments' => array(
      3,
      5,
      'capture',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'file' => 'includes/commerce_square.admin.inc',
    'weight' => 10,
  );
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/square-void'] = array(
    'title' => 'Void',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_square_void_form',
      3,
      5,
    ),
    'access callback' => 'commerce_square_payment_transaction_access',
    'access arguments' => array(
      3,
      5,
      'void',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'file' => 'includes/commerce_square.admin.inc',
    'weight' => 10,
  );
  $items['admin/commerce/orders/%commerce_order/payment/%commerce_payment_transaction/square-refund'] = array(
    'title' => 'Refund',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'commerce_square_refund_form',
      3,
      5,
    ),
    'access callback' => 'commerce_square_payment_transaction_access',
    'access arguments' => array(
      3,
      5,
      'refund',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_INLINE,
    'file' => 'includes/commerce_square.admin.inc',
    'weight' => 10,
  );
  return $items;
}

/**
 * Square OAuth flow menu callback to redirect back to the payment method form.
 */
function commerce_square_obtain_token() {
  $options = array(
    'query' => array(
      'code' => $_GET['code'],
    ),
  );
  drupal_goto('admin/commerce/config/square', $options);
}

/**
 * Square OAuth flow menu access callback.
 */
function commerce_square_obtain_token_access() {
  if (empty($_GET['state'])) {
    return FALSE;
  }
  if (drupal_valid_token(check_plain($_GET['state']))) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Square payment checkout pane form callback.
 */
function commerce_square_payment_method_submit_form($payment_method, $pane_values, $checkout_pane, $order) {
  $settings = variable_get('commerce_square_settings', commerce_square_default_settings()) + commerce_square_default_settings();
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  $postal_code = NULL;
  if (!empty($order->commerce_customer_billing) && !empty($order_wrapper->commerce_customer_billing
    ->value()->commerce_customer_address)) {
    $address_value = $order_wrapper->commerce_customer_billing->commerce_customer_address
      ->value();
    if (!empty($address_value['postal_code'])) {
      $postal_code = $address_value['postal_code'];
    }
  }
  $mode = $payment_method['settings']['mode'];
  $element['#attached'] = array(
    'js' => array(
      array(
        'data' => array(
          'commerceSquare' => array(
            'applicationId' => $settings[$mode . '_app_id'],
            'postalCode' => $postal_code,
          ),
        ),
        'type' => 'setting',
      ),
    ),
  );
  _commerce_square_ensure_assets_attached($element, $mode);
  $element['#type'] = 'container';
  $element['#attributes']['class'][] = 'square-form';

  // Populated by the JS library.
  $element['payment_method_nonce'] = array(
    '#type' => 'hidden',
    '#attributes' => array(
      'class' => array(
        'square-nonce',
      ),
    ),
  );
  $element['card_type'] = array(
    '#type' => 'hidden',
    '#attributes' => array(
      'class' => array(
        'square-card-type',
      ),
    ),
  );
  $element['last4'] = array(
    '#type' => 'hidden',
    '#attributes' => array(
      'class' => array(
        'square-last4',
      ),
    ),
  );
  $element['exp_month'] = array(
    '#type' => 'hidden',
    '#attributes' => array(
      'class' => array(
        'square-exp-month',
      ),
    ),
  );
  $element['exp_year'] = array(
    '#type' => 'hidden',
    '#attributes' => array(
      'class' => array(
        'square-exp-year',
      ),
    ),
  );
  $element['number'] = array(
    '#type' => 'item',
    '#title' => t('Card number'),
    '#markup' => '<div id="square-card-number"></div>',
  );
  $element['details'] = array(
    '#type' => 'container',
    '#attributes' => array(
      'class' => array(
        'credit-card-form__expiration',
      ),
    ),
  );
  $element['details']['expiration'] = array(
    '#type' => 'item',
    '#title' => t('Expiration'),
    '#markup' => '<div id="square-expiration-date"></div>',
  );
  $element['details']['cvv'] = array(
    '#type' => 'item',
    '#title' => t('CVV'),
    '#markup' => '<div id="square-cvv"></div>',
  );
  $element['details']['postal-code'] = array(
    '#type' => 'item',
    '#title' => t('Postal code'),
    '#markup' => '<div id="square-postal-code"></div>',
  );
  return $element;
}

/**
 * Square payment checkout pane validate callback.
 */
function commerce_square_payment_method_submit_form_validate($payment_method, $pane_form, $pane_values, $order) {
  if (empty($pane_values['payment_method_nonce'])) {
    drupal_set_message('There was an error collecting the payment information.', 'error');
    return FALSE;
  }
  libraries_load('square');
  $mode = $payment_method['settings']['mode'];
  $location_id = $payment_method['settings'][$mode . '_location_id'];
  $charge = commerce_payment_order_balance($order);
  $square_total_amount = $charge['amount'];
  $square_order_currency = $charge['currency_code'];
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  $square_api = SquareApi::createFromInstanceId($payment_method['instance_id']);
  $api_client = $square_api
    ->getClient();
  $order_api_instance = new OrdersApi($api_client);
  $charge_api_instance = new TransactionsApi($api_client);

  // Containers for accumulating line item data
  $line_items = array();
  $square_line_item_total = 0;

  // Iterate through Drupal Commerce standard line items
  foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) {
    $line_item = new OrderLineItem();
    $base_price_money = new Money();
    $base_price_money
      ->setAmount((int) $line_item_wrapper->commerce_unit_price->amount
      ->value());
    $base_price_money
      ->setCurrency($line_item_wrapper->commerce_unit_price->currency_code
      ->value());
    $line_item
      ->setBasePriceMoney($base_price_money);
    $line_item_label = $line_item_wrapper->line_item_label
      ->value();

    // Convert line item label to product title + (SKU).
    if (isset($line_item_wrapper->commerce_product)) {
      $line_item_label = $line_item_wrapper->commerce_product->title
        ->value() . ' (' . $line_item_wrapper->commerce_product->sku
        ->value() . ')';
    }
    $line_item
      ->setName($line_item_label);

    // Quantity needs to be a string integer, it cannot be a float.
    $line_item
      ->setQuantity((string) (int) $line_item_wrapper->quantity
      ->value());

    // Handling DISCOUNTS and VAT:
    // If either are included in line item price, include details in Note field.
    // Note that we are NOT adding them as Square discount/tax elements because of
    // limitations in Square's discount/tax calculation flexibility and to avoid any
    // discrepancies in totals between Drupal and Square.
    $line_item_price_data_array = $line_item_wrapper->commerce_unit_price->data
      ->value();
    $line_item_price_components = $line_item_price_data_array['components'];
    if (count($line_item_price_components) > 1) {
      $price_component_notes = array();
      foreach ($line_item_price_components as $price) {

        // Skip base_price component and components that are not included as
        // part of the price (ie, VAT.)
        if ($price['name'] === 'base_price' || $price['included'] === FALSE) {
          continue;
        }
        $formatted_component_price = commerce_currency_format($price['price']['amount'], $price['price']['currency_code']);
        $component_label = $price['name'];

        // If the price component is from the commerce_discount module and
        // provides a component title, use that.
        if (isset($price['price']['data']['discount_component_title'])) {
          $component_label = $price['price']['data']['discount_component_title'];
        }
        elseif ($price['name'] === 'discount') {
          $component_label = t('Sale');
        }
        elseif (isset($price['price']['data']['tax_rate']['display_title'])) {
          $component_label = $price['price']['data']['tax_rate']['display_title'];
        }
        $price_component_notes[] = $component_label . ' ' . $formatted_component_price;
      }

      // Format Note if there was at least one component to be displayed.
      if (count($price_component_notes) > 0) {
        $adjust_text = format_plural(count($price_component_notes), 'Adjustment', 'Adjustments');
        $include_text = t('included in price (per item):');
        $notes_text = implode('; ', $price_component_notes);
        $line_item
          ->setNote($adjust_text . ' ' . $include_text . ' ' . $notes_text);
      }
    }
    $line_items[] = $line_item;
    $square_line_item_total += $line_item_wrapper->commerce_total->amount
      ->value();
  }

  // ONLY IF commerce_tax is installed & enabled:
  // Add order-level taxes (EXCLUDED from line item price) as generic line items,
  // ignoring Square's tax functionality to avoid discrepancies in calculations and rounding.
  // Note: if any other tax module is being used, it will NOT be added as a line item here,
  // but will be handled with the catch-all final "adjustments"
  if (module_exists('commerce_tax')) {
    $order_total_data_array = $order_wrapper->commerce_order_total->data
      ->value();
    $order_taxes = commerce_tax_components($order_total_data_array['components']);
    foreach ($order_taxes as $tax) {
      if ($tax['included'] === TRUE) {
        continue;
      }
      $tax_money = new Money();
      $tax_money
        ->setAmount((int) $tax['price']['amount']);
      $tax_money
        ->setCurrency($tax['price']['currency_code']);
      $line_item = new OrderLineItem();
      $line_item
        ->setBasePriceMoney($tax_money);
      $line_item
        ->setName($tax['price']['data']['tax_rate']['display_title']);
      $line_item
        ->setQuantity('1');

      // Done building line item. Add it to line items array.
      $line_items[] = $line_item;

      // Add line item total to running order total
      $square_line_item_total += $tax['price']['amount'];
    }
  }

  // Square requires the order total to match the payment amount, the following
  // logic accommodates for rounding error or other omitted calculations.
  if ($square_line_item_total != $square_total_amount) {
    $diff = $square_total_amount - $square_line_item_total;
    $total_money = new Money();
    $total_money
      ->setAmount($diff);
    $total_money
      ->setCurrency($square_order_currency);
    $line_item = new OrderLineItem();
    $line_item
      ->setBasePriceMoney($total_money);
    $line_item
      ->setName(t('Adjustment'));
    $line_item
      ->setQuantity('1');
    $line_items[] = $line_item;
  }

  // Start building the Square Order.
  $order_request = new CreateOrderRequest();
  $order_request
    ->setIdempotencyKey(uniqid($order->order_id . '-', TRUE));
  $order_request
    ->setReferenceId($order->order_id);
  $order_request
    ->setLineItems($line_items);

  // Make the Order API call
  try {
    $order_result = $order_api_instance
      ->createOrder($location_id, $order_request);
  } catch (ApiException $e) {
    $response = $e
      ->getResponseBody();
    $error = $response->errors[0];
    drupal_set_message($error->detail, 'error');
    $vars = array(
      '@category' => $error->category,
      '@code' => $error->code,
      '@detail' => $error->detail,
    );
    watchdog('commerce_square', 'Square order error. category: @category code: @code detail: @detail', $vars);
    return FALSE;
  }

  //// Above is all processing needed to prepare Order & get Square Order ID

  //// Below we process the ChargeRequest
  $charge_amount = new Money();
  $charge_amount
    ->setAmount((int) $charge['amount']);
  $charge_amount
    ->setCurrency($charge['currency_code']);
  $charge_request = new IntegrationChargeRequest();

  // Link the transaction to this order.
  $charge_request
    ->setOrderId($order_result
    ->getOrder()
    ->getId());
  $charge_request
    ->setAmountMoney($charge_amount);
  $charge_request
    ->setDelayCapture($payment_method['settings']['type'] == COMMERCE_CREDIT_AUTH_ONLY);
  $charge_request
    ->setCardNonce($pane_values['payment_method_nonce']);
  $charge_request
    ->setIdempotencyKey(uniqid('', TRUE));
  $charge_request
    ->setBuyerEmailAddress($order->mail);

  // The Square note field identifies transactions in the Square dashboard.
  // TODO: make site-configurable using tokens
  $charge_label = t('Order #@order_number, @store', array(
    '@order_number' => $order->order_id,
    '@store' => variable_get('site_name'),
  ));

  // Trim to max 60 characters (Square limit on field).
  $charge_label = substr($charge_label, 0, 60);
  $charge_request
    ->setNote($charge_label);

  // The `integration_id` is only valid when live.
  if ($mode === 'live') {
    $charge_request
      ->setIntegrationId('sqi_b6ff0cd7acc14f7ab24200041d066ba6');
  }
  try {
    $result = $charge_api_instance
      ->charge($location_id, $charge_request);
    $order->square_result = $result;
  } catch (ApiException $e) {
    $response = $e
      ->getResponseBody();
    $error = $response->errors[0];
    drupal_set_message($error->detail, 'error');
    $vars = array(
      '@category' => $error->category,
      '@code' => $error->code,
      '@detail' => $error->detail,
    );
    watchdog('commerce_square', 'Square transaction error. category: @category code: @code detail: @detail', $vars);
    return FALSE;
  }
  return TRUE;
}

/**
 * Square payment checkout pane submit callback.
 */
function commerce_square_payment_method_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) {

  /** @var \SquareConnect\Model\ChargeResponse $result */
  $result = $order->square_result;
  $transaction = commerce_payment_transaction_new('commerce_square', $order->order_id);
  $transaction->instance_id = $payment_method['instance_id'];
  $transaction->amount = $charge['amount'];
  $transaction->currency_code = $charge['currency_code'];
  $transaction->status = $payment_method['settings']['type'] == COMMERCE_CREDIT_AUTH_ONLY ? COMMERCE_PAYMENT_STATUS_PENDING : COMMERCE_PAYMENT_STATUS_SUCCESS;
  $remote_transaction = $result
    ->getTransaction();
  $tender = $remote_transaction
    ->getTenders();
  $tender = $tender[0];
  $transaction->remote_id = $remote_transaction
    ->getId() . '|' . $tender
    ->getId();
  $transaction->message = $tender
    ->getNote();
  commerce_payment_transaction_save($transaction);
}

/**
 * Implements hook_form_alter().
 *
 * Workaround https://www.drupal.org/node/1988968#comment-12032406. We need to
 * add the js files to the payment method selection since drupal AJAX cannot
 * ensure the proper loading of new js files.
 */
function commerce_square_form_alter(&$form, $form_state) {
  $square_payment_method = FALSE;
  if (isset($form['commerce_payment']['payment_methods']['#value']) && is_array($form['commerce_payment']['payment_methods']['#value'])) {
    foreach ($form['commerce_payment']['payment_methods']['#value'] as $key => $method) {
      if ($method['method_id'] === 'commerce_square') {
        $square_payment_method = TRUE;
        break;
      }
    }
    if ($square_payment_method) {
      _commerce_square_ensure_assets_attached($form, $method['settings']['mode']);
    }
  }
}

/**
 * Ensures the JavaScript assets are attached to the Square payment form.
 *
 * @param array $element
 *   The element.
 * @param string $mode
 *   The API mode.
 */
function _commerce_square_ensure_assets_attached(&$element, $mode) {
  if ($mode === 'test') {
    $element['#attached']['js']['https://js.squareupsandbox.com/v2/paymentform'] = array(
      'type' => 'external',
    );
  }
  else {
    $element['#attached']['js']['https://js.squareup.com/v2/paymentform'] = array(
      'type' => 'external',
    );
  }
  $element['#attached']['js'][] = drupal_get_path('module', 'commerce_square') . '/js/commerce_square.form.js';
}

/**
 * Access callback for transaction forms.
 *
 * @param object $order
 *   The order.
 * @param object $transaction
 *   The transaction.
 * @param string $type
 *   The operation type.
 *
 * @return bool
 *   The access.
 */
function commerce_square_payment_transaction_access($order, $transaction, $type) {
  if ($transaction->payment_method != 'commerce_square') {
    return FALSE;
  }
  if (!entity_access('update', 'commerce_payment_transaction', $transaction)) {
    return FALSE;
  }
  if ($transaction->amount <= 0) {
    return FALSE;
  }
  switch ($type) {
    case 'void':
      return $transaction->status == COMMERCE_PAYMENT_STATUS_PENDING;
    case 'capture':
      return $transaction->status == COMMERCE_PAYMENT_STATUS_PENDING;
    case 'refund':
      return $transaction->status == COMMERCE_PAYMENT_STATUS_SUCCESS;
    default:
      return FALSE;
  }
}

/**
 * Implements hook_cron().
 */
function commerce_square_cron() {
  commerce_square_renew_access_token();
}

Functions

Namesort descending Description
commerce_square_commerce_payment_method_info Implements hook_commerce_payment_method_info().
commerce_square_cron Implements hook_cron().
commerce_square_default_settings Returns the default settings for the Square module.
commerce_square_form_alter Implements hook_form_alter().
commerce_square_libraries_info Implements hook_libraries_info().
commerce_square_menu Implements hook_menu().
commerce_square_obtain_token Square OAuth flow menu callback to redirect back to the payment method form.
commerce_square_obtain_token_access Square OAuth flow menu access callback.
commerce_square_payment_method_default_settings Returns the default settings for the Square payment method.
commerce_square_payment_method_settings_form Payment method form callback.
commerce_square_payment_method_submit_form Square payment checkout pane form callback.
commerce_square_payment_method_submit_form_submit Square payment checkout pane submit callback.
commerce_square_payment_method_submit_form_validate Square payment checkout pane validate callback.
commerce_square_payment_transaction_access Access callback for transaction forms.
commerce_square_permission Implements hook_permission().
commerce_square_renew_access_token Renew access token.
_commerce_square_ensure_assets_attached Ensures the JavaScript assets are attached to the Square payment form.
_commerce_square_get_supported_modes Returns the payment method modes.