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