uc_gc_client-8.x-1.x-dev/uc_gc_client.module

uc_gc_client.module
<?php

/**
 * @file
 * Provides an integration with GoCardless.com for the Ubercart module.
 */

use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\uc_gc_client\Controller\GoCardlessPartner;
use Drupal\uc_order\Entity\Order;

require_once dirname(__FILE__) . '/uc_gc_client.admin.inc';

/**
 * Implements hook_help().
 */
function uc_gc_client_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.uc_gc_client':
      $url = 'https://seamless-cms.co.uk/ubercart-installation-8';
      $output = '<p>' . t('For more information, and installation instructions for the module, visit:') . '</p>';
      $output .= '<p>' . t('<a target="new" href = @url>@url</a>', [
        '@url' => $url,
      ]) . '</p>';
      $output .= '<p>' . t('Or check out the README.txt file that ships with the module.') . '</p>';
      return $output;
  }
}

/**
 * Implements hook_uc_add_to_cart_data().
 *
 * Adds extra information to a cart item's "data" array.
 */
function uc_gc_client_uc_add_to_cart_data($form_values) {
  $nid = $form_values['nid'];
  $extras = db_select('uc_gc_client_products', 'p')
    ->fields('p')
    ->condition('nid', $nid, '=')
    ->execute()
    ->fetchAssoc();

  if (!$extras['gc_use']) {
    return;
  }

  // Set the interval_params length and unit from the Interval attribute.
  if (isset($form_values['attributes'])) {
    $attributes = $form_values['attributes'];
    foreach ($form_values['node']->attributes as $att_value) {
      if ($att_value->name == 'Interval') {
        $option_id = $attributes[$att_value->aid];
        if (isset($option_id)) {
          $attr_option = $att_value->options[$option_id]->name;
        }
      }
    }
  }
  if (isset($attr_option)) {
    $interval_params = uc_gc_client_interval_params($attr_option);
  }
  elseif (isset($extras['interval_length'])) {
    $interval_params = [
      'length' => $extras['interval_length'],
      'unit' => $extras['interval_unit'],
      'string' => $extras['interval_length'] . ' ' . $extras['interval_unit'],
      'unit_gc' => $extras['interval_unit'] . 'ly',
    ];
  }

  // Add GC type.
  if (isset($extras['type'])) {
    $extras['type'] == 'S' ? $gc_type = 'subscription' : $gc_type = 'payment';
  }
  else {
    $gc_type = NULL;
  }

  $data = [
    'gc_auth_type' => $gc_type,
    'price_x' => $extras['price_x'],
    'interval_params' => isset($interval_params) ? $interval_params : NULL,
  ];
  return $data;
}

/**
 * Returns an array of interval params.
 *
 * @param string $option
 *   The interval type.
 *
 * @return array
 *   Length and unit interval parameters.
 */
function uc_gc_client_interval_params($option) {
  switch ($option) {
    case 'Yearly':
      $interval_params = [
        'length' => 1,
        'unit' => 'yearly',
        'string' => '1 year',
        'unit_gc' => 'yearly',
      ];
      break;

    case 'Monthly':
      $interval_params = [
        'length' => 1,
        'unit' => 'month',
        'string' => '1 month',
        'unit_gc' => 'monthly',
      ];
      break;

    case 'Weekly':
      $interval_params = [
        'length' => 1,
        'unit' => 'week',
        'string' => '1 week',
        'unit_gc' => 'weekly',
      ];
      break;

    case 'Fortnightly':
      $interval_params = [
        'length' => 2,
        'unit' => 'week',
        'string' => '2 week',
        'unit_gc' => 'weekly',
      ];
      break;
  }
  return $interval_params;
}

/**
 * Implements hook_form_FORM_ID_alter() for the Ubercart checkout form.
 */
function uc_gc_client_form_uc_cart_checkout_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $session = \Drupal::service('session');
  $order_id = intval($session->get('cart_order'));
  if (!isset($order_id) || $order_id == 0) {
    return;
  }
  $order = Order::load($order_id);
  $config_id = \Drupal::state()->get('uc_gc_client_payment_method_id');
  $payment_method_id = explode('.', $config_id)[2];

  if (is_null($order->getPaymentMethodID()) || $order->getPaymentMethodId() == $payment_method_id) {

    // Change the address fieldsets to a bespoke type that has countries
    // filtered to only include GC enabled countries.
    $settings = GoCardlessPartner::getSettings();
    if ($settings['currencies']) {
      if (isset($form['panes']['delivery'])) {
        $form['panes']['delivery']['address']['#type'] = 'uc_gc_client_address';
      }
      if (isset($form['panes']['billing'])) {
        $form['panes']['billing']['address']['#type'] = 'uc_gc_client_address';
      }
    }

    // Optional checkout review selected. (This should only be selected
    // if GC is the only enabled payment method.)
    if ($settings['checkout_review']) {
      $form['actions']['continue']['#value'] = $settings['checkout_label'];
      //$form['#submit'][] = 'uc_gc_client_checkout_form_submit';
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for the Ubercart Checkout Review form.
 *
 * Overrides checkout review form and provides a custom GoCardless submit
 * button.
 */
function uc_gc_client_form_uc_cart_checkout_review_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Optional checkout is not selected.
  $session = \Drupal::service('session');
  $order_id = intval($session->get('cart_order'));
  if (!isset($order_id) || $order_id == 0) {
    return;
  }
  $order = Order::load($order_id);
  $config_id = GoCardlessPartner::getSettings()['config_id'];
  if ($config_id != $order->getPaymentMethodId()) {
    return;
  }
  $form['actions']['submit']['#access'] = FALSE;
  $form['#prefix'] = '<table style="display: inline; padding-top: 1em;"><tr><td>';
  $form['gocardless_link'] = [
    '#type' => 'submit',
    '#value' => \Drupal::config('uc_payment.method.' . $config_id)->get('settings')['checkout_label'],
  ];
  $form['#suffix'] = '</td></tr></table>';
}

/**
 * Returns an array of mandate details.
 *
 * Mandate details are passed on to GoCardless Redirect Flows endpoint.
 *
 * @param object $order
 *   The Ubercart order object.
 *
 * @return array
 *   Mandate details array for GoCardless Redirect flow.
 */
function uc_gc_client_mandate_details($order) {
  // Depending on if Optional Checkout Review is selected.
  if (is_array($order)) {
    $order = $order['build_info']['args']['0'];
  }
  $fields = [
    'first_name',
    'last_name',
    'company',
    'street1',
    'street2',
    'city',
    'postal_code',
    'country',
  ];
  $billing = $order->getAddress('billing');
  $delivery = $order->getAddress('delivery');

  foreach ($fields as $field) {
    !is_null($billing->$field) ? $$field = $billing->$field : $$field = $delivery->$field;
  }

  $valid_countries = \Drupal::config('uc_gc_client.settings')->get('countries');
  if (!isset($valid_countries[$country])) {
    drupal_set_message(t('Sorry, this country is not avaialable to GoCardless right now.'), 'warning');
    drupal_goto($_SERVER['HTTP_REFERER']);
  }

  // Generate the mandate_details array.
  $mandate_details = [
    'name'              => 'Order ' . $order->id(),
    'user'              => [
      'first_name'        => $first_name,
      'last_name'         => $last_name,
      'email'             => $order->getEmail(),
      'company_name'      => $company,
      'billing_address1'  => $street1,
      'billing_address2'  => $street2,
      'billing_town'      => $city,
      'billing_postcode'  => $postal_code,
      'billing_country'  => $country,
    ],
  ];

  $settings = \Drupal::config('uc_gc_client.settings')->get();
  $sandbox = $settings['sandbox'];
  $sandbox ? $ext = '_sandbox' : $ext = '_live';

  $mandate_details['redirect_uri'] = $settings['partner_url'] . '/gc_partner/mandate?org_id=' . $settings['org_id' . $ext] . '&order_id=' . $order->id();

  // Provide a hook so that mandate details can be altered by another module.
  \Drupal::moduleHandler()->alter('gc_client_mandate_details', $mandate_details, $order);
  return $mandate_details;
}

/**
 * Implements hook_menu_local_tasks_alter().
 *
 * Removes the Payments tab from an order's View pane if the module is
 * configured to do this on the settings form.
 */
function uc_gc_client_menu_local_tasks_alter(&$data, $route_name) {
  if (isset($data['tabs'][0]['uc_gc_client.payments_form'])) {
    $settings = GoCardlessPartner::getSettings();
    if (isset($settings['payments_tab']) && $settings['payments_tab']) {
      $current_route = \Drupal::routeMatch();
      $order = $current_route->getParameters()->get('uc_order');
      if ($order && $order->getPaymentMethodId() == $settings['config_id']) {
        if (isset($data['tabs'][0]['uc_payments.order_payments'])) {
          unset($data['tabs'][0]['uc_payments.order_payments']);
        }
      }
    }
  }
}

/**
 * Implements hook_date_format_types().
 */
function uc_gc_client_date_format_types() {
  return [
    'gocardless' => t('GoCardless'),
  ];
}

/**
 * Implements hook_date_formats().
 */
function uc_gc_client_date_formats() {
  return [
    [
      'type' => 'gocardless',
      'format' => 'Y-m-d',
    ],
  ];
}

/**
 * Implements hook_uc_checkout_complete().
 *
 * For anonymous checkouts update the uid from 0 in the uc_gcsubs table.
 */
function uc_gc_client_uc_checkout_complete($order, $account) {
  if (isset($order->data['new_user'])) {
    db_update('uc_gc_client')
      ->fields([
        'uid' => $order->uid,
      ])
      ->condition('ucid', $order->order_id, '=')
      ->execute();
  }
}

/**
 * Implements hook_cron().
 *
 * Loops through GoCardless items and if the next_payment date is in the
 * past attempts to create a payment, and if successful updates the
 * next_payment date based on the product's recurrence rules.
 */
function uc_gc_client_cron() {
  // Postpone the operation if there is already an instance of
  // commerce_gc_client_cron running, to mitigate against the possibility of
  // extra payments being created in error. This can happen if cron takes more
  // than 240s to complete, and it is called again whilst still executing.
  if (!\Drupal::lock()->acquire('uc_gc_client_cron', 3600)) {
    \Drupal::logger('uc_gc_client')->warning('GoCardless payments could not be created becasue there is already an instance of cron running', []);
    return;
  }

  // Get list of active orders where next_payment is in the past.
  $payments = db_select('uc_gc_client', 'g')
    ->fields('g')
    ->condition('type', ['S', 'P'], 'IN')
    ->condition('status', 'canceled', '!=')
    ->condition('next_payment', REQUEST_TIME, '<=')
    ->execute()->fetchAll();

  if (!empty($payments)) {
    $partner = new GoCardlessPartner();
    $uuid_service = \Drupal::service('uuid');
  }

  foreach ($payments as $payment) {
    $result = $partner->api([
      'endpoint' => 'mandates',
      'action' => 'get',
      'mandate' => $payment->gcid,
    ]);
    if ($result->response->status_code == 200) {
      $mandate = $result->response->body->mandates;
    }

    if (!isset($mandate) || in_array($mandate->status, ['cancelled', 'failed'])) {
      $log = t('Order #@ucid: Payment creation error.', ['@ucid' => $payment->ucid]);
      \Drupal::logger('uc_gc_client')->error($log, []);
      continue;
    }

    $order = Order::load($payment->ucid);
    if (!$order instanceof EntityInterface) {
      continue;
    }
    $product = $order->products[$payment->ucpid];
    $data = $product->data->getValue()[0];

    // Retry a Subscription creation if there was a failure at the GoCardless
    // end during checkout.
    if ($payment->type == 'S') {
      $update_next_payment = TRUE;
      if (isset($data['subscription_details'])) {
        $result = $partner->api($data['subscription_details']);

        // New subscription created successfully, or had already been created.
        if (isset($result->response) && in_array($result->response->status_code, [200, 201])) {
          unset($data['subscription_details']);
          $product->set('data', $data)->save();
        }
        else {
          // If no response or response code 500 (Internal Server Error) then
          // do nothing and the Subscription creation will be retried on the
          // next cron run. But if there is a reponse then the error is at this
          // end so abort Subscription creation attempt.
          $watchdog_array = ['@order_id' => $payment->ucid];
          if (isset($result->response) && $result->response && $result->response->status_code != 500) {
            $watchdog = t('A scheduled subscription creation could not be created for order #@order_id.', $watchdog_array);
            watchdog('uc_gc_client', $watchdog, NULL, WATCHDOG_ERROR);
          }
          else {
            $update_next_payment = FALSE;
            $watchdog = t('There was a problem with a scheduled subscription creation for order #@order_id, and it will be retried on the next cron run.', $watchdog_array);
            watchdog('uc_gc_client', $watchdog, NULL, WATCHDOG_WARNING);
          }
        }
      }
      if ($update_next_payment) {
        db_update('uc_gc_client')->fields([
          'next_payment' => NULL,
          'next_payment_uuid' => NULL,
        ])->condition('ucpid', $payment->ucpid)->execute();
      }
    }

    // One-off payments type.
    else {
      // Calculate amount.
      $next_paymentdate = date('D d M Y', $payment->next_payment);
      $calculate = uc_gc_client_price_calculate($order, $payment->ucpid, $payment);
      $amount = $calculate['amount'];
      $store_config = \Drupal::config('uc_store.settings');
      $currency_code = isset($calculate['currency']) ? $calculate['currency'] : $store_config->get('currency.code');
      $currency_sign = isset($calculate['sign']) ? $calculate['sign'] : $store_config->get('currency.symbol');
      $uid = $order->getOwnerId();
      $settings = GoCardlessPartner::getSettings();

      // Make sure daily payment limit hasn't been exceeded.
      if (!empty($settings['payment_limit']) && $settings['payment_limit'] != 0) {
        $count = 0;
        $result = $partner->api([
          'endpoint' => 'payments',
          'action' => 'list',
          'mandate' => $payment->gcid,
          'created_at_on_after' => date('c', strtotime('midnight')),
        ]);
        if ($result->response->status_code == 200) {
          foreach ($result->response->body->payments as $pay) {
            if (isset($pay->metadata->ucpid)) {
              if ($pay->metadata->ucpid == $payment->ucpid) {
                $count++;
              }
            }
          }
        }

        // If dayly payment limit has been exceeded.
        if ($count >= $settings['payment_limit']) {
          $message = t("Payment for @amount has not been raised because the daily payment limit of @payment_limit has been exceeded for the order.", [
            '@amount' => uc_currency_format($amount, $currency_sign),
            '@payment_limit' => $settings['payment_limit'],
          ]);
          uc_order_comment_save($order->id(), $uid, $message, 'order');

          $watchdog_array = [
            '@amount' => uc_currency_format($amount, $currency_sign),
            '@order_id' => $order->id(),
            '@uid' => $uid,
            '@payment_limit' => $settings['payment_limit'],
          ];
          $watchdog = t("Payment for @amount for order #@order_id has not been created on user #@uid's account because the daily payment limit of @payment_limit has been exceeded.", $watchdog_array);
          \Drupal::logger('uc_gc_client')->warning($watchdog, []);

          // Send a warning email to admin.
          $mail_recipient = $settings['warnings_email'];
          $mail_params = [
            'order_id' => $order->id(),
            'payment_limit' => $settings['payment_limit'],
            'amount' => uc_currency_format($amount, $currency_sign),
          ];
          $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
          \Drupal::service('plugin.manager.mail')
            ->mail('uc_gc_client', 'payment-limit-reached', $mail_recipient, $langcode, $mail_params);

          continue;
        }
      }

      $payment_details = [
        'amount' => $amount,
        'name' => $calculate['name'],
      ];

      // Provide a hook so that payment details can be altered by another module.
      \Drupal::moduleHandler()->alter('gc_client_scheduled_payment_details', $payment_details, $order);
      $amount = $payment_details['amount'];
      $name = $payment_details['name'];

      // Validate the payment amount before creating.
      if ($amount < 1 && $amount != 0) {
        $message = t("Payment for @amount has not been created because it is less than @minimum.", [
          '@minimum' => uc_currency_format(1, $currency_sign),
          '@amount' => uc_currency_format($amount, $currency_sign),
        ]);
        uc_order_comment_save($order->id(), $uid, $message, 'order');

        $watchdog = t("Payment for @amount for order #@order_id has not been created on user #@uid's account because it is less than @minimum.", [
          '@minimum' => uc_currency_format(1, $currency_sign),
          '@amount' => uc_currency_format($amount, $currency_sign),
          '@order_id' => $order->id(),
          '@uid' => $uid,
        ]);
        \Drupal::logger('uc_gc_client')->warning($watchdog, []);

        // Send a warning email to admin.
        $mail_recipient = $settings['warnings_email'];
        $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
        $mail_params = ['order_id' => $order->id()];
        \Drupal::service('plugin.manager.mail')
          ->mail('uc_gc_client', 'payment-less-than-one', $mail_recipient, $langcode, $mail_params);
      }

      elseif ($amount == 0) {
        $message = t("No payment raised because scheduled amount is @sign0.", ['@sign' => $currency_sign]);
        uc_order_comment_save($order->id(), $uid, $message, 'order');

        $watchdog = t("No payment raised for order #@order_id on user #@uid's account because the amount is @sign0.", [
          '@order_id' => $order->id(),
          '@uid' => $uid,
          '@sign' => $currency_sign,
        ]);
        \Drupal::logger('uc_gc_client')->warning($watchdog, []);
      }

      else {
        // Create the payment.
        if (!$uuid = $payment->next_payment_uuid) {
          $uuid = $uuid_service->generate();
        }
        $result = $partner->api([
          'endpoint' => 'payments',
          'action' => 'create',
          'mandate' => $payment->gcid,
          'amount' => $amount,
          'currency' => $currency_code,
          'description' => $name,
          'metadata' => [
            'ucpid' => $payment->ucpid,
          ],
          'idempotency_key' => $uuid,
        ]);

        if (isset($result->response) && in_array($result->response->status_code, [200, 201])) {
          $payment_created = $result->response->body->payments;
          $comment = t('Payment for @amount has been created with GoCardless and will be made from your account on @charge_date.', [
            '@amount' => uc_currency_format($amount, $currency_sign),
            '@charge_date' => format_date(strtotime($payment_created->charge_date), 'uc_store'),
          ]);
          uc_order_comment_save($order->id(), $uid, $comment, 'order', 'pending', TRUE);
          $watchdog = "Payment for @amount for order #@order_id has been created with GoCardless and will be taken from user #@uid's account on @charge_date.";
          \Drupal::logger('uc_gc_client')->info($watchdog, [
            '@amount' => uc_currency_format($amount, $currency_sign),
            '@order_id' => $order->id(),
            '@uid' => $uid,
            '@charge_date' => format_date(strtotime($payment_created->charge_date), 'uc_store'),
          ]);
        }
        else {
          // If no response or response code 500 (Internal Server Error) then
          // add the uuid to the current record in uc_gc_client, and continue 
          // to next item. This is so the item will be retried, and the uuid can
          // be used again as the same idempotency key, to ensure a duplicate 
          // payment isn't created.    
          if (!isset($result->response) || !$result->response || $result->response->status_code == 500) {
            db_update('uc_gc_client')->fields([
              'next_payment_uuid' => $uuid,
            ])
            ->condition('ucpid', $payment->ucpid)
            ->execute();
          }

          $log = t('There was a problem creating a scheduled payment for order #@order_id.', [
            '@order_id' => $order->id(),
          ]);
          \Drupal::logger('commerce_gc_client')->error($log, []);
          continue;
        }
      }

      // Update next_payment field in uc_gc_client table.
      // TODO should next scheduled payment be adjustded if payment fails?
      // Configuration setting perhaps?
      if (isset($data['interval_params']['string'])) {
        $string = '+' . $data['interval_params']['string'];
        $next_payment = strtotime($string, $payment->next_payment);
      }
      else {
        // If the interval params are not set then set next payment to NULL
        // to prevent repeated payment creations for same day.
        $next_payment = NULL;
      }

      // Provide a hook so that next_payment can be altered by another module.
      \Drupal::moduleHandler()->alter('gc_client_next_scheduled_payment_date', $next_payment, $order);

      db_update('uc_gc_client')
        ->fields([
          'next_payment' => $next_payment,
          'next_payment_uuid' => NULL,
          'updated' => REQUEST_TIME,
        ])
        ->condition('ucpid', $payment->ucpid)
        ->execute();

      // Update status field in uc_gc_client_schedules table.
      db_update('uc_gc_client_schedules')
        ->fields([
          'status' => 2,
        ])
        ->condition('type', 'adjustment')
        ->condition('date', $next_paymentdate)
        ->condition('ucpid', $payment->ucpid)
        ->execute();
    }
  }
  \Drupal::lock()->release('uc_gc_client_cron');
}

/**
 * Calculates an amount in preparation for creating a payment.
 *
 * @param object $order
 *   Ubercart order entity.
 * @param int $ucpid
 *   Ubercart order product Id.
 * @param object $payment
 *   Set if it is a scheduled payment and the function is called from cron.
 *   Not set if it is called on return to site during checkout.
 *
 * @return array
 *   An array of values relating to a payment, including the amount to be
 *   created and the currency code.
 */
function uc_gc_client_price_calculate($order, $ucpid, $payment = NULL) {
  $adjs_arr = [];
  $adjs_total = 0;
  if ($payment) {
    // Process scheduled adjustments.
    $query = db_select('uc_gc_client_schedules', 's');
    $adjustments = $query
      ->fields('s', ['sid', 'date', 'status', 'data'])
      ->condition('s.status', 1)
      ->condition('s.date', date('d M Y', $payment->next_payment))
      ->condition('type', 'adjustment')
      ->condition('ucpid', $ucpid)
      ->orderBy('timestamp', 'ASC')
      ->execute()->fetchAll();

    foreach ($adjustments as $adj) {
      // Todo Ensure this will not break if two adjustments on same date have
      // same title.
      $adj_data = unserialize($adj->data);
      $adjs_arr[] = [$adj_data['title'] => $adj_data['amount']];
      $adjs_total = $adjs_total + $adj_data['amount'];
    }
  }

  // Factor in price multiplier.
  $product = $order->products[$ucpid];
  $product_data = $product->data->getValue()[0];

  if (isset($product_data['price_x'])) {
    !is_null($product_data['price_x']) ? $price_x = $product_data['price_x'] : $price_x = 1;
  }
  else {
    $price_x = 1;
  }

  // Todo add extra rules here for handling line items.
  $line_items_amount = 0;
  $line_items = $order->getLineItems();
  foreach ($line_items as $item) {
    if ($item['type'] == 'subtotal') {
      continue;
    }
    // $line_items_amount = $line_items_amount + $item['amount'];
  }

  $product_amount = $product->price->value * $product->qty->value;
  $amount = ($product_amount + $adjs_total + $line_items_amount) * $price_x;

  // Get the currency code and symbol.
  if ($payment) {
    $scheme = $payment->scheme;
  }
  else {
    $scheme = db_select('uc_gc_client', 'g')
      ->fields('g', ['scheme'])
      ->condition('ucid', $order->id())
      ->execute()->fetchField();
  }
  if ($scheme) {
    foreach (\Drupal::config('uc_gc_client.settings')->get('countries') as $country) {
      if ($country['region'] == $scheme) {
        $gc_currency_code = $country['currency'];
        $gc_currency_sign = $country['sign'];
        break;
      }
    }
  }
  else {
    $default_currency = \Drupal::config('uc_store.settings')->get('currency');
    $gc_currency_code = $default_currency['code'];
    $gc_currency_sign = $default_currency['symbol'];
  }

  // Modify payment amount and currency if international customer.
  $settings = GoCardlessPartner::getSettings();
  if ($settings['currencies'] && $fixer = $settings['fixer']) {
    $product_currency_code = $order->getCurrency();
    if ($product_currency_code != $gc_currency_code) {

      // Use fixer.io to provide latest exchange rates for currency conversion.
      $uri = 'http://data.fixer.io/api/latest?access_key=' . $fixer;
      $result = \Drupal::httpClient()->get($uri, [
        'headers' => ['Content-Type' => 'application/json'],
      ]);
      $data = json_decode($result->getBody());

      if (!$data->success) {
        $log = t('Unable to obtain currency exchange rate from fixer.io: @error_type', [
          '@error' => $data->error->type,
        ]);
        \Drupal::logger('commerce_gc_client')->warning($log, []);
        if (!$payment) {
          drupal_set_message(t('We were unable to obtain a currency exchange rate for your order, please contact the site administrator.'), 'warning');
        }
      }
      else {
        $exchange_rate = $data->rates->$product_currency_code;
        if ($product_currency_code == 'EUR') {
          $amount = $amount * $exchange_rate;
        }
        elseif ($gc_currency_code == 'EUR') {
          $amount = $amount / $exchange_rate;
        }
        else {
          $exchange_rate_2 = $data->rates->$gc_currency_code;
          $amount = ($amount / $exchange_rate) * $exchange_rate_2;
        }

        if (!$payment) {
          drupal_set_message(t('The amount for your purchase has been converted into @symbol using current exchange rates at fixer.io', [
            '@symbol' => $gc_currency_sign,
          ]));
        }
      }
    }
  }

  $calc = [
    'name' => 'Payment for Order #' . $order->id(),
    'price' => $product->price->value,
    'adjs' => $adjs_arr,
    'adjs_total' => $adjs_total,
    'amount' => number_format(html::escape($amount, "number"), 2, '.', ''),
    'currency' => $gc_currency_code,
    'exchange_rate' => isset($exchange_rate) ? $exchange_rate : NULL,
    'sign' => $gc_currency_sign,
    'price_x' => $price_x,
  ];
  return $calc;
}

/**
 * Implements hook_mail().
 */
function uc_gc_client_mail($key, &$message, $params) {
  global $base_url;
  $order_url = $base_url . '/admin/store/orders/' . $params['order_id'];
  $link = t('order <a href="@url">#@order_id</a>', [
    '@order_id' => $params['order_id'],
    '@url' => $order_url,
  ]);

  $message_arr = [
    '@link' => $link,
    '@order_url' => $order_url,
    '@order_id' => $params['order_id'],
    '@payment_limit' => isset($params['payment_limit']) ? $params['payment_limit'] : NULL,
    '@amount' => isset($params['amount']) ? $params['amount'] : NULL,
    '@sign' => isset($params['sign']) ? $params['sign'] : NULL,
  ];

  $message['headers']['Content-Type'] = 'text/html; charset=UTF-8; format=flowed';

  switch ($key) {

    case 'payment-less-than-one':
      $message['subject'] = "A payment's Amount was less than @sign1 !";
      $message['body'][] = t("<p>A payment for order <a href='@order_url'>#@order_id</a> has failed because it's amount was less than @sign1.</p>", $message_arr);
      break;

    case 'payment-limit-reached':
      $message['subject'] = "The daily payment limit has been exceeded for an order!";
      $message['body'][] = t("<p>A payment of @amount for order <a href='@order_url'>#@order_id</a> could not be raised because the daily automatic limit of @payment_limit has been exceeded.</p>", $message_arr);
      break;
  }
}

/**
 * Implements hook_ENTITY_TYPE_update() for uc_order entities.
 */
function uc_gc_client_uc_order_update(EntityInterface $order) {
  if ($order->order_status->getString() == 'canceled') {
    uc_gc_client_uc_order_cancel($order);
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete() for uc_order entities.
 *
 * Removes records from the database upon deletion of an order.
 */
function uc_gc_client_uc_order_delete($order) {
  // Cancel GoCardless mandate first if order status is not canceled.
  if ($order->order_status->getString() != 'canceled') {
    uc_gc_client_uc_order_cancel($order);
  }
  db_delete('uc_gc_client')
    ->condition('ucid', $order->id())
    ->execute();

  db_delete('uc_gc_client_schedules')
    ->condition('ucid', $order->id())
    ->execute();
}

/**
 * Cancels a GC mandate upon cancelling an order.
 *
 * @param object $order
 *   The Ubercart order entity that is being cancelled.
 */
function uc_gc_client_uc_order_cancel($order) {
  $comment = t('Order @order_id cancelled by customer.', ['@order_id' => $order->id()]);
  uc_order_comment_save($order->id(), $order->getOwnerId(), $comment, 'order', 'canceled', TRUE);
  uc_gc_client_mandate_cancel($order);
}

/**
 * Cancels a GoCardless mandate.
 *
 * @param object $order
 *   The Ubercart order entity relating to the GoCardledd mandate that is
 *   being cancelled.
 */
function uc_gc_client_mandate_cancel($order) {
  $gc_client = db_select('uc_gc_client', 'c')
    ->fields('c')
    ->condition('ucid', $order->id())
    ->condition('status', 'canceled', '!=')
    ->execute()->fetch();

  if (empty($gc_client)) {
    return;
  }
  $partner = new GoCardlessPartner();
  $result = $partner->api([
    'endpoint' => 'mandates',
    'action' => 'cancel',
    'mandate' => $gc_client->gcid,
  ]);

  if ($result->response->status_code == 200) {
    // Update status on database for all products in order.
    db_update('uc_gc_client')
      ->fields([
        'status' => 'canceled',
        'updated' => REQUEST_TIME,
      ])
      ->condition('ucid', $order->id())
      ->execute();

    drupal_set_message(t('GoCardless direct debit mandate @ucid has been cancelled, and you will receive an email confirmation.', ['@ucid' => $gc_client->gcid]));
  }
  else {
    drupal_set_message(t('Something went wrong cancelling GoCardless direct debit mandate @ucid. Please try again or contact the site administrator for assistance.', ['@ucid' => $gc_client->gcid]), 'error');
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete() for uc_payment_method entities.
 */
function uc_gc_client_uc_payment_method_delete(EntityInterface $entity) {
  if ($entity->getPlugin()->getPluginId() == 'gc_client') {
    \Drupal::state()->delete('uc_gc_client_payment_method_id');
  }
}

/**
 * Returns a date calculated as working days from the date provided.
 *
 * @param string $op
 *   Either "+" or "-".
 * @param int $days
 *   The number of working days to calculate from the provided date.
 * @param string $date
 *   The date to calculate from, as a string.
 *
 * @Todo
 * This function will malfunction if there is a bank holiday(s) within the
 * specified range of days
 *
 * @return string
 *   The calculated date.
 */
function uc_gc_client_working_days_calculate($op, $days, $date) {
  $x = 1;
  $string = $op . '1 day';
  while ($x <= $days) {
    $date = date('Y-m-d', strtotime($string, strtotime($date)));
    if (!in_array(date('D', strtotime($date)), ['Sat', 'Sun'])) {
      $x++;
    }
  }
  return $date;
}

/**
 * Returns currency information.
 *
 * @param string $country_code
 *   The country either as an ISO Numeric code or a ISO ALPHA-2 code.
 *
 * @return array
 *   Array including currency code and symbol.
 */
function uc_gc_client_currency($country_code) {
  $countries = \Drupal::config('uc_gc_client.settings')->get('countries');
  if (is_numeric($country_code)) {
    $country_code = db_select('uc_countries', 'c')
      ->fields('c', ['country_iso_code_2'])
      ->condition('country_id', $country_code)
      ->execute()->fetchField();
  }
  if (isset($countries[$country_code])) {
    return [
      'currency' => $countries[$country_code]['currency'],
      'sign' => $countries[$country_code]['sign'],
    ];
  }
}

/**
 * Formats an amount for display with the required currency sign.
 *
 * @param float $value
 *   The value of the currency to format.
 * @param string $country_code
 *   The country either as an ISO Numeric code or a ISO ALPHA-2 code.
 * @param null|false $thou
 *   The thousands seperator character.
 * @param null|false $dec
 *   The decimal seperator character.
 *
 * @see uc_curency_format()
 *
 * @return string
 *   String containing price formatted with currency symbol and separators.
 */
function uc_gc_client_currency_format($value, $country_code, $thou = NULL, $dec = NULL) {
  $sign = uc_gc_client_currency($country_code)['sign'];
  return uc_currency_format($value, $sign, $thou = NULL, $dec = NULL);
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc