Unverified Commit 5e892e33 authored by tuutti's avatar tuutti Committed by GitHub
Browse files

Merge pull request #44 from tuutti/3312490

Issue #3312490: Support Guzzle 7
parents 0a1d8e23 b2fd0fd0
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@ env:
  SIMPLETEST_DB: "mysql://drupal:drupal@mariadb:3306/drupal"
  SIMPLETEST_BASE_URL: "http://127.0.0.1:8080"
  DRUPAL_MODULE_NAME: "commerce_paytrail"
  DRUPAL_CORE_VERSION: 9.3.x
  DRUPAL_CORE_VERSION: 9.4.x
  SYMFONY_DEPRECATIONS_HELPER: disabled
jobs:
  test-contrib:
@@ -43,9 +43,9 @@ jobs:
          cd $DRUPAL_ROOT
          composer config platform.php ${{ matrix.php-version }}
          composer config repositories.0 path $GITHUB_WORKSPACE
          composer require drupal/$DRUPAL_MODULE_NAME
          composer require drupal/$DRUPAL_MODULE_NAME -W
          composer run-script drupal-phpunit-upgrade
          composer require "drush/drush ^10.0"
          composer require "drush/drush ^11.0"
          composer config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
          composer require --dev "drupal/coder"

+1 −1
Original line number Diff line number Diff line
@@ -6,6 +6,6 @@
    "require": {
        "php": ">=8.0",
        "drupal/commerce": "^2",
        "tuutti/php-paytrail-payment-api": "^1.4"
        "tuutti/php-paytrail-payment-api": "^1.4 || ^2.0"
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ final class Header {
   *   The method.
   * @param string $nonce
   *   The nonce.
   * @param string $timestamp
   * @param int|string $timestamp
   *   The timestamp.
   * @param string|null $transactionId
   *   The transactionId.
@@ -34,7 +34,7 @@ final class Header {
    public string $hashAlgorithm,
    public string $method,
    public string $nonce,
    public string $timestamp,
    public int|string $timestamp,
    public ?string $transactionId = NULL,
    public ?string $platformName = NULL
  ) {
+62 −17
Original line number Diff line number Diff line
@@ -10,8 +10,8 @@ use Drupal\commerce_payment\Exception\PaymentGatewayException;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsNotificationsInterface;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsRefundsInterface;
use Drupal\commerce_paytrail\Exception\SecurityHashMismatchException;
use Drupal\commerce_paytrail\RequestBuilder\PaymentRequestBuilder;
use Drupal\commerce_paytrail\RequestBuilder\RefundRequestBuilder;
use Drupal\commerce_paytrail\RequestBuilder\PaymentRequestBuilderInterface;
use Drupal\commerce_paytrail\RequestBuilder\RefundRequestBuilderInterface;
use Drupal\commerce_price\Price;
use Drupal\Core\Queue\QueueInterface;
use Drupal\Core\Url;
@@ -41,16 +41,16 @@ final class Paytrail extends PaytrailBase implements SupportsNotificationsInterf
  /**
   * The payment request builder.
   *
   * @var \Drupal\commerce_paytrail\RequestBuilder\PaymentRequestBuilder
   * @var \Drupal\commerce_paytrail\RequestBuilder\PaymentRequestBuilderInterface
   */
  private PaymentRequestBuilder $paymentRequest;
  private PaymentRequestBuilderInterface $paymentRequest;

  /**
   * The refund request builder.
   *
   * @var \Drupal\commerce_paytrail\RequestBuilder\RefundRequestBuilder
   * @var \Drupal\commerce_paytrail\RequestBuilder\RefundRequestBuilderInterface
   */
  private RefundRequestBuilder $refundRequest;
  private RefundRequestBuilderInterface $refundRequest;

  /**
   * The queue.
@@ -63,7 +63,6 @@ final class Paytrail extends PaytrailBase implements SupportsNotificationsInterf
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) : static {
    /** @var \Drupal\commerce_paytrail\Plugin\Commerce\PaymentGateway\Paytrail $instance */
    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);

    // Populate via setters to avoid overriding the parent constructor.
@@ -120,6 +119,16 @@ final class Paytrail extends PaytrailBase implements SupportsNotificationsInterf
   *   The response.
   */
  public function onNotify(Request $request) : Response {
    $callback = match ($request->query->get('event')) {
      // Refunds can be asynchronous, meaning the refund can be in 'pending'
      // state and requires a valid success/cancel callback. Payments are
      // always marked as refunded regardless of its remote state.
      // Return a 200 response to make sure Paytrail doesn't keep
      // calling this for no reason.
      'refund-success', 'refund-cancel' => function (Request $request) : Response {
        return new Response();
      },
      default => function (Request $request) : Response {
        $storage = $this->entityTypeManager->getStorage('commerce_order');

        try {
@@ -129,8 +138,12 @@ final class Paytrail extends PaytrailBase implements SupportsNotificationsInterf
          }
          $this->validateResponse($order, $request);

          // Queue the order to avoid a race-condition between onNotify() and
          // onReturn() callbacks.
          // @see https://www.drupal.org/node/3268851
          $this->queue->createItem([
            'order_id' => $order->id(),
            'transaction_id' => $request->query->get('checkout-transaction-id'),
          ]);
          return new Response();
        }
@@ -139,6 +152,24 @@ final class Paytrail extends PaytrailBase implements SupportsNotificationsInterf
        }
        return new Response(status: Response::HTTP_FORBIDDEN);
      }
    };

    return $callback($request);
  }

  /**
   * {@inheritdoc}
   */
  public function getNotifyUrl(string $eventName = NULL) : Url {
    $url = parent::getNotifyUrl();

    if ($eventName) {
      $query = $url->getOption('query');
      $query['event'] = $eventName;
      $url->setOption('query', $query);
    }
    return $url;
  }

  /**
   * Validate and store transaction for order.
@@ -152,7 +183,10 @@ final class Paytrail extends PaytrailBase implements SupportsNotificationsInterf
    try {
      $this->validateResponse($order, $request);

      $paymentResponse = $this->paymentRequest->get($order);
      $paymentResponse = $this->paymentRequest->get(
        $request->query->get('checkout-transaction-id'),
        $order
      );
      $this->assertResponseStatus($paymentResponse->getStatus(), [
        Payment::STATUS_OK,
        Payment::STATUS_PENDING,
@@ -175,12 +209,26 @@ final class Paytrail extends PaytrailBase implements SupportsNotificationsInterf
   *
   * @throws \Drupal\commerce_paytrail\Exception\SecurityHashMismatchException
   */
  protected function validateResponse(OrderInterface $order, Request $request) : void {
  private function validateResponse(OrderInterface $order, Request $request) : void {
    [
      'checkout-reference' => $requestOrderId,
      'checkout-transaction-id' => $transactionId,
    ] = $request->query->all() + [
      'checkout-reference' => NULL,
      'checkout-transaction-id' => NULL,
    ];

    if (!$transactionId) {
      throw new SecurityHashMismatchException('Transaction ID not set.');
    }
    // onReturn() uses {commerce_order} to load the order, which is not a part
    // of the signature hash calculation. Make sure the order entity ID
    // matches the order id in 'checkout-reference' to make sure a valid return
    // URL cannot be reused.
    if (!$requestOrderId || $requestOrderId !== $order->id()) {
      throw new SecurityHashMismatchException('Order ID mismatch.');
    }
    $this->paymentRequest
      // onNotify() uses {commerce_order} to load the order which is not a part
      // of signature hash calculation. Make sure stamp matches with the stamp
      // saved in order entity so a valid return URL cannot be re-used.
      ->validateStamp($order, $request->query->get('checkout-stamp'))
      ->validateSignature($this, $request->query->all());
  }

@@ -223,9 +271,6 @@ final class Paytrail extends PaytrailBase implements SupportsNotificationsInterf

  /**
   * {@inheritdoc}
   *
   * @todo Refunds can be asynchronous in the future.
   * @see https://docs.paytrail.com/#/?id=refund
   */
  public function refundPayment(PaymentInterface $payment, Price $amount = NULL) : void {
    $this->assertPaymentState($payment, ['completed', 'partially_refunded']);
@@ -239,7 +284,7 @@ final class Paytrail extends PaytrailBase implements SupportsNotificationsInterf
    $newRefundedAmount = $oldRefundedAmount->add($amount);

    try {
      $response = $this->refundRequest->refund($order, $amount);
      $response = $this->refundRequest->refund($payment->getRemoteId(), $order, $amount);

      $this->assertResponseStatus($response->getStatus(), [
        RefundResponse::STATUS_OK,
+14 −8
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@ namespace Drupal\commerce_paytrail\Plugin\QueueWorker;
use Drupal\commerce_order\OrderStorage;
use Drupal\commerce_paytrail\Exception\PaytrailPluginException;
use Drupal\commerce_paytrail\PaymentGatewayPluginTrait;
use Drupal\commerce_paytrail\RequestBuilder\PaymentRequestBuilder;
use Drupal\commerce_paytrail\RequestBuilder\PaymentRequestBuilderInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Queue\QueueWorkerBase;
use Paytrail\Payment\Model\Payment;
@@ -41,7 +41,7 @@ final class NotificationWorker extends QueueWorkerBase implements ContainerFacto
   *   The plugin definition.
   * @param \Drupal\commerce_order\OrderStorage $orderStorage
   *   The order storage.
   * @param \Drupal\commerce_paytrail\RequestBuilder\PaymentRequestBuilder $paymentRequest
   * @param \Drupal\commerce_paytrail\RequestBuilder\PaymentRequestBuilderInterface $paymentRequest
   *   The request builder.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger.
@@ -51,7 +51,7 @@ final class NotificationWorker extends QueueWorkerBase implements ContainerFacto
    string $plugin_id,
    array $plugin_definition,
    private OrderStorage $orderStorage,
    private PaymentRequestBuilder $paymentRequest,
    private PaymentRequestBuilderInterface $paymentRequest,
    private LoggerInterface $logger
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
@@ -80,12 +80,19 @@ final class NotificationWorker extends QueueWorkerBase implements ContainerFacto
   * {@inheritdoc}
   */
  public function processItem($data) : void {
    ['order_id' => $id] = $data;
    ['order_id' => $id, 'transaction_id' => $transactionId] = $data;

    // Order not found or is paid already, we can safely ignore the item.
    // Order not found or is paid already. We can safely ignore the item.
    if ((!$order = $this->orderStorage->load($id)) || $order->isPaid()) {
      return;
    }

    // The order validation/loading logic changed in 3.0-alpha4 release. Support
    // orders made before 3.0-alpha4 release.
    // @todo Remove this in 4.x.
    if (!$transactionId) {
      $transactionId = $order->getData('commerce_paytrail_transaction_id', NULL);
    }
    /** @var \Drupal\commerce_order\Entity\OrderInterface $order */
    $numTries = $order->getData(self::MAX_TRIES_SETTING, 0);

@@ -101,7 +108,7 @@ final class NotificationWorker extends QueueWorkerBase implements ContainerFacto
    }

    try {
      $paymentResponse = $this->paymentRequest->get($order);
      $paymentResponse = $this->paymentRequest->get($transactionId, $order);
    }
    catch (PaytrailPluginException) {
      // Nothing to do if dealing with non-paytrail order.
@@ -109,8 +116,7 @@ final class NotificationWorker extends QueueWorkerBase implements ContainerFacto
    }

    try {
      // Re-queue if order is not marked as paid. This probably should never
      // happen.
      // Re-queue if order is not marked as paid. This should never happen.
      if ($paymentResponse->getStatus() !== Payment::STATUS_OK) {
        throw new \InvalidArgumentException(
          sprintf('Order payment is not completed for order: %s', $id)
Loading