commerce_api-8.x-1.x-dev/src/Resource/Checkout/PaymentResource.php

src/Resource/Checkout/PaymentResource.php
<?php

declare(strict_types=1);

namespace Drupal\commerce_api\Resource\Checkout;

use Drupal\commerce_api\EntityResourceShim;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_order\Exception\OrderVersionMismatchException;
use Drupal\commerce_payment\Entity\PaymentGatewayInterface;
use Drupal\commerce_payment\Entity\PaymentInterface;
use Drupal\commerce_payment\Exception\DeclineException;
use Drupal\commerce_payment\Exception\PaymentGatewayException;
use Drupal\commerce_payment\PaymentOrderUpdaterInterface;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\ManualPaymentGatewayInterface;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel;
use Drupal\jsonapi\JsonApiResource\ResourceObject;
use Drupal\jsonapi\JsonApiResource\ResourceObjectData;
use Drupal\jsonapi\ResourceResponse;
use Drupal\jsonapi_resources\Resource\EntityResourceBase;
use Drupal\jsonapi_resources\Unstable\Entity\ResourceObjectToEntityMapperAwareTrait;
use Drupal\jsonapi_resources\Unstable\Value\NewResourceObject;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

/**
 * Resource for manual and payment method payment transactions.
 */
final class PaymentResource extends EntityResourceBase implements ContainerInjectionInterface {

  use ResourceObjectToEntityMapperAwareTrait;

  /**
   * Constructs a new PaymentResource object.
   *
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer.
   * @param \Drupal\commerce_api\EntityResourceShim $inner
   *   The JSON:API controller shim.
   * @param \Drupal\commerce_payment\PaymentOrderUpdaterInterface $paymentOrderUpdater
   *   The order update manager.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger.
   * @param \Drupal\Core\Database\Connection $connection
   *   The database connection.
   */
  public function __construct(private RendererInterface $renderer, protected EntityResourceShim $inner, protected PaymentOrderUpdaterInterface $paymentOrderUpdater, protected LoggerInterface $logger, protected Connection $connection) {}

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new self(
      $container->get('renderer'),
      $container->get('commerce_api.jsonapi_controller_shim'),
      $container->get('commerce_payment.order_updater'),
      $container->get('logger.channel.commerce_payment'),
      $container->get('database')
    );
  }

  /**
   * Process the resource request.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   * @param \Drupal\commerce_order\Entity\OrderInterface $commerce_order
   *   The order.
   * @param \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel $document
   *   The deserialized request document.
   *
   * @return \Drupal\jsonapi\ResourceResponse
   *   The response.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   * @throws \Drupal\Core\Entity\EntityStorageException
   * @throws \Symfony\Component\HttpKernel\Exception\ConflictHttpException
   */
  public function process(Request $request, OrderInterface $commerce_order, JsonApiDocumentTopLevel $document): ResourceResponse {
    // Starting a transaction here ensures no payment will be saved in case
    // of an order version mismatch exception.
    $transaction = $this->connection->startTransaction();
    try {
      return $this->doProcess($request, $commerce_order, $document);
    }
    catch (EntityStorageException $exception) {
      if ($exception->getPrevious() instanceof OrderVersionMismatchException) {
        $transaction->rollBack();
        throw new ConflictHttpException($exception->getMessage(), $exception);
      }
      throw $exception;
    }
  }

  /**
   * Process the resource request.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   * @param \Drupal\commerce_order\Entity\OrderInterface $order
   *   The order.
   * @param \Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel $document
   *   The deserialized request document.
   *
   * @return \Drupal\jsonapi\ResourceResponse
   *   The response.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  private function doProcess(Request $request, OrderInterface $order, JsonApiDocumentTopLevel $document) {
    if ($document === NULL) {
      throw new UnprocessableEntityHttpException('The request document was empty.');
    }

    if ($order->get('payment_gateway')->isEmpty()) {
      throw new UnprocessableEntityHttpException('A payment gateway is not set for this order.');
    }
    $payment_gateway = $order->get('payment_gateway')->entity;
    if (!$payment_gateway instanceof PaymentGatewayInterface) {
      throw new UnprocessableEntityHttpException('A payment gateway is not set for this order.');
    }

    $payment_gateway_plugin = $payment_gateway->getPlugin();
    if (!$payment_gateway_plugin instanceof ManualPaymentGatewayInterface && !$payment_gateway_plugin instanceof SupportsStoredPaymentMethodsInterface) {
      throw new UnprocessableEntityHttpException(sprintf('The payment gateway for the order does not implement %s or %s', ManualPaymentGatewayInterface::class, SupportsStoredPaymentMethodsInterface::class));
    }

    $data = $document->getData();
    if ($data->getCardinality() !== 1) {
      throw new UnprocessableEntityHttpException("The request document's primary data must not be an array.");
    }
    // Ensure the "place" transition is allowed before creating a payment.
    if (!$order->getState()->isTransitionAllowed('place')) {
      throw new UnprocessableEntityHttpException('The "place" transition is not allowed.');
    }

    $resource_object = $data->getIterator()->current();
    assert($resource_object instanceof NewResourceObject);

    $allowed_fields = ['capture'];
    $has_disallowed_fields = array_diff(array_keys($resource_object->getFields()), $allowed_fields);
    if (count($has_disallowed_fields) > 0) {
      throw new UnprocessableEntityHttpException('The following fields are not allowed: ' . implode(', ', $has_disallowed_fields));
    }

    $payment = $this->resourceObjectToEntityMapper->createEntityFromResourceObject($resource_object);
    assert($payment instanceof PaymentInterface);

    // @todo make this fields write access denied in FieldAccess.
    // @todo is there a way to set these in the resource object directly before mapping.
    $payment->state = 'new';
    $payment->amount = $order->getBalance();
    $payment->payment_gateway = $order->get('payment_gateway')->target_id;
    $payment->payment_method = $order->get('payment_method')->target_id;
    $payment->order_id = $order->id();

    try {
      if ($payment_gateway_plugin instanceof SupportsStoredPaymentMethodsInterface) {
        $capture = $resource_object->getField('capture') ?? TRUE;
        $payment_gateway_plugin->createPayment($payment, $capture);
      }
      elseif ($payment_gateway_plugin instanceof ManualPaymentGatewayInterface) {
        $payment_gateway_plugin->createPayment($payment);
      }
      // No other payment gateway processing possibilities.
      else {
        throw new UnprocessableEntityHttpException('We encountered an unexpected error processing your payment method. Please try again later.');
      }
    }
    catch (DeclineException $e) {
      $this->logger->error($e->getMessage());
      throw new UnprocessableEntityHttpException('We encountered an error processing your payment method. Please verify your details and try again.');
    }
    catch (PaymentGatewayException $e) {
      $this->logger->error($e->getMessage());
      throw new UnprocessableEntityHttpException('We encountered an unexpected error processing your payment method. Please try again later.');
    }
    catch (\InvalidArgumentException $e) {
      $this->logger->error($e->getMessage());
      throw new UnprocessableEntityHttpException($e->getMessage());
    }

    $render_context = new RenderContext();
    $this->renderer->executeInRenderContext($render_context, function () use ($order) {
      // The on return method is concerned with creating/completing payments,
      // so we can assume the order has been finished and place it.
      $order->getState()->applyTransitionById('place');

      if ($this->paymentOrderUpdater->needsUpdate($order)) {
        $this->paymentOrderUpdater->updateOrder($order);
      }

      $order->save();
    });

    $primary_data = new ResourceObjectData([
      ResourceObject::createFromEntity($this->resourceTypeRepository->get($payment->getEntityTypeId(), $payment->bundle()), $payment),
    ], 1);
    $response = $this->createJsonapiResponse($primary_data, $request, 201);
    if (!$render_context->isEmpty() &&
      $response instanceof CacheableResponseInterface) {
      $response->addCacheableDependency($render_context->pop());
    }
    return $response;
  }

}

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

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