Skip to content

Commit

Permalink
Add support for partial refunds
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubtobiasz committed Oct 29, 2024
1 parent efd905d commit 387efde
Show file tree
Hide file tree
Showing 17 changed files with 370 additions and 29 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"polishsymfonycommunity/symfony-mocker-container": "^1.0",
"shipmonk/composer-dependency-analyser": "^1.7",
"sylius-labs/coding-standard": "^4.2",
"sylius/refund-plugin": "^1.5",
"sylius/sylius": "^1.12",
"symfony/browser-kit": "^5.4 || ^6.0",
"symfony/debug-bundle": "^5.4 || ^6.0",
Expand Down
11 changes: 11 additions & 0 deletions config/config/winzou_state_machine.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,16 @@
],
],
],
'sylius_refund_refund_payment' => [
'callbacks' => [
'before' => [
'tpay_refund_payment' => [
'on' => ['complete'],
'do' => ['@commerce_weavers_sylius_tpay.refunding.dispatcher.refund', 'dispatch'],
'args' => ['object'],
],
]
]
],
]);
};
5 changes: 5 additions & 0 deletions config/services/payum/action.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use CommerceWeavers\SyliusTpayPlugin\Payum\Action\Api\PayWithCardAction;
use CommerceWeavers\SyliusTpayPlugin\Payum\Action\CaptureAction;
use CommerceWeavers\SyliusTpayPlugin\Payum\Action\GetStatusAction;
use CommerceWeavers\SyliusTpayPlugin\Payum\Action\PartialRefundAction;
use CommerceWeavers\SyliusTpayPlugin\Payum\Action\RefundAction;
use CommerceWeavers\SyliusTpayPlugin\Payum\Action\ResolveNextRouteAction;
use CommerceWeavers\SyliusTpayPlugin\Payum\Factory\TpayGatewayFactory;
Expand Down Expand Up @@ -99,6 +100,10 @@
->tag('payum.action', ['factory' => TpayGatewayFactory::NAME, 'alias' => 'cw.tpay.get_status'])
;

$services->set(PartialRefundAction::class)
->tag('payum.action', ['factory' => TpayGatewayFactory::NAME, 'alias' => 'cw.tpay.partial_refund'])
;

$services->set(RefundAction::class)
->tag('payum.action', ['factory' => TpayGatewayFactory::NAME, 'alias' => 'cw.tpay.refund'])
;
Expand Down
9 changes: 8 additions & 1 deletion config/services/refunding.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use CommerceWeavers\SyliusTpayPlugin\Refunding\Checker\RefundPluginAvailabilityCheckerInterface;
use CommerceWeavers\SyliusTpayPlugin\Refunding\Dispatcher\RefundDispatcher;
use CommerceWeavers\SyliusTpayPlugin\Refunding\Dispatcher\RefundDispatcherInterface;
use CommerceWeavers\SyliusTpayPlugin\Refunding\Workflow\Listener\DispatchRefundListener;
Expand All @@ -12,10 +13,15 @@
return function(ContainerConfigurator $container): void {
$services = $container->services();

$services->set('commerce_weavers_sylius_tpay.refunding.checker.refund_plugin_availability', RefundPluginAvailabilityCheckerInterface::class)
->alias(RefundPluginAvailabilityCheckerInterface::class, 'commerce_weavers_sylius_tpay.refunding.checker.refund_plugin_availability')
;

$services->set('commerce_weavers_sylius_tpay.refunding.dispatcher.refund', RefundDispatcher::class)
->public()
->args([
service('payum'),
service('commerce_weavers_sylius_tpay.gateway'),
service('commerce_weavers_sylius_tpay.refunding.checker.refund_plugin_availability'),
])
->alias(RefundDispatcherInterface::class, 'commerce_weavers_sylius_tpay.refunding.dispatcher.refund')
;
Expand All @@ -26,6 +32,7 @@
service('commerce_weavers_sylius_tpay.refunding.dispatcher.refund'),
])
->tag('kernel.event_listener', ['event' => 'workflow.sylius_payment.transition.refund'])
->tag('kernel.event_listener', ['event' => 'workflow.sylius_refund_refund_payment.transition.complete'])
;
}
};
7 changes: 7 additions & 0 deletions src/CommerceWeaversSyliusTpayPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@

namespace CommerceWeavers\SyliusTpayPlugin;

use CommerceWeavers\SyliusTpayPlugin\DependencyInjection\CompilerPass\AddSupportedRefundPaymentMethodPass;
use Sylius\Bundle\CoreBundle\Application\SyliusPluginTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

final class CommerceWeaversSyliusTpayPlugin extends Bundle
{
use SyliusPluginTrait;

public function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new AddSupportedRefundPaymentMethodPass());
}

public function getPath(): string
{
if (!isset($this->path)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\DependencyInjection\CompilerPass;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class AddSupportedRefundPaymentMethodPass implements CompilerPassInterface
{
private const SUPPORTED_GATEWAYS_PARAM_NAME = 'sylius_refund.supported_gateways';

public function process(ContainerBuilder $container): void
{
if (!$container->hasParameter(self::SUPPORTED_GATEWAYS_PARAM_NAME)) {
return;
}

/** @var array<string, mixed> $supportedGateways */
$supportedGateways = $container->getParameter(self::SUPPORTED_GATEWAYS_PARAM_NAME);
$supportedGateways[] = 'tpay';

$container->setParameter(self::SUPPORTED_GATEWAYS_PARAM_NAME, $supportedGateways);
}
}
67 changes: 67 additions & 0 deletions src/Payum/Action/PartialRefundAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Payum\Action;

use CommerceWeavers\SyliusTpayPlugin\Model\PaymentDetails;
use CommerceWeavers\SyliusTpayPlugin\Payum\Action\Api\BaseApiAwareAction;
use CommerceWeavers\SyliusTpayPlugin\Payum\Exception\RefundCannotBeMadeException;
use Payum\Core\GatewayAwareInterface;
use Payum\Core\GatewayAwareTrait;
use Payum\Core\Request\Refund;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\RefundPlugin\Entity\RefundPaymentInterface;
use Webmozart\Assert\Assert;

final class PartialRefundAction extends BaseApiAwareAction implements GatewayAwareInterface
{
use GatewayAwareTrait;

/**
* @param Refund $request
*/
public function execute(mixed $request): void
{
/** @var RefundPaymentInterface $refundPayment */
$refundPayment = $request->getModel();
$payment = $this->extractPaymentFrom($refundPayment);
$paymentDetails = PaymentDetails::fromArray($payment->getDetails());
$transactionId = $paymentDetails->getTransactionId();

if (null === $transactionId) {
throw new RefundCannotBeMadeException('Tpay transaction id cannot be found.');
}

if ($refundPayment->getAmount() === $payment->getAmount()) {
$this->gateway->execute(new Refund($payment));

return;
}

$this->api->transactions()->createRefundByTransactionId(
['amount' => $this->convertFromMinorToMajorCurrency($refundPayment->getAmount())],
$transactionId,
);
}

public function supports(mixed $request): bool
{
return $request instanceof Refund && $request->getModel() instanceof RefundPaymentInterface;
}

private function extractPaymentFrom(RefundPaymentInterface $refundPayment): PaymentInterface
{
$order = $refundPayment->getOrder();
$payment = $order->getLastPayment();

Assert::notNull($payment);

return $payment;
}

private function convertFromMinorToMajorCurrency(int $amount): float
{
return $amount / 100;
}
}
15 changes: 15 additions & 0 deletions src/Refunding/Checker/RefundPluginAvailabilityChecker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Refunding\Checker;

use Sylius\RefundPlugin\Entity\RefundPaymentInterface;

final class RefundPluginAvailabilityChecker implements RefundPluginAvailabilityCheckerInterface
{
public function isAvailable(): bool
{
return interface_exists(RefundPaymentInterface::class);
}
}
10 changes: 10 additions & 0 deletions src/Refunding/Checker/RefundPluginAvailabilityCheckerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Refunding\Checker;

interface RefundPluginAvailabilityCheckerInterface
{
public function isAvailable(): bool;
}
29 changes: 19 additions & 10 deletions src/Refunding/Dispatcher/RefundDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,35 @@

namespace CommerceWeavers\SyliusTpayPlugin\Refunding\Dispatcher;

use Payum\Core\Payum;
use CommerceWeavers\SyliusTpayPlugin\Refunding\Checker\RefundPluginAvailabilityCheckerInterface;
use Payum\Core\GatewayInterface;
use Payum\Core\Request\Refund;
use Sylius\Bundle\PayumBundle\Model\GatewayConfigInterface;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\Component\Core\Model\PaymentMethodInterface;
use Sylius\RefundPlugin\Entity\RefundPaymentInterface;

final class RefundDispatcher implements RefundDispatcherInterface
{
public function __construct(
private Payum $payum,
private readonly GatewayInterface $gateway,
private readonly RefundPluginAvailabilityCheckerInterface $refundPluginAvailabilityChecker,
) {
}

public function dispatch(PaymentInterface $payment): void
public function dispatch(PaymentInterface|RefundPaymentInterface $payment): void
{
/** @var PaymentMethodInterface $paymentMethod */
$paymentMethod = $payment->getMethod();
/** @var GatewayConfigInterface $gatewayConfig */
$gatewayConfig = $paymentMethod->getGatewayConfig();
if (!$this->checkIfShouldBeDispatched($payment)) {
return;
}

$this->payum->getGateway($gatewayConfig->getGatewayName())->execute(new Refund($payment));
$this->gateway->execute(new Refund($payment));
}

private function checkIfShouldBeDispatched(PaymentInterface|RefundPaymentInterface $payment): bool
{
$isRefundPaymentAvailable = $this->refundPluginAvailabilityChecker->isAvailable();
$isPayment = $payment instanceof PaymentInterface;
$isRefundPayment = $payment instanceof RefundPaymentInterface;

return (!$isRefundPaymentAvailable && $isPayment) || ($isRefundPaymentAvailable && $isRefundPayment);
}
}
3 changes: 2 additions & 1 deletion src/Refunding/Dispatcher/RefundDispatcherInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
namespace CommerceWeavers\SyliusTpayPlugin\Refunding\Dispatcher;

use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\RefundPlugin\Entity\RefundPaymentInterface;

interface RefundDispatcherInterface
{
public function dispatch(PaymentInterface $payment): void;
public function dispatch(PaymentInterface|RefundPaymentInterface $payment): void;
}
18 changes: 18 additions & 0 deletions symfony.lock
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,15 @@
"knplabs/knp-menu-bundle": {
"version": "v3.4.2"
},
"knplabs/knp-snappy-bundle": {
"version": "1.10",
"recipe": {
"repo": "github.com/symfony/recipes-contrib",
"branch": "main",
"version": "1.5",
"ref": "c81bdcf4a9d4e7b1959071457f9608631865d381"
}
},
"laminas/laminas-code": {
"version": "4.14.0"
},
Expand Down Expand Up @@ -592,6 +601,15 @@
"sylius/mailer-bundle": {
"version": "v2.0.0"
},
"sylius/refund-plugin": {
"version": "1.5",
"recipe": {
"repo": "github.com/symfony/recipes-contrib",
"branch": "main",
"version": "0.4",
"ref": "a3f813f608c6f04bd7d0b4cefd73a96bf378c390"
}
},
"sylius/registry": {
"version": "v1.6.0"
},
Expand Down
2 changes: 2 additions & 0 deletions tests/Application/config/bundles.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
League\FlysystemBundle\FlysystemBundle::class => ['all' => true],
Nelmio\Alice\Bridge\Symfony\NelmioAliceBundle::class => ['all' => true],
Fidry\AliceDataFixtures\Bridge\Symfony\FidryAliceDataFixturesBundle::class => ['all' => true],
Knp\Bundle\SnappyBundle\KnpSnappyBundle::class => ['all' => true],
Sylius\RefundPlugin\SyliusRefundPlugin::class => ['all' => true],
];

if (SyliusCoreBundle::VERSION_ID >= 11300) {
Expand Down
2 changes: 2 additions & 0 deletions tests/Application/config/packages/sylius_refund.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
imports:
- { resource: "@SyliusRefundPlugin/Resources/config/app/config.yml" }
2 changes: 2 additions & 0 deletions tests/Application/config/routes/sylius_refund.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
sylius_refund:
resource: "@SyliusRefundPlugin/Resources/config/routing.yml"
Loading

0 comments on commit 387efde

Please sign in to comment.