Skip to content

Commit

Permalink
PISHPS-314: Added refund credit notes (#835)
Browse files Browse the repository at this point in the history
  • Loading branch information
m-muxfeld-diw authored Sep 13, 2024
1 parent 064e649 commit 1edeafe
Show file tree
Hide file tree
Showing 10 changed files with 574 additions and 4 deletions.
46 changes: 42 additions & 4 deletions src/Controller/Api/Order/RefundControllerBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Kiener\MolliePayments\Components\RefundManager\Request\RefundRequestItem;
use Kiener\MolliePayments\Exception\PaymentNotFoundException;
use Kiener\MolliePayments\Service\OrderService;
use Kiener\MolliePayments\Service\Refund\Exceptions\CreditNoteException;
use Kiener\MolliePayments\Service\Refund\RefundCreditNoteService;
use Kiener\MolliePayments\Service\Refund\RefundService;
use Kiener\MolliePayments\Traits\Api\ApiTrait;
use Psr\Log\LoggerInterface;
Expand Down Expand Up @@ -40,19 +42,30 @@ class RefundControllerBase extends AbstractController
*/
private $logger;

/**
* @var RefundCreditNoteService
*/
private $creditNoteService;


/**
* @param OrderService $orderService
* @param RefundManagerInterface $refundManager
* @param RefundService $refundService
* @param LoggerInterface $logger
*/
public function __construct(OrderService $orderService, RefundManagerInterface $refundManager, RefundService $refundService, LoggerInterface $logger)
{
public function __construct(
OrderService $orderService,
RefundManagerInterface $refundManager,
RefundService $refundService,
LoggerInterface $logger,
RefundCreditNoteService $creditNoteService
) {
$this->orderService = $orderService;
$this->refundManager = $refundManager;
$this->refundService = $refundService;
$this->logger = $logger;
$this->creditNoteService = $creditNoteService;
}


Expand Down Expand Up @@ -168,7 +181,7 @@ public function refundOrderID(RequestDataBag $data, Context $context): JsonRespo
$items = $itemsBag->all();
}

return $this->refundAction(
$response = $this->refundAction(
$orderId,
'',
$description,
Expand All @@ -177,6 +190,18 @@ public function refundOrderID(RequestDataBag $data, Context $context): JsonRespo
$items,
$context
);

if ($response->getStatusCode() === 200 && $response->getContent() !== false) {
$refundId = json_decode($response->getContent(), true)['refundId'];
try {
$this->creditNoteService->addCreditNoteToOrder($orderId, $refundId, $items, $context);
} catch (CreditNoteException $exception) {
$this->logger->error($exception->getMessage(), ['code' => $exception->getCode(),]);
return $this->buildErrorResponse($exception->getMessage());
}
}

return $response;
}

/**
Expand All @@ -187,7 +212,20 @@ public function refundOrderID(RequestDataBag $data, Context $context): JsonRespo
*/
public function cancel(RequestDataBag $data, Context $context): JsonResponse
{
return $this->cancelRefundAction($data->getAlnum('orderId'), $data->get('refundId'), $context);
$orderId = $data->getAlnum('orderId');
$refundId = $data->get('refundId');
$response = $this->cancelRefundAction($orderId, $refundId, $context);

if ($response->getStatusCode() === 200) {
try {
$this->creditNoteService->cancelCreditNoteToOrder($orderId, $refundId, $context);
} catch (CreditNoteException $exception) {
$this->logger->error($exception->getMessage(), ['code' => $exception->getCode(),]);
return $this->buildErrorResponse($exception->getMessage());
}
}

return $response;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ Component.override('sw-order-line-items-grid', {
*/
onCloseRefundManager() {
this.showRefundModal = false;
location.reload();
},

//==== Shipping =============================================================================================//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ Component.override('sw-order-detail-general', {
*/
onCloseRefundManager() {
this.showRefundModal = false;
location.reload();
},

/**
Expand Down
30 changes: 30 additions & 0 deletions src/Resources/config/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,36 @@
<helpText lang="de-DE">Diese Funktion kann benutzt werden, um die Anleitungen auszublenden, sofern man einmal mit dem Refund Manager vetraut ist.</helpText>
<helpText lang="nl-NL">Toont of verbergt de handleiding. Als het storend is, kan je het direkt deactiveren als je eenmaal vertrouwd bent met de Refund Manager.</helpText>
</input-field>
<input-field type="bool">
<name>refundManagerCreateCreditNotes</name>
<defaultValue>true</defaultValue>
<label>Create Credit Notes</label>
<label lang="de-DE">Gutschriften erstellen</label>
<label lang="nl-NL">Creditnota's maken</label>
<helpText>Automatically create a credit note in Shopware when a refund is processed.</helpText>
<helpText lang="de-DE">Erstellt automatisch eine Gutschrift in Shopware, wenn eine Rückerstattung verarbeitet wird.</helpText>
<helpText lang="nl-NL">Maak automatisch een creditnota in Shopware wanneer een terugbetaling wordt verwerkt.</helpText>
</input-field>
<input-field type="text">
<name>refundManagerCreateCreditNotesPrefix</name>
<defaultValue>Refund: </defaultValue>
<label>Credit Note Prefix</label>
<label lang="de-DE">Gutschrift Präfix</label>
<label lang="nl-NL">Creditnota voorvoegsel</label>
<helpText>Prefix for the credit note number. Credit note consist of prefix, original line item name and suffix</helpText>
<helpText lang="de-DE">Präfix für die Gutschriftsnummer. Die Gutschrift besteht aus Präfix, ursprünglichem Positionsnamen und Suffix</helpText>
<helpText lang="nl-NL">Voorvoegsel voor het creditnotanummer. De creditnota bestaat uit een voorvoegsel, de oorspronkelijke naam van het line item en een achtervoegsel.</helpText>
</input-field>
<input-field type="text">
<name>refundManagerCreateCreditNotesSuffix</name>
<defaultValue />
<label>Credit Note Suffix</label>
<label lang="de-DE">Gutschrift Suffix</label>
<label lang="nl-NL">Creditnota achtervoegsel</label>
<helpText>Suffix for the credit note number. Credit note consist of prefix, original line item name and suffix</helpText>
<helpText lang="de-DE">Suffix für die Gutschriftsnummer. Die Gutschrift besteht aus Präfix, ursprünglichem Positionsnamen und Suffix</helpText>
<helpText lang="nl-NL">Achtervoegsel voor het creditnotanummer. De creditnota bestaat uit een voorvoegsel, de oorspronkelijke naam van het line item en een achtervoegsel.</helpText>
</input-field>
</card>
<card>
<title>Order State Automation</title>
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/config/services/controller.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
<argument type="service" id="Kiener\MolliePayments\Components\RefundManager\RefundManager"/>
<argument type="service" id="Kiener\MolliePayments\Service\Refund\RefundService"/>
<argument type="service" id="mollie_payments.logger"/>
<argument type="service" id="Kiener\MolliePayments\Service\Refund\RefundCreditNoteService"/>

<call method="setContainer">
<argument type="service" id="service_container"/>
</call>
Expand Down
8 changes: 8 additions & 0 deletions src/Resources/config/services/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@
<argument type="service" id="mollie_payments.logger"/>
</service>

<service id="Kiener\MolliePayments\Service\Refund\RefundCreditNoteService">
<argument type="service" id="order.repository"/>
<argument type="service" id="order_line_item.repository"/>
<argument type="service" id="Kiener\MolliePayments\Service\SettingsService"/>
<argument type="service" id="mollie_payments.logger"/>
</service>


<service id="Kiener\MolliePayments\Service\Refund\RefundService">
<argument type="service" id="Kiener\MolliePayments\Service\MollieApi\Order"/>
<argument type="service" id="Kiener\MolliePayments\Service\OrderService"/>
Expand Down
24 changes: 24 additions & 0 deletions src/Service/Refund/Exceptions/CreditNoteException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);

namespace Kiener\MolliePayments\Service\Refund\Exceptions;

class CreditNoteException extends \Exception
{
public const CODE_ADDING_CREDIT_NOTE_LINE_ITEMS = 1;
public const CODE_REMOVING_CREDIT_NOTE_LINE_ITEMS = 2;
final private function __construct(string $message, int $code)
{
parent::__construct($message, $code);
}

public static function forAddingLineItems(string $message): CreditNoteException
{
return new self($message, self::CODE_ADDING_CREDIT_NOTE_LINE_ITEMS);
}

public static function forRemovingLineItems(string $message): CreditNoteException
{
return new self($message, self::CODE_REMOVING_CREDIT_NOTE_LINE_ITEMS);
}
}
178 changes: 178 additions & 0 deletions src/Service/Refund/RefundCreditNoteService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<?php
declare(strict_types=1);

namespace Kiener\MolliePayments\Service\Refund;

use Kiener\MolliePayments\Service\Refund\Exceptions\CreditNoteException;
use Kiener\MolliePayments\Service\SettingsService;
use Psr\Log\LoggerInterface;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice;
use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition;
use Shopware\Core\Checkout\Cart\Tax\Struct\CalculatedTaxCollection;
use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Uuid\Uuid;

class RefundCreditNoteService
{
/**
* @var EntityRepository
*/
private $orderRepository;

/**
* @var EntityRepository
*/
private $orderLineItemRepository;

/**
* @var bool
*/
private $enabled;

/**
* @var string
*/
private $prefix;

/**
* @var string
*/
private $suffix;

/**
* @var LoggerInterface
*/
private $logger;

public function __construct(
EntityRepository $orderRepository,
EntityRepository $orderLineItemRepository,
SettingsService $settingsService,
LoggerInterface $logger
) {
$this->orderRepository = $orderRepository;
$this->orderLineItemRepository = $orderLineItemRepository;
$settings = $settingsService->getSettings();
$this->enabled = $settings->isRefundManagerCreateCreditNotesEnabled();
$this->prefix = $settings->getRefundManagerCreateCreditNotesPrefix();
$this->suffix = $settings->getRefundManagerCreateCreditNotesSuffix();
$this->logger = $logger;
}

/**
* @param array<int|string, array{id: string}> $lineItems
* @throws CreditNoteException
*/
public function addCreditNoteToOrder(string $orderId, string $refundId, array $lineItems, Context $context): void
{
if (!$this->enabled) {
$this->logger->debug('Credit note creation is disabled');
return;
}

if (empty($orderId) || empty($refundId)) {
throw CreditNoteException::forAddingLineItems(sprintf('OrderId or RefundId is empty. OrderID: %s RefundID: %s', $orderId, $refundId));
}

if (empty($lineItems)) {
throw CreditNoteException::forAddingLineItems(sprintf('No line items found for credit note. OrderID: %s RefundID: %s', $orderId, $refundId));
}

$data = ['id' => $orderId, 'lineItems' => []];

foreach ($lineItems as ['id' => $lineItemId]) {
$lineItem = $this->orderLineItemRepository->search(new Criteria([$lineItemId]), $context)->first();
if (!$lineItem instanceof OrderLineItemEntity) {
continue;
}
$price = $lineItem->getPrice();
if (!$price instanceof CalculatedPrice) {
continue;
}
$taxRules = $price->getTaxRules();
$totalPrice = $lineItem->getTotalPrice();
$quantity = $lineItem->getQuantity();
if ($totalPrice <= 0 || $quantity <= 0) {
continue;
}
$unitPrice = round($totalPrice / $quantity, 2);
$totalPrice *= -1;
$unitPrice *= -1;
$data['lineItems'][] = [
'id' => Uuid::fromStringToHex($lineItemId),
'identifier' => Uuid::fromStringToHex($lineItem->getIdentifier()),
'quantity' => $quantity,
'label' => sprintf('%s%s%s', $this->prefix, $lineItem->getLabel(), $this->suffix),
'type' => LineItem::CREDIT_LINE_ITEM_TYPE,
'price' => new CalculatedPrice($unitPrice, $totalPrice, new CalculatedTaxCollection(), $taxRules),
'priceDefinition' => new QuantityPriceDefinition($totalPrice, $taxRules, $quantity),
'customFields' => [
'mollie_payments' => [
'type' => 'refund',
'refundId' => $refundId,
'lineItemId' => $lineItemId
],
],
];
}

if (empty($data['lineItems'])) {
throw CreditNoteException::forAddingLineItems(sprintf('No credit note line items found for order. OrderID: %s RefundID: %s', $orderId, $refundId));
}

$this->logger->debug('Adding credit note to order', ['orderId' => $orderId, 'refundId' => $refundId, 'lineItems' => $data['lineItems']]);
$this->orderRepository->upsert([$data], $context);
}

/**
* @throws CreditNoteException
*/
public function cancelCreditNoteToOrder(string $orderId, string $refundId, Context $context): void
{
if (empty($orderId) || empty($refundId)) {
throw CreditNoteException::forRemovingLineItems(sprintf('OrderId or RefundId is empty. OrderID: %s RefundID: %s', $orderId, $refundId));
}

$criteria = new Criteria([$orderId]);
$criteria->addAssociation('lineItems');
$searchResult = $this->orderRepository->search($criteria, $context);
$order = $searchResult->first();

if (!$order instanceof OrderEntity) {
throw CreditNoteException::forRemovingLineItems(sprintf('Order not found. OrderID: %s RefundID: %s', $orderId, $refundId));
}

$lineItems = $order->getLineItems();

if ($lineItems === null) {
throw CreditNoteException::forRemovingLineItems(sprintf('No line items found for order. OrderID: %s RefundID: %s', $orderId, $refundId));
}

$ids = [];
foreach ($lineItems as $lineItem) {
/** @var OrderLineItemEntity $lineItem */
$customFields = $lineItem->getCustomFields();
if (!isset($customFields['mollie_payments'], $customFields['mollie_payments']['type']) || $customFields['mollie_payments']['type'] !== 'refund') {
continue;
}

$lineItemRefundId = $customFields['mollie_payments']['refundId'];
if ($lineItemRefundId !== $refundId) {
continue;
}

$ids[] = ['id' => $lineItem->getId()];
}

if (empty($ids)) {
throw CreditNoteException::forRemovingLineItems(sprintf('No credit note line items found for order. OrderID: %s RefundID: %s', $orderId, $refundId));
}

$this->orderLineItemRepository->delete($ids, $context);
}
}
Loading

0 comments on commit 1edeafe

Please sign in to comment.