uc_gc_client-8.x-1.x-dev/src/Controller/WebhookHandler.php

src/Controller/WebhookHandler.php
<?php

namespace Drupal\uc_gc_client\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\uc_order\Entity\Order;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Database\Driver\mysql\Connection;
use Drupal\Core\Extension\ModuleHandler;

/**
 * Controller for handling webhooks from GoCardless.com.
 */
class WebhookHandler extends ControllerBase {

  /**
   * The database driver connection.
   *
   * @var \Drupal\Core\Database\Driver\mysql\Connection
   */
  protected $db;

  /**
   * The logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  private $logger;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandler
   */
  protected $moduleHandler;

  /**
   * Constructs the WebhookHandler object.
   *
   * @param \Drupal\Core\Database\Driver\mysql\Connection $connection
   *   The database driver connection.
   * @param \Drupal\Core\Extension\ModuleHandler $moduleHandler
   *   The module handler.
   */
  public function __construct(Connection $connection, ModuleHandler $moduleHandler) {
    $this->db = $connection;
    $this->moduleHandler = $moduleHandler;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('database'),
      $container->get('module_handler')
    );
  }

  /**
   * Handles webhooks from GoCardless.com.
   */
  public function webhook() {
    $settings = GoCardlessPartner::getSettings();
    $secret = GoCardlessPartner::getPartnerWebhook();
    $webhook = file_get_contents('php://input');
    $headers = function_exists('getallheaders') ? getallheaders() : $this->getAllHeaders();
    $provided_signature = $headers["Webhook-Signature"];
    $calculated_signature = hash_hmac("sha256", $webhook, $secret);
    $hash_equals = function_exists('hash_equals') ? hash_equals($provided_signature, $calculated_signature) : $this->hashEquals($provided_signature, $calculated_signature);

    if ($hash_equals) {
      $data = json_decode($webhook, TRUE);
      // Optionally write webhook to log.
      if ($settings['log_webhook']) {
        if (!isset($this->logger)) {
          $this->logger = $this->getLogger('uc_gc_client');
        }
        $this->logger->notice('<pre>GoCardless webhook: <br />' . print_r($data, TRUE) . '</pre>');
      }

      // Process the events.
      foreach ($data['events'] as $event) {
        switch ($event['resource_type']) {
          case 'mandates':
            if ($order_id = $this->getOrderId($event['links']['mandate'])) {
              if ($order = Order::load($order_id)) {
                $resource = [];
                $this->mandate($order, $event['action'], $event);
              }
            }
            break;

          case 'subscriptions':
            if ($order_id = $this->getOrderId($event['links']['mandate'])) {
              if ($order = Order::load($order_id)) {
                $resource = [];
                $this->subscription($order, $event['action'], $event);
              }
            }
            break;

          case 'payments':
            $payment_id = $event['links']['payment'];
            $partner = new GoCardlessPartner();
            $result = $partner->api([
              'endpoint' => 'payments',
              'action' => 'get',
              'id' => $payment_id,
            ]);
            if ($result->response->status_code == 200) {
              $resource = $result->response->body->payments;
              if ($order_id = $this->getOrderId($resource->links->mandate)) {
                if ($order = Order::load($order_id)) {
                  $this->payment($order, $event['action'], $event, $resource);
                }
              }
            }
            break;
        }

        // Provide hook so other modules can respond to GoCardless webhooks.
        $params = [
          'event' => $event,
          'resource' => $resource,
          'order_id' => isset($order_id) ? $order_id : NULL,
        ];
        $this->moduleHandler->invokeAll('gc_client_webhook', [$params]);
      }

      // Send a success header.
      return new Response();
    }
    else {
      $this->getLogger('uc_gc_client')
        ->warning('Webhook cannot be processed because the webhook secret is not set or is invalid.');
      return new Response('Invalid Token.', 498, ['Content-Type' => 'text/html']);
    }
  }

  /**
   * Processes 'mandate' webhooks.
   *
   * @param object $order
   *   Ubercart order entity.
   * @param string $action
   *   The webhook event type, as provided by GoCardless.
   * @param array $event
   *   The webhook event, provided by GoCardless.
   */
  private function mandate($order, $action, array $event) {
    switch ($action) {
      case 'submitted':
        if ($order->order_status->getString() <> 'processing') {
          $comment = $this->t('Your direct debit mandate @mandate has been submitted to your bank by GoCardless and will be processed soon.', ['@mandate' => $event['links']['mandate']]);
          uc_order_comment_save($order->id(), $order->uid->getString(), $comment, 'order', 'processing', FALSE);
          $order->setStatusId('processing')->save();
          $this->db->update('uc_gc_client')
            ->fields([
              'status' => 'pending',
              'updated' => REQUEST_TIME,
            ])
            ->condition('ucid', $order->id())
            ->execute();
        }
        break;

      case 'failed':
        if ($order->order_status->getString() <> 'mandate_failed') {
          $comment = $this->t('Your direct debit mandate @mandate creation has failed.', ['@mandate' => $event['links']['mandate']]);
          uc_order_comment_save($order->id(), $order->uid->getString(), $comment, 'order', 'processing', TRUE);
          $order->setStatusId('mandate_failed')->save();
        }
        break;

      case 'active':
        if ($order->order_status->getString() <> 'mandate_active') {
          $comment = $this->t('Your direct debit mandate @mandate has been activated successfully with your bank.', ['@mandate' => $event['links']['mandate']]);
          uc_order_comment_save($order->id(), $order->uid->getString(), $comment, 'order', 'completed', TRUE);
          $order->setStatusId('mandate_active')->save();
          $this->db->update('uc_gc_client')
            ->fields([
              'status' => 'completed',
              'updated' => REQUEST_TIME,
            ])
            ->condition('ucid', $order->id())
            ->execute();
        }
        break;

      case 'cancelled':
        if ($order->order_status->getString() <> 'canceled') {
          $comment = $this->t('Your direct debit mandate @mandate has been cancelled with your bank by GoCardless.', ['@mandate' => $event['links']['mandate']]);
          uc_order_comment_save($order->id(), $order->uid->getString(), $comment, 'order', 'canceled', TRUE);
          $order->setStatusId('canceled')->save();
          $this->db->update('uc_gc_client')
            ->fields([
              'status' => 'canceled',
              'updated' => REQUEST_TIME,
            ])
            ->condition('ucid', $order->id())
            ->execute();
        }
        break;

      case 'reinstated':
        if ($order->order_status->getString() <> 'processing') {
          $comment = $this->t('Your direct debit mandate @mandate has been reinstated at GoCardless.', ['@mandate' => $event['links']['mandate']]);
          uc_order_comment_save($order->id(), $order->uid->getString(), $comment, 'order', 'processing', FALSE);
          $order->setStatusId('pending')->save();
          $this->db->update('uc_gc_client')
            ->fields([
              'status' => 'pending',
              'updated' => REQUEST_TIME,
            ])
            ->condition('ucid', $order->id())
            ->execute();
        }
        break;
    }
  }

  /**
   * Processes 'payment' webhooks.
   *
   * @param object $order
   *   Ubercart order entity.
   * @param string $action
   *   The webhook event type, as provided by GoCardless.
   * @param array $event
   *   The webhook event, provided by GoCardless.
   * @param object $resource
   *   The GoCardless payment resource relating to the webhook event. This is
   *   retreived from GoCardless after receiving the webhook.
   */
  private function payment($order, $action, array $event, $resource) {
    $amount = $resource->amount / 100;
    !empty($order->billing_country->getString()) ? $country_code = $order->billing_country->getString() : $country_code = $order->delivery_country->getString();
    $currency_sign = uc_gc_client_currency($country_code)['sign'];

    switch ($action) {
      case 'confirmed':
        uc_payment_enter($order->id(), 'gc_client', $amount, 0, NULL, $this->t('Direct debit has been taken by GoCardless'));
        $comment = $this->t('Your payment of @amount has been confirmed by GoCardless and will be paid from your bank account.', ['@amount' => uc_currency_format($amount, $currency_sign)]);
        uc_order_comment_save($order->id(), 0, $comment, 'order', $order->order_status->getString(), TRUE);
        // Update status to payment_received if it is the first one.
        if ($order->order_status->getString() == 'mandate_active') {
          $order->setStatusId('payment_received')->save();
        }
        break;

      case 'cancelled':
        $comment = $this->t("Your direct debit payment '@id' for @amount has been cancelled at GoCardless.", [
          '@id' => $event['id'],
          '@amount' => uc_currency_format($amount, $currency_sign),
        ]);
        uc_order_comment_save($order->id, 0, $comment, 'order', $order->order_status->getString(), TRUE);
        break;
    }
  }

  /**
   * Processes 'subscription' webhooks.
   *
   * @param object $order
   *   Ubercart order entity.
   * @param string $action
   *   The webhook event type, as provided by GoCardless.
   * @param array $event
   *   The webhook event, provided by GoCardless.
   */
  private function subscription($order, $action, array $event) {
    /*
    switch ($action) {
    case 'cancelled' :
    foreach ($items as $item) {
    isset($item['source_id']) ? $gc_order_id = $item['source_id'] : $gc_order_id = $item['id'];
    $order_id = uc_gc_client_id($gc_order_id);
    $order = uc_order_load($order_id);
    uc_order_update_status($order_id, 'canceled');
    uc_order_comment_save($order_id, $order->uid, $this->t('This direct debit Subscription has been cancelled with GoCardless.com.'), 'order', 'canceled', TRUE);
    // update the status on the database
    $update = $this->db->update('uc_gcsubs')
    ->fields(array(
    'status' => 'canceled',
    'updated' => time(),
    ))
    ->condition('ucid', $order_id, '=')
    ->execute();
    }

    // Invoke Rules event
    //if (module_exists('rules')) {
    //  $items_string = json_encode($items);
    //  rules_invoke_event('uc_gcsubs_subs_cancellation', $items_string);
    //}
    break;
    }
     */
  }

  /**
   * Return the Ubercart order ID from database for specified GC mandate ID.
   *
   * @param string $gcid
   *   The GoCardless mandate ID.
   *
   * @return int
   *   The Ubercart order ID for the mandate.
   */
  private function getOrderId($gcid) {
    return(
      $this->db->select('uc_gc_client', 'c')
        ->fields('c', ['ucid'])
        ->condition('gcid', $gcid)
        ->execute()->fetchField()
    );
  }

  /**
   * Timing attack safe string comparison.
   *
   * Compares two strings using the same time whether they're equal or not.
   * Required when hash_equals() function is not present (PHP < 5.6.0).
   *
   * @param string $known_string
   *   The string of known length to compare against.
   * @param string $user_string
   *   The user-supplied string.
   *
   * @return bool
   *   Returns TRUE when the two strings are equal, FALSE otherwise.
   */
  private function hashEquals($known_string, $user_string) {
    $ret = 0;
    if (strlen($known_string) !== strlen($user_string)) {
      $user_string = $known_string;
      $ret = 1;
    }
    $res = $known_string ^ $user_string;
    for ($i = strlen($res) - 1; $i >= 0; --$i) {
      $ret |= ord($res[$i]);
    }
    return !$ret;
  }

  /**
   * Fetch all HTTP request headers.
   *
   * Required for Nginx servers that do not support getallheaders().
   *
   * @return mixed
   *   An associative array of all the HTTP headers in the current request, or
   *   FALSE on failure.
   */
  private function getAllHeaders() {
    $headers = [];
    foreach ($_SERVER as $name => $value) {
      if (substr($name, 0, 5) == 'HTTP_') {
        $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
      }
    }
    return $headers;
  }

}

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

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