You are here

function commerce_square_payment_method_submit_form_validate in Commerce Square Connect 7

Square payment checkout pane validate callback.

File

./commerce_square.module, line 472
Module file for Commerce Square.

Code

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;
}