commerce_api-8.x-1.x-dev/src/Resource/PaymentGateway/PaymentApproveResource.php
src/Resource/PaymentGateway/PaymentApproveResource.php
<?php declare(strict_types=1); namespace Drupal\commerce_api\Resource\PaymentGateway; use Drupal\commerce_api\Resource\FixIncludeTrait; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_order\Exception\OrderVersionMismatchException; use Drupal\commerce_payment\Entity\PaymentGatewayInterface; use Drupal\commerce_payment\Exception\PaymentGatewayException; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayInterface; 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_resources\Resource\EntityResourceBase; 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 off-site payment transactions. * * @see \Drupal\commerce_payment\Controller\PaymentCheckoutController. */ final class PaymentApproveResource extends EntityResourceBase implements ContainerInjectionInterface { use FixIncludeTrait; /** * Constructs a new OnReturnResource object. * * @param \Psr\Log\LoggerInterface $logger * The logger. * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer. * @param \Drupal\Core\Database\Connection $connection * The database connection. */ public function __construct(private LoggerInterface $logger, private RendererInterface $renderer, private Connection $connection) {} /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new self( $container->get('logger.channel.commerce_payment'), $container->get('renderer'), $container->get('database') ); } /** * Process the request. * * @param \Symfony\Component\HttpFoundation\Request $request * The request. * @param \Drupal\commerce_order\Entity\OrderInterface $commerce_order * The order. */ public function process(Request $request, OrderInterface $commerce_order) { $transaction = $this->connection->startTransaction(); try { return $this->doProcess($request, $commerce_order); } catch (EntityStorageException $exception) { if ($exception->getPrevious() instanceof OrderVersionMismatchException) { $transaction->rollBack(); throw new ConflictHttpException($exception->getMessage(), $exception); } throw $exception; } } /** * Process the request. * * @param \Symfony\Component\HttpFoundation\Request $request * The request. * @param \Drupal\commerce_order\Entity\OrderInterface $order * The order. */ private function doProcess(Request $request, OrderInterface $order) { // @todo should this actually be a "not allowed" exception? // instead be kind and just return the order object to be reentrant. if (!$order->getState()->isTransitionAllowed('place')) { $this->fixOrderInclude($request); $top_level_data = $this->createIndividualDataFromEntity($order); return $this->createJsonapiResponse($top_level_data, $request); } 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 OffsitePaymentGatewayInterface) { throw new UnprocessableEntityHttpException('The payment gateway for the order does not implement ' . OffsitePaymentGatewayInterface::class); } try { $payment_gateway_plugin->onReturn($order, $request); } catch (PaymentGatewayException $e) { $this->logger->error($e->getMessage()); throw new UnprocessableEntityHttpException( 'Payment failed at the payment server. Please review your information and try again.', $e ); } // The on return method is concerned with creating/completing payments, so // we can assume the order has been finished and place it. $render_context = new RenderContext(); $this->renderer->executeInRenderContext($render_context, function () use ($order) { $order->getState()->applyTransitionById('place'); $order->save(); }); $this->fixOrderInclude($request); $top_level_data = $this->createIndividualDataFromEntity($order); return $this->createJsonapiResponse($top_level_data, $request); } }