diff --git a/config/common.yml b/config/common.yml index f94a67ac5..b819e665e 100644 --- a/config/common.yml +++ b/config/common.yml @@ -627,6 +627,7 @@ services: public: true arguments: - "@ps_checkout.repository.pscheckoutcart" + - "@ps_checkout.order.state.service.order_state_mapper" ps_checkout.query.handler.order.get_order_for_payment_reversed: class: 'PrestaShop\Module\PrestashopCheckout\Order\QueryHandler\GetOrderForPaymentReversedQueryHandler' diff --git a/ps_checkout.php b/ps_checkout.php index 4b367e558..6fbfa2c19 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -46,6 +46,7 @@ class Ps_checkout extends PaymentModule 'actionObjectOrderPaymentUpdateAfter', 'displayPaymentReturn', 'displayOrderDetail', + 'actionOrderSlipAdd', ]; /** @@ -1671,4 +1672,134 @@ public function hookDisplayOrderDetail(array $params) return $this->display(__FILE__, 'views/templates/hook/displayOrderDetail.tpl'); } + + /** + * Refund based on OrderSlip + * + * @param array{cookie: Cookie, cart: Cart, altern: int, order: Order} $params + * + * @return void + */ + public function hookActionOrderSlipAdd(array $params) + { + try { + /** @var Order $order */ + $order = $params['order']; + + if (!Validate::isLoadedObject($order)) { + return; + } + + /** @var OrderSlip[]|bool $orderSlipCollection */ + $orderSlipCollection = $order->getOrderSlipsCollection()->getResults(); + + if (!$orderSlipCollection) { + return; + } + + /** @var OrderSlip $orderSlip */ + $orderSlip = end($orderSlipCollection); + + if (!Validate::isLoadedObject($orderSlip)) { + return; + } + + $customer = new Customer((int) $order->id_customer); + $useTax = Group::getPriceDisplayMethod((int) $customer->id_default_group); + + if ($useTax) { + $amount = $orderSlip->total_products_tax_excl; + } else { + $amount = $orderSlip->total_products_tax_incl; + } + + if ($orderSlip->shipping_cost) { + if ($useTax) { + $amount += $orderSlip->total_shipping_tax_excl; + } else { + $amount += $orderSlip->total_shipping_tax_incl; + } + } + + $cartRuleTotal = 0; + + // Refund based on product prices, but do not refund the voucher amount + if ($orderSlip->order_slip_type == 1 && is_array($cartRules = $order->getCartRules())) { + foreach ($cartRules as $cartRule) { + if ($useTax) { + $cartRuleTotal -= $cartRule['value_tax_excl']; + } else { + $cartRuleTotal -= $cartRule['value']; + } + } + } + + $amount += $cartRuleTotal; + + if ($amount <= 0) { + return; + } + + $psCheckoutCartCollection = new PrestaShopCollection('PsCheckoutCart'); + $psCheckoutCartCollection->where('id_cart', '=', (int) $order->id_cart); + $psCheckoutCartCollection->where('paypal_status', 'in', [PsCheckoutCart::STATUS_COMPLETED, PsCheckoutCart::STATUS_PARTIALLY_COMPLETED]); + $psCheckoutCartCollection->orderBy('date_upd', 'ASC'); + + if (!$psCheckoutCartCollection->count()) { + return; + } + + /** @var PsCheckoutCart|bool $psCheckoutCart */ + $psCheckoutCart = $psCheckoutCartCollection->getFirst(); + + if (!$psCheckoutCart) { + return; + } + + /** @var \PrestaShop\Module\PrestashopCheckout\PayPal\PayPalOrderProvider $paypalOrderProvider */ + $paypalOrderProvider = $this->getService('ps_checkout.paypal.provider.order'); + + try { + $paypalOrder = $paypalOrderProvider->getById($psCheckoutCart->paypal_order); + } catch (Exception $exception) { + return; + } + + if (!isset($paypalOrder['purchase_units'][0]['payments']['captures'][0])) { + return; + } + + $capture = $paypalOrder['purchase_units'][0]['payments']['captures'][0]; + + $totalCaptured = (float) $capture['amount']['value']; + + $totalAlreadyRefund = 0; + + if (isset($paypalOrder['purchase_units'][0]['payments']['refunds'])) { + $totalAlreadyRefund = array_reduce($paypalOrder['purchase_units'][0]['payments']['refunds'], function ($totalRefunded, $refund) { + return $totalRefunded + (float) $refund['amount']['value']; + }); + } + + if ($totalCaptured < $amount + $totalAlreadyRefund) { + throw new \PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException(sprintf('Refund amount %s is greater than captured amount %s', $totalCaptured, $amount)); + } + + $currency = new Currency($params['order']->id_currency); + + /** @var \PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface $commandBus */ + $commandBus = $this->getService('ps_checkout.bus.command'); + $commandBus->handle(new \PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Command\RefundPayPalCaptureCommand( + $psCheckoutCart->getPaypalOrderId(), + $capture['id'], + $currency->iso_code, + sprintf('%01.2F', $amount) + )); + } catch (Exception $exception) { + // Do not break the Admin process if an exception is thrown + $this->getLogger()->error(__FUNCTION__, [ + 'exception' => $exception, + ]); + } + } } diff --git a/src/Order/Query/GetOrderForPaymentRefundedQueryResult.php b/src/Order/Query/GetOrderForPaymentRefundedQueryResult.php index b3cff68f1..a72e5322f 100644 --- a/src/Order/Query/GetOrderForPaymentRefundedQueryResult.php +++ b/src/Order/Query/GetOrderForPaymentRefundedQueryResult.php @@ -62,6 +62,11 @@ class GetOrderForPaymentRefundedQueryResult */ private $currencyId; + /** + * @var int[] + */ + private $orderStateIdHistory; + /** * @param int $orderId * @param int $currentStateId @@ -70,6 +75,7 @@ class GetOrderForPaymentRefundedQueryResult * @param string $totalAmount * @param string $totalRefund * @param int $currencyId + * @param int[] $orderStateIdHistory * * @throws OrderException * @throws OrderStateException @@ -81,7 +87,8 @@ public function __construct( $hasBeenTotallyRefund, $totalAmount, $totalRefund, - $currencyId + $currencyId, + array $orderStateIdHistory = [] ) { $this->orderId = new OrderId($orderId); $this->currentStateId = new OrderStateId($currentStateId); @@ -90,6 +97,7 @@ public function __construct( $this->totalAmount = $totalAmount; $this->totalRefund = $totalRefund; $this->currencyId = $currencyId; + $this->orderStateIdHistory = $orderStateIdHistory; } /** @@ -147,4 +155,12 @@ public function getCurrencyId() { return $this->currencyId; } + + /** + * @return int[] + */ + public function getOrderStateIdHistory() + { + return $this->orderStateIdHistory; + } } diff --git a/src/Order/QueryHandler/GetOrderForPaymentRefundedQueryHandler.php b/src/Order/QueryHandler/GetOrderForPaymentRefundedQueryHandler.php index 3ba5b28b5..6dd564748 100644 --- a/src/Order/QueryHandler/GetOrderForPaymentRefundedQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForPaymentRefundedQueryHandler.php @@ -28,6 +28,9 @@ use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQuery; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQueryResult; +use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; +use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; +use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; use PrestaShopCollection; use PrestaShopDatabaseException; @@ -42,9 +45,15 @@ class GetOrderForPaymentRefundedQueryHandler */ private $psCheckoutCartRepository; - public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) + /** + * @var OrderStateMapper + */ + private $orderStateMapper; + + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository, OrderStateMapper $orderStateMapper) { $this->psCheckoutCartRepository = $psCheckoutCartRepository; + $this->orderStateMapper = $orderStateMapper; } /** @@ -88,7 +97,8 @@ public function handle(GetOrderForPaymentRefundedQuery $query) $this->hasBeenTotallyRefunded($totalRefund, $order), (string) $order->getTotalPaid(), (string) $totalRefund, - (int) $order->id_currency + (int) $order->id_currency, + $this->getOrderStateHistory($order) ); } @@ -109,4 +119,39 @@ private function getTotalRefund(Order $order) return $refundAmount; } + + /** + * @param Order $order + * + * @return int[] + */ + private function getOrderStateHistory(Order $order) + { + $orderHistory = $order->getHistory($order->id_lang); + + if (!$orderHistory) { + return []; + } + + try { + $orderStateRefundedId = $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED); + $orderStatePartiallyRefundedId = $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED); + } catch (OrderStateException $exception) { + return []; + } + + $orderStateIdHistory = []; + + foreach ($orderHistory as $historyItem) { + $orderStateId = (int) $historyItem['id_order_state']; + + if ($orderStateId !== $orderStateRefundedId && $orderStateId !== $orderStatePartiallyRefundedId) { + continue; + } + + $orderStateIdHistory[] = $orderStateId; + } + + return $orderStateIdHistory; + } } diff --git a/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php b/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php index 19699c7d7..b4eb9127c 100644 --- a/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php +++ b/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php @@ -114,10 +114,6 @@ public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) $this->orderPayPalCache->delete($event->getPayPalOrderId()->getValue()); } - if (!$order->hasBeenPaid() || $order->hasBeenTotallyRefund()) { - return; - } - $orderPayPal = $this->orderProvider->getById($event->getPayPalOrderId()->getValue()); if (empty($orderPayPal['purchase_units'][0]['payments']['refunds'])) { @@ -129,11 +125,16 @@ public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) }); $orderFullyRefunded = (float) $order->getTotalAmount() <= (float) $totalRefunded; + $newOrderStateId = $this->orderStateMapper->getIdByKey($orderFullyRefunded ? OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED : OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED); + + if (in_array($newOrderStateId, $order->getOrderStateIdHistory())) { + return; + } $this->commandBus->handle( new UpdateOrderStatusCommand( $order->getOrderId()->getValue(), - $this->orderStateMapper->getIdByKey($orderFullyRefunded ? OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED : OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED) + $newOrderStateId ) ); } diff --git a/views/templates/hook/displayPDFOrderSlip.tpl b/views/templates/hook/displayPDFOrderSlip.tpl new file mode 100644 index 000000000..e69de29bb