uc_gc_client-8.x-1.x-dev/src/Controller/GoCardless.php
src/Controller/GoCardless.php
<?php
namespace Drupal\uc_gc_client\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\uc_gc_client\Plugin\Ubercart\PaymentMethod\GoCardlessClient;
use Drupal\uc_order\Entity\Order;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Component\Uuid\Php;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\uc_payment\Plugin\PaymentMethodManager;
use Drupal\Core\Extension\ModuleHandler;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\user\PrivateTempStoreFactory;
/**
* Returns responses for GoCardless routes.
*/
class GoCardless extends ControllerBase {
/**
* The date formatter.
*
* @var \Drupal\Core\Datetime\DateFormatterInterface
*/
protected $dateFormatter;
/**
* The uc_store configuration settings.
*
* @var array
*/
protected $ucStoreSettings;
/**
* The Ubercart payment method manager.
*
* @var \Drupal\uc_payment\Plugin\PaymentMethodManager
*/
protected $ucPaymentMethod;
/**
* The module handler.
*
* @var \Drupal\core\extension\modulehandler
*/
protected $moduleHandler;
/**
* The Private Tempstore Factory.
*
* @var \Drupal\user\PrivateTempStoreFactory
*/
protected $tempStore;
/**
* The Uuid service.
*
* @var \Drupal\Component\Uuid\Php;
*/
protected $uuidService;
/**
* Constructs the GoCardless object.
*
* @param \Drupal\Core\Datetime\DateFormatterInterface $dateFormatter
* The date formatter.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The Config Factory interface.
* @param \Drupal\uc_payment\Plugin\PaymentMethodManager $ucPaymentMethod
* The Ubercart payment method manager.
* @param \Drupal\Core\Extension\ModuleHandler $moduleHandler
* The module handler.
* @param \Drupal\user\PrivateTempStoreFactory $tempStoreFactory
* The Private Tempstore Factory.
* @param \Drupal\Component\Uuid\Pho $uuidService
* The Uuid PHP service.
*/
public function __construct(DateFormatterInterface $dateFormatter, ConfigFactoryInterface $configFactory, PaymentMethodManager $ucPaymentMethod, ModuleHandler $moduleHandler, PrivateTempStoreFactory $tempStoreFactory, Php $uuidService) {
$this->dateFormatter = $dateFormatter;
$this->ucStoreSettings = $configFactory->get('uc_store.settings')->get();
$this->ucPaymentMethod = $ucPaymentMethod;
$this->moduleHandler = $moduleHandler;
$this->tempStore = $tempStoreFactory->get('uc_gc_client');
$this->uuidService = $uuidService;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('date.formatter'),
$container->get('config.factory'),
$container->get('plugin.manager.uc_payment.method'),
$container->get('module_handler'),
$container->get('user.private_tempstore'),
$container->get('uuid')
);
}
/**
* Handles a complete GoCardless order.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect to the cart or checkout complete page.
*/
public function goCardlessComplete($redirect, $mandate_id, $customer, $order_id, $start_date = NULL, $cart_id = 0) {
// Obtain mandate object from GoCardless to check it is valid, and to
// get the currency scheme.
$partner = new GoCardlessPartner();
$result = $partner->api([
'endpoint' => 'mandates',
'action' => 'get',
'mandate' => $mandate_id,
]);
if ($result->response->status_code != 200) {
return $this->redirect('uc_cart.cart');
}
else {
$mandate = $result->response->body->mandates;
}
// Ensure the payment method is GoCardless.
$order = Order::load($order_id);
$method = $this->ucPaymentMethod->createFromOrder($order);
if (!$method instanceof GoCardlessClient) {
return $this->redirect('uc_cart.cart');
}
$uid = $order->getOwnerId();
// Let customer know mandate has been created.
$comment = $this->t('Your new direct debit mandate @mandate has been created by GoCardless.', [
'@mandate' => $mandate_id,
]);
uc_order_comment_save($order_id, $uid, $comment, 'order', 'pending', TRUE);
drupal_set_message($comment);
// Set up seperate payments / subscriptions for each product in cart.
foreach ($order->products as $product) {
$uuid = $this->uuidService->generate();
$ucpid = $product->order_product_id->value;
$data = $product->data->getValue()[0];
//If the payment type isn't set then set it to 'P' by default.
$gc_type = isset($data['gc_auth_type']) && $data['gc_auth_type'] == 'subscription' ? 'S' : 'P';
$product_uc = db_select('uc_gc_client_products', 'p')
->fields('p')
->condition('nid', $product->nid->getString())
->execute()->fetch();
// Obtain initial payment creation date.
// Use start date.
if ($product_uc->start_date) {
if (strtotime($product_uc->start_date) < REQUEST_TIME) {
$start_date = REQUEST_TIME;
}
else {
$start_date = strtotime($product_uc->start_date);
}
}
// Use day(s) of month.
elseif ($product_uc->dom) {
$doms = explode(',', $product_uc->dom);
$dates = [];
foreach ($doms as $dom) {
$dom >= date('d') ? $month = 'last' : $month = 'this';
$time = strtotime('+' . $dom . ' days', strtotime('last day of ' . $month . ' month'));
$dates[] = $time;
}
sort($dates);
$start_date = array_shift($dates);
}
// Create payment immidiately.
elseif ($product_uc->create_payment) {
$start_date = REQUEST_TIME;
}
// Provide hook so initial date can be provided by another module.
elseif (is_null($start_date)) {
$this->moduleHandler->alter('gc_client_start_date', $start_date, $product);
}
// Insert info about the order into the database.
$created = REQUEST_TIME;
$insert_fields = [
'ucid' => $order_id,
'ucpid' => $ucpid,
'gcid' => $mandate_id,
'gcrid' => $redirect,
'gccid' => $customer,
'scheme' => $mandate->scheme,
'uid' => $uid,
'type' => $gc_type,
'status' => 'pending_submission',
'created' => $created,
'start_date' => $start_date,
'updated' => $created,
'sandbox' => \Drupal::config('uc_gc_client.settings')->get('sandbox'),
];
$update_fields = [
'updated' => $created,
];
db_merge('uc_gc_client')
->key(['ucpid' => $ucpid])
->insertFields($insert_fields)
->updateFields($update_fields)
->execute();
isset($data['interval_params']) ? $interval = $data['interval_params'] : NULL;
$currency_code = $this->ucStoreSettings['currency']['code'];
$currency_sign = $this->ucStoreSettings['currency']['symbol'];
// Subscription payments.
if ($gc_type == 'S') {
$calculate = uc_gc_client_price_calculate($order, $ucpid);
if (isset($calculate['currency'])) {
$currency_code = $calculate['currency'];
}
if (isset($calculate['sign'])) {
$currency_sign = $calculate['sign'];
}
$payment_details = [
'type' => 'subscription',
'amount' => $calculate['amount'],
'currency' => $currency_code,
'name' => 'Subscription plan for ' . $product->title->value,
'interval' => $interval['length'],
'interval_unit' => $interval['unit_gc'],
'metadata' => [
'ucpid' => $ucpid,
],
];
// Provide hook so payment details can be altered by another module
// before the payment is created with GC.
$this->moduleHandler->alter('gc_client_subs_payment', $payment_details, $order);
$params = [
'endpoint' => 'subscriptions',
'action' => 'create',
'mandate' => $mandate_id,
'amount' => $payment_details['amount'],
'currency' => $payment_details['currency'],
'name' => $payment_details['name'],
'interval' => $payment_details['interval'],
'interval_unit' => $payment_details['interval_unit'],
'metadata' => $payment_details['metadata'],
'idempotency_key' => $uuid,
];
if (!isset($product_uc->create_payment)) {
$params['start_date'] = $this->dateFormatter->format($start_date, 'gocardless');
}
$result = $partner->api($params);
if ($result->response->status_code == 201) {
$sub = $result->response->body->subscriptions;
$comment_arr = [
'@product' => $product->title->value,
'@interval' => $sub->interval,
'@interval_unit' => $sub->interval_unit,
'@amount' => uc_currency_format($sub->amount / 100, $currency_sign),
'@start_date' => $this->dateFormatter->format(strtotime($sub->start_date), 'uc_store'),
];
$comment = $this->t('Your @interval @interval_unit subscription plan of @amount for @product has been created with GoCardless, and the first payment will be made from your bank on @start_date.', $comment_arr);
uc_order_comment_save($order_id, $uid, $comment, 'order', 'pending', TRUE);
drupal_set_message($comment);
}
else {
if (!isset($result->response) || !$result->response || $result->response->status_code == 500) {
drupal_set_message($this->t('There was a problem creating your subscription at GoCardless, so we will resubmit it later.'), 'warning');
db_update('uc_gc_client')->fields([
'next_payment' => $created,
'next_payment_uuid' => $uuid,
])->condition('ucpid', $ucpid)->execute();
$data['subscription_details'] = $params;
$product->set('data', $data)->save();
}
else {
drupal_set_message($this->t('Something went wrong creating your subscription with GoCardless. Please contact the site administrator for assistance.'), 'error');
}
}
}
// One-off payments.
elseif ($gc_type == 'P') {
// Create the first payment immediately.
if (!is_null($start_date) && $start_date <= REQUEST_TIME) {
$calculate = uc_gc_client_price_calculate($order, $ucpid);
if (isset($calculate['currency'])) {
$currency_code = $calculate['currency'];
}
if (isset($calculate['sign'])) {
$currency_sign = $calculate['sign'];
}
$order->save();
$payment_details = [
'type' => 'one-off payment',
'amount' => $calculate['amount'],
'currency' => $currency_code,
'description' => 'Initial payment for ' . $product->title->value,
'metadata' => [
'ucpid' => $ucpid,
],
];
// Provide hook so payment details can be altered by another module
// before the payment is created with GC.
$this->moduleHandler->alter('gc_client_payments_payment', $payment_details, $order);
$result = $partner->api([
'endpoint' => 'payments',
'action' => 'create',
'mandate' => $mandate_id,
'amount' => $payment_details['amount'],
'currency' => $payment_details['currency'],
'description' => $payment_details['description'],
'metadata' => $payment_details['metadata'],
'idempotency_key' => $uuid,
]);
if ($result->response->status_code == 201) {
// Update next_payment field in gc_client table.
$payment = $result->response->body->payments;
isset($interval) ? $next_payment = strtotime('+' . $interval['string']) : $next_payment = NULL;
db_update('uc_gc_client')
->fields([
'next_payment' => $next_payment,
])
->condition('ucpid', $ucpid)
->execute();
// Let everyone know what is going on.
$comment_array = [
'@amount' => uc_currency_format($payment->amount / 100, $currency_sign),
'@charge_date' => date('D d M Y', strtotime($result->response->body->payments->charge_date)),
'@product' => $product->title->value,
];
$comment = $this->t('An initial payment of @amount for @product has been created with GoCardless, and will be made from your bank on @charge_date.', $comment_array);
uc_order_comment_save($order_id, $uid, $comment, 'order', 'pending', TRUE);
drupal_set_message($comment);
}
else {
drupal_set_message($this->t('An initial payment could not be created due to an unknown error. We will try and raise it again later.'), 'warning');
// Update next_payment to created time so that it will get picked
// up on next cron run.
$update = [
'next_payment' => $created,
];
if (!isset($result->response) || !$result->response || $result->response->status_code == 500) {
// Provide the next payment with the Uuid which will be re-
// submitted in the cron run to ensure a duplicate payment isn't
// created.
$update['next_payment_uuid'] = $uuid;
}
db_update('uc_gc_client')
->fields($update)->condition('ucpid', $ucpid)->execute();
}
}
// Else if a start date is set for the product then defer the
// first payment creation.
elseif (!is_null($start_date)) {
// Update next_payment field in uc_gcsubs table.
db_update('uc_gc_client')
->fields([
'next_payment' => $start_date,
'updated' => REQUEST_TIME,
])
->condition('ucpid', $ucpid)
->execute();
$calculate = uc_gc_client_price_calculate($order, $ucpid);
if (isset($calculate['currency'])) {
$currency_code = $calculate['currency'];
}
if (isset($calculate['sign'])) {
$currency_sign = $calculate['sign'];
}
$order->save();
// Let everyone know what is going on.
$comment = $this->t('A payment for @amount will be created with GoCardless on @start_date.', [
'@amount' => uc_currency_format($calculate['amount'], $currency_sign),
'@start_date' => $this->dateFormatter->format($start_date, 'uc_store'),
]);
uc_order_comment_save($order_id, $uid, $comment, 'order', 'pending', TRUE);
drupal_set_message($comment);
}
}
}
// This lets us know it's a legitimate access of the complete page.
// $this->tempStore->set('uc_checkout_complete_' . $order_id, TRUE);.
$session = \Drupal::service('session');
$session->remove('uc_checkout_review_' . $order->id());
$session->set('uc_checkout_complete_' . $order_id, TRUE);
return $this->redirect('uc_cart.checkout_complete');
}
}
