commerce_signifyd-1.0.x-dev/src/Signifyd/Webhook.php

src/Signifyd/Webhook.php
<?php

namespace Drupal\commerce_signifyd\Signifyd;

use Drupal\commerce_signifyd\Entity\SignifydTeamInterface;
use Drupal\commerce_signifyd\Event\SignifydEvents;
use Drupal\commerce_signifyd\Event\SignifydWebhookEvent;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\EntityMalformedException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

/**
 * {@inheritdoc}
 */
class Webhook extends SignifydAbstract implements WebhookInterface {

  /**
   * {@inheritdoc}
   */
  public function events(Request $request, SignifydTeamInterface $signifyd_team) {
    $body = $request->getContent();
    $topic = $request->headers->get('X-SIGNIFYD-TOPIC');
    $hash = $request->headers->get('X-SIGNIFYD-SEC-HMAC-SHA256');
    $valid = $this->validWebhookRequest($body, $hash, $topic, $signifyd_team);

    if ($this->logging) {
      $this->logger->notice(t('<code>@body</code></br><code>@topic</code></br><code>@hash</code>', [
        '@body' => $body,
        '@topic' => $topic,
        '@hash' => $hash,
      ]));
    }

    // Each payload needs to be validated.
    if (!$valid) {
      throw new BadRequestHttpException('Validation failed');
    }

    $case_storage = $this->entityTypeManager->getStorage('signifyd_case');
    $signifyd_data = Json::decode($body);

    if (!isset($signifyd_data['caseId'])) {
      throw new BadRequestHttpException('Malformed request');
    }

    /** @var \Drupal\commerce_signifyd\Entity\SignifydCaseInterface $signifyd_case */
    $signifyd_case = $case_storage->load($signifyd_data['caseId']);

    // Determine if we create or update Signifyd Case.
    $entity_action = $signifyd_case ? self::SIGNIFYD_UPDATE : self::SIGNIFYD_CREATE;

    switch ($topic) {
      case self::SIGNIFYD_CASE_CREATION:
      case self::SIGNIFYD_CASE_RESCORE:
      case self::SIGNIFYD_CASE_REVIEW:
        // Either create or update existing Signifyd case.
        if ($entity_action === self::SIGNIFYD_CREATE) {
          $signifyd_case = $case_storage->create([
            'case_id' => $signifyd_data['caseId'],
            'order_id' => $signifyd_data['orderId'],
            'score' => $signifyd_data['score'] ?? 0,
            'guarantee' => $signifyd_data['guaranteeDisposition'],
            'investigation_id' => $signifyd_data['investigationId'],
            'status' => $signifyd_data['status'],
            'team_id' => $signifyd_team->id(),
          ]);
        }
        else {
          $signifyd_case->setScore($signifyd_data['score'] ?? 0);
          $signifyd_case->setGuarantee($signifyd_data['guaranteeDisposition']);
          $signifyd_case->setInvestigationId($signifyd_data['investigationId']);
          $signifyd_case->set('status', $signifyd_data['status']);
          $signifyd_case->setTeam($signifyd_team);
        }

        $signifyd_case->save();
        $this->createWebhookLog($signifyd_case, $topic);
        break;

      case self::SIGNIFYD_DESCISION_MADE:
        // Either create or update existing Signifyd case.
        if ($entity_action === self::SIGNIFYD_CREATE) {
          $signifyd_case = $case_storage->create([
            'case_id' => $signifyd_data['caseId'],
            'order_id' => $signifyd_data['customerCaseId'],
            'score' => $signifyd_data['score'] ?? 0,
            'decision' => $signifyd_data['checkpointAction'],
            'team_id' => $signifyd_team->id(),
          ]);
        }
        else {
          $signifyd_case->setScore($signifyd_data['score']);
          $signifyd_case->setDecision($signifyd_data['checkpointAction']);
          $signifyd_case->setTeam($signifyd_team);
        }

        // When only decision made webhook is used, we don't get guarantee
        // value. Mapping only ACCEPT and REJECT.
        if (isset(self::SIGNIFYD_MAP_ACTION_TO_GUARANTEE[$signifyd_data['checkpointAction']])) {
          $signifyd_case->setGuarantee(self::SIGNIFYD_MAP_ACTION_TO_GUARANTEE[$signifyd_data['checkpointAction']]);
        }

        $signifyd_case->save();
        $this->createWebhookLog($signifyd_case, $topic);
        break;

      default:
        // Do nothing out of box.
        // Non specified topics falls into this category:
        // claims/paid, claim/reviewed, decisions/*.
        if (!$signifyd_case && $topic !== 'cases/test') {
          throw new EntityMalformedException('Non-existing case');
        }
    }

    // For test webhook call we don't have Signifyd case.
    if ($signifyd_case) {
      // Trigger event to react on Signifyd webhook updates.
      $event = new SignifydWebhookEvent($signifyd_case, $signifyd_data, $entity_action, $topic);
      $this->eventDispatcher->dispatch($event, SignifydEvents::SIGNIFYD_WEBHOOK);
      $order = $signifyd_case->getOrder();
      $order_type = $order->bundle();

      // Check if automatic workflow is enabled.
      if ($this->signifydSettings->get('order_types.' . $order_type . '.workflow')) {
        $declined_transition = $this->signifydSettings->get('order_types.' . $order_type . '.declined');
        $approved_transition = $this->signifydSettings->get('order_types.' . $order_type . '.approved');
        $decision_type = $this->signifydSettings->get('decision_type');

        $move_to_declined = FALSE;
        $move_to_approved = FALSE;
        switch ($decision_type) {
          case 'score':
            $case_score = $signifyd_case->getScore();

            // Only move if there is a score.
            if ($case_score > 0) {
              if ($signifyd_case->getScore() >= (int) $this->signifydSettings->get('score')) {
                $move_to_approved = TRUE;
              }
              else {
                $move_to_declined = TRUE;
              }
            }
            break;

          case 'decision':

            // Move unless decision is HOLD.
            if ($signifyd_case->getDecision() === 'ACCEPT') {
              $move_to_approved = TRUE;
            }
            elseif ($signifyd_case->getDecision() === 'REJECT') {
              $move_to_declined = TRUE;
            }

            break;

          // Guarantee logic.
          default:
            // Move unless guarantee is in review or pending.
            if ($signifyd_case->getGuarantee() === 'APPROVED') {
              $move_to_approved = TRUE;
            }
            elseif (in_array($signifyd_case->getGuarantee(), [
              'CANCELED',
              'DECLINED',
            ])) {
              $move_to_declined = TRUE;
            }
        }

        if ($move_to_declined && $order->getState()->isTransitionAllowed($declined_transition)) {
          $order->getState()->applyTransitionById($declined_transition);
          $order->save();
        }

        if ($move_to_approved && $order->getState()->isTransitionAllowed($approved_transition)) {
          $order->getState()->applyTransitionById($approved_transition);
          $order->save();
        }
      }
    }

    $response = new Response();
    $response->setStatusCode(200);
    return $response;
  }

  /**
   * Validate a webhook request.
   *
   * @param string $body
   *   The request body.
   * @param string $hash
   *   The hashed request.
   * @param string $topic
   *   The topic.
   * @param \Drupal\commerce_signifyd\Entity\SignifydTeamInterface $signifyd_team
   *   The Signifyd team.
   *
   * @return bool
   *   Return true if validated.
   *
   * @see https://github.com/signifyd/php/blob/main/lib/Core/Api/WebhooksApi.php
   */
  protected function validWebhookRequest($body, $hash, $topic, SignifydTeamInterface $signifyd_team) {
    if (empty($body) || empty($hash) || empty($topic)) {
      return FALSE;
    }
    $check = base64_encode(
      hash_hmac('sha256', $body, $signifyd_team->getApiKey(), TRUE)
    );

    if ($check == $hash) {
      return TRUE;
    }
    else {
      if ($topic == "cases/test") {
        // In the case that this is a webhook test,
        // the encoding ABCDE is allowed.
        $check = base64_encode(
          hash_hmac('sha256', $body, 'ABCDE', TRUE)
        );
        if ($check == $hash) {
          return TRUE;
        }
      }
    }

    return FALSE;
  }

}

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

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