Created
September 21, 2017 13:43
-
-
Save clrockwell/2df0553149d533aea2ccd51c9d9c8115 to your computer and use it in GitHub Desktop.
A sample endpoint for refunding a payment in Commerce 2.x
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Drupal\module\Plugin\rest\resource; | |
use Drupal\commerce_order\Entity\Order; | |
use Drupal\commerce_payment\Entity\Payment; | |
use Drupal\commerce_payment\Exception\PaymentGatewayException; | |
use Drupal\commerce_payment\PaymentGatewayManager; | |
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\PaymentGatewayBase; | |
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsRefundsInterface; | |
use Drupal\commerce_price\Price; | |
use Drupal\Component\Serialization\Json; | |
use Drupal\Core\Entity\EntityTypeManagerInterface; | |
use Drupal\Core\Session\AccountProxyInterface; | |
use Drupal\rest\Plugin\ResourceBase; | |
use Drupal\rest\ResourceResponse; | |
use Drupal\scholarrx_api\OrderUtility; | |
use Symfony\Component\DependencyInjection\ContainerInterface; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; | |
use Psr\Log\LoggerInterface; | |
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; | |
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; | |
/** | |
* Provides a resource to get view modes by entity and bundle. | |
* | |
* @RestResource( | |
* id = "refund_payment", | |
* label = @Translation("Refund payment"), | |
* uri_paths = { | |
* "canonical" = "/api/orders/{order_id}/refunds", | |
* "https://www.drupal.org/link-relations/create" = "/api/orders/{order_id}/refunds" | |
* } | |
* ) | |
*/ | |
class RefundPayment extends ResourceBase { | |
/** | |
* A current user instance. | |
* | |
* @var \Drupal\Core\Session\AccountProxyInterface | |
*/ | |
protected $currentUser; | |
/** | |
* @var \Drupal\Core\Entity\EntityTypeManagerInterface | |
*/ | |
protected $entityTypeManager; | |
/** | |
* @var \Drupal\commerce_payment\PaymentGatewayManager | |
*/ | |
protected $paymentGatewayManager; | |
/** | |
* Constructs a new RefundPayment object. | |
* | |
* @param array $configuration | |
* A configuration array containing information about the plugin instance. | |
* @param string $plugin_id | |
* The plugin_id for the plugin instance. | |
* @param mixed $plugin_definition | |
* The plugin implementation definition. | |
* @param array $serializer_formats | |
* The available serialization formats. | |
* @param \Psr\Log\LoggerInterface $logger | |
* A logger instance. | |
* @param \Drupal\Core\Session\AccountProxyInterface $current_user | |
* A current user instance. | |
*/ | |
public function __construct( | |
array $configuration, | |
$plugin_id, | |
$plugin_definition, | |
array $serializer_formats, | |
LoggerInterface $logger, | |
AccountProxyInterface $current_user, | |
EntityTypeManagerInterface $entityTypeManager, | |
PaymentGatewayManager $paymentGatewayManager) { | |
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger); | |
$this->currentUser = $current_user; | |
$this->entityTypeManager = $entityTypeManager; | |
$this->paymentGatewayManager = $paymentGatewayManager; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { | |
return new static( | |
$configuration, | |
$plugin_id, | |
$plugin_definition, | |
$container->getParameter('serializer.formats'), | |
$container->get('logger.factory')->get('scholarrx_api'), | |
$container->get('current_user'), | |
$container->get('entity_type.manager'), | |
$container->get('plugin.manager.commerce_payment_gateway') | |
); | |
} | |
/** | |
* Responds to GET requests. | |
* | |
* Returns a refunds for an order | |
* | |
* @throws \Symfony\Component\HttpKernel\Exception\HttpException | |
* Throws exception expected. | |
*/ | |
public function get() { | |
throw new MethodNotAllowedHttpException('GET has not been implemented, please use the order endpoint to view any adjustments'); | |
} | |
/** | |
* Responds to POST requests. | |
* | |
* @param Request $request | |
* | |
* @throws \Symfony\Component\HttpKernel\Exception\HttpException | |
* Throws exception expected. | |
*/ | |
public function post($order_id, $unknown = '', Request $request) { | |
$content = Json::decode($request->getContent()); | |
$this->assertContent($content); | |
/** @var Price $refund_amount */ | |
$refund_amount = new Price($content['amount'], 'USD'); | |
$message = $content['message']; | |
/** @var Order $order */ | |
$order = $this->entityTypeManager->getStorage('commerce_order') | |
->load($order_id); | |
$this->assertOrder($order); | |
$payments = $this->entityTypeManager->getStorage('commerce_payment') | |
->loadMultipleByOrder($order); | |
$this->assertPayments($payments); | |
$use = $this->getPaymentToUse($payments, $refund_amount); | |
$this->assertPayment($use); | |
$this->assertCardOnPayment($content['card'], $use); | |
/** @var SupportsRefundsInterface $gateway_plugin */ | |
$gateway_plugin = $use->getPaymentGateway()->getPlugin(); | |
$this->assertGatewayPlugin($gateway_plugin); | |
try { | |
$gateway_plugin->refundPayment($use, $refund_amount); | |
} | |
catch (PaymentGatewayException $exception) { | |
throw new PaymentGatewayException($exception->getMessage() . ' This is likely caused by a duplicate transaction: https://support.authorize.net/authkb/index?page=content&id=A425'); | |
} | |
$this->logMessageToOrder($message, $order); | |
$payment = $this->entityTypeManager->getStorage('commerce_payment')->load($use->id()); | |
return new ResourceResponse(OrderUtility::massagePayment($payment)); | |
} | |
protected function getPaymentToUse($payments, Price $refund_amount) { | |
$use = FALSE; | |
/** @var Payment $payment */ | |
foreach ($payments as $payment) { | |
/** @var Price $balance */ | |
$balance = $payment->getBalance(); | |
if ($refund_amount->greaterThan($balance)) { | |
continue; | |
} | |
else { | |
/** @var Payment $use */ | |
$use = $payment; | |
} | |
} | |
return $use; | |
} | |
protected function logMessageToOrder($message, $order) { | |
$this->entityTypeManager->getStorage('commerce_log')->generate($order, 'api_refund', [ | |
'message' => $message, | |
])->save(); | |
} | |
protected function assertContent($content) { | |
$this->assertAmount($content); | |
$this->assertMessage($content); | |
$this->assertCard($content); | |
} | |
protected function assertAmount($content) { | |
if (!$content['amount']) { | |
throw new BadRequestHttpException('A refund amount must be specified.'); | |
} | |
} | |
protected function assertMessage($content) { | |
if (!$content['message']) { | |
throw new BadRequestHttpException('A message for the refund must be specified.'); | |
} | |
} | |
protected function assertCard($content) { | |
if (!$content['card']) { | |
throw new BadRequestHttpException('The last 4 digits of card must be specified.'); | |
} | |
} | |
protected function assertOrder($order) { | |
if (!$order || !$order instanceof Order) { | |
throw new BadRequestHttpException('No order found'); | |
} | |
} | |
protected function assertPayments($payments) { | |
if (empty($payments)) { | |
throw new BadRequestHttpException('There are no payments for this order'); | |
} | |
} | |
protected function assertPayment($payment) { | |
if (!$payment || !$payment instanceof Payment) { | |
throw new BadRequestHttpException('There are no payments that are at least the amount of the refund request. Refunding an amount to multiple payments is not currently supported'); | |
} | |
} | |
protected function assertCardOnPayment($card, Payment $payment) { | |
if ($card != $payment->getPaymentMethod()->get('card_number')->value) { | |
throw new AccessDeniedHttpException('The provided card does not match with an available payment method.'); | |
} | |
} | |
protected function assertGatewayPlugin(PaymentGatewayBase $gateway) { | |
if (!$gateway instanceof SupportsRefundsInterface) { | |
throw new BadRequestHttpException('There is no refundPayment method on the gateway available'); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment