Skip to content

Commit

Permalink
Add support for redirect-based payments in API (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubtobiasz authored Oct 7, 2024
2 parents f727972 + 88d3d50 commit 4901882
Show file tree
Hide file tree
Showing 20 changed files with 467 additions and 30 deletions.
6 changes: 6 additions & 0 deletions config/services/api/command.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use CommerceWeavers\SyliusTpayPlugin\Api\Command\AbstractPayByHandler;
use CommerceWeavers\SyliusTpayPlugin\Api\Command\PayByBlikHandler;
use CommerceWeavers\SyliusTpayPlugin\Api\Command\PayByCardHandler;
use CommerceWeavers\SyliusTpayPlugin\Api\Command\PayByRedirectHandler;
use CommerceWeavers\SyliusTpayPlugin\Api\Command\PayHandler;

return function(ContainerConfigurator $container): void {
Expand Down Expand Up @@ -39,4 +40,9 @@
->parent('commerce_weavers_tpay.api.command.abstract_pay_by_handler')
->tag('messenger.message_handler')
;

$services->set('commerce_weavers_tpay.api.command.pay_by_redirect_handler', PayByRedirectHandler::class)
->parent('commerce_weavers_tpay.api.command.abstract_pay_by_handler')
->tag('messenger.message_handler')
;
};
8 changes: 8 additions & 0 deletions config/services/api/factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use CommerceWeavers\SyliusTpayPlugin\Api\Factory\NextCommand\PayByBlikFactory;
use CommerceWeavers\SyliusTpayPlugin\Api\Factory\NextCommand\PayByCardFactory;
use CommerceWeavers\SyliusTpayPlugin\Api\Factory\NextCommand\PayByRedirectFactory;
use CommerceWeavers\SyliusTpayPlugin\Api\Factory\NextCommandFactory;
use CommerceWeavers\SyliusTpayPlugin\Api\Factory\NextCommandFactoryInterface;

Expand All @@ -26,4 +27,11 @@
$services->set('commerce_weavers_tpay.api.factory.next_command.pay_by_card', PayByCardFactory::class)
->tag('commerce_weavers_tpay.api.factory.next_command')
;

$services->set('commerce_weavers_tpay.api.factory.next_command.pay_by_redirect', PayByRedirectFactory::class)
->args([
service('payum.dynamic_gateways.cypher'),
])
->tag('commerce_weavers_tpay.api.factory.next_command')
;
};
13 changes: 13 additions & 0 deletions src/Api/Command/PayByRedirect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Api\Command;

final class PayByRedirect
{
public function __construct(
public readonly int $paymentId,
) {
}
}
36 changes: 36 additions & 0 deletions src/Api/Command/PayByRedirectHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Api\Command;

use CommerceWeavers\SyliusTpayPlugin\Model\PaymentDetails;
use Sylius\Component\Core\Model\PaymentInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Webmozart\Assert\Assert;

#[AsMessageHandler]
final class PayByRedirectHandler extends AbstractPayByHandler
{
public function __invoke(PayByRedirect $command): PayResult
{
$payment = $this->findOr404($command->paymentId);

$this->createTransaction($payment);

return $this->createResultFrom($payment);
}

private function createResultFrom(PaymentInterface $payment): PayResult
{
$paymentDetails = PaymentDetails::fromArray($payment->getDetails());

Assert::notNull($paymentDetails->getStatus(), 'Payment status is required to create a result.');
Assert::notNull($paymentDetails->getPaymentUrl(), 'Payment URL is required to create a result.');

return new PayResult(
$paymentDetails->getStatus(),
$paymentDetails->getPaymentUrl(),
);
}
}
15 changes: 6 additions & 9 deletions src/Api/Command/PayHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace CommerceWeavers\SyliusTpayPlugin\Api\Command;

use CommerceWeavers\SyliusTpayPlugin\Api\Factory\NextCommandFactoryInterface;
use CommerceWeavers\SyliusTpayPlugin\Model\PaymentDetails;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
Expand Down Expand Up @@ -41,7 +42,11 @@ public function __invoke(Pay $command): PayResult
throw new NotFoundHttpException(sprintf('Order with token "%s" does not have a new payment.', $command->orderToken));
}

$this->setPaymentDetails($lastPayment, ['successUrl' => $command->successUrl, 'failureUrl' => $command->failureUrl]);
$paymentDetails = PaymentDetails::fromArray($lastPayment->getDetails());
$paymentDetails->setSuccessUrl($command->successUrl);
$paymentDetails->setFailureUrl($command->failureUrl);

$lastPayment->setDetails($paymentDetails->toArray());

$nextCommand = $this->nextCommandFactory->create($command, $lastPayment);

Expand All @@ -53,12 +58,4 @@ public function __invoke(Pay $command): PayResult

return $nextCommandResult;
}

private function setPaymentDetails(PaymentInterface $payment, array $details): void
{
$payment->setDetails(array_merge(
$payment->getDetails(),
$details,
));
}
}
64 changes: 64 additions & 0 deletions src/Api/Factory/NextCommand/PayByRedirectFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Api\Factory\NextCommand;

use CommerceWeavers\SyliusTpayPlugin\Api\Command\Pay;
use CommerceWeavers\SyliusTpayPlugin\Api\Command\PayByRedirect;
use CommerceWeavers\SyliusTpayPlugin\Api\Factory\Exception\UnsupportedNextCommandFactory;
use CommerceWeavers\SyliusTpayPlugin\Api\Factory\NextCommandFactoryInterface;
use Payum\Core\Model\GatewayConfigInterface;
use Payum\Core\Security\CryptedInterface;
use Payum\Core\Security\CypherInterface;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\Component\Core\Model\PaymentMethodInterface;

final class PayByRedirectFactory implements NextCommandFactoryInterface
{
public function __construct(
private readonly CypherInterface $cypher,
) {
}

public function create(Pay $command, PaymentInterface $payment): PayByRedirect
{
if (!$this->supports($command, $payment)) {
throw new UnsupportedNextCommandFactory('This factory does not support the given command.');
}

/** @var int $paymentId */
$paymentId = $payment->getId();

return new PayByRedirect($paymentId);
}

public function supports(Pay $command, PaymentInterface $payment): bool
{
if ($payment->getId() === null) {
return false;
}

$gatewayConfig = $this->getGatewayConfig($payment);

if (null === $gatewayConfig) {
return false;
}

if ($gatewayConfig instanceof CryptedInterface) {
$gatewayConfig->decrypt($this->cypher);
}

$config = $gatewayConfig->getConfig();

return isset($config['type']) && $config['type'] === 'redirect';
}

private function getGatewayConfig(PaymentInterface $payment): ?GatewayConfigInterface
{
/** @var PaymentMethodInterface|null $paymentMethod */
$paymentMethod = $payment->getMethod();

return $paymentMethod?->getGatewayConfig();
}
}
26 changes: 26 additions & 0 deletions src/Model/PaymentDetails.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public function __construct(
#[\SensitiveParameter]
private ?string $encodedCardData = null,
private ?string $paymentUrl = null,
private ?string $successUrl = null,
private ?string $failureUrl = null,
) {
}

Expand Down Expand Up @@ -78,6 +80,26 @@ public function setPaymentUrl(?string $paymentUrl): void
$this->paymentUrl = $paymentUrl;
}

public function getSuccessUrl(): ?string
{
return $this->successUrl;
}

public function setSuccessUrl(?string $successUrl): void
{
$this->successUrl = $successUrl;
}

public function getFailureUrl(): ?string
{
return $this->failureUrl;
}

public function setFailureUrl(?string $failureUrl): void
{
$this->failureUrl = $failureUrl;
}

public function clearSensitiveData(): void
{
$this->blikToken = null;
Expand All @@ -93,6 +115,8 @@ public static function fromArray(array $details): self
$details['tpay']['blik_token'] ?? null,
$details['tpay']['card'] ?? null,
$details['tpay']['payment_url'] ?? null,
$details['tpay']['success_url'] ?? null,
$details['tpay']['failure_url'] ?? null,
);
}

Expand All @@ -106,6 +130,8 @@ public function toArray(): array
'blik_token' => $this->blikToken,
'card' => $this->encodedCardData,
'payment_url' => $this->paymentUrl,
'success_url' => $this->successUrl,
'failure_url' => $this->failureUrl,
],
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public function execute($request): void
$this->createRedirectBasedPaymentPayloadFactory->createFrom($model, $notifyToken->getTargetUrl(), $localeCode),
);

$paymentDetails->setStatus($response['status']);
$paymentDetails->setTransactionId($response['transactionId']);
$paymentDetails->setPaymentUrl($response['transactionPaymentUrl']);

Expand Down
37 changes: 20 additions & 17 deletions src/Tpay/Factory/CreateRedirectBasedPaymentPayloadFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,18 @@

namespace CommerceWeavers\SyliusTpayPlugin\Tpay\Factory;

use CommerceWeavers\SyliusTpayPlugin\Model\PaymentDetails;
use Sylius\Component\Core\Model\PaymentInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Webmozart\Assert\Assert;

final class CreateRedirectBasedPaymentPayloadFactory implements CreateRedirectBasedPaymentPayloadFactoryInterface
{
private const TPAY_FIELD = 'tpay';

private const SUCCESS_URL_FIELD = 'successUrl';

private const FAILURE_URL_FIELD = 'failureUrl';

public function __construct(
private RouterInterface $router,
private string $successRoute,
private string $errorRoute,
private readonly RouterInterface $router,
private readonly string $successRoute,
private readonly string $errorRoute,
) {
}

Expand Down Expand Up @@ -59,23 +54,31 @@ public function createFrom(PaymentInterface $payment, string $notifyUrl, string

private function getSuccessUrl(PaymentInterface $payment, string $localeCode): string
{
$paymentDetails = $payment->getDetails();
$paymentDetails = PaymentDetails::fromArray($payment->getDetails());

if (!isset($paymentDetails[self::TPAY_FIELD][self::SUCCESS_URL_FIELD])) {
return $this->router->generate($this->successRoute, ['_locale' => $localeCode], UrlGeneratorInterface::ABSOLUTE_URL);
if ($paymentDetails->getSuccessUrl() === null) {
return $this->router->generate(
$this->successRoute,
['_locale' => $localeCode],
UrlGeneratorInterface::ABSOLUTE_URL,
);
}

return $paymentDetails[self::TPAY_FIELD][self::SUCCESS_URL_FIELD];
return $paymentDetails->getSuccessUrl();
}

private function getFailureUrl(PaymentInterface $payment, string $localeCode): string
{
$paymentDetails = $payment->getDetails();
$paymentDetails = PaymentDetails::fromArray($payment->getDetails());

if (!isset($paymentDetails[self::TPAY_FIELD][self::FAILURE_URL_FIELD])) {
return $this->router->generate($this->errorRoute, ['_locale' => $localeCode], UrlGeneratorInterface::ABSOLUTE_URL);
if ($paymentDetails->getFailureUrl() === null) {
return $this->router->generate(
$this->errorRoute,
['_locale' => $localeCode],
UrlGeneratorInterface::ABSOLUTE_URL,
);
}

return $paymentDetails[self::TPAY_FIELD][self::FAILURE_URL_FIELD];
return $paymentDetails->getFailureUrl();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"@context": {
"@vocab": "http://localhost/api/v2/docs.jsonld#",
"hydra": "http://www.w3.org/ns/hydra/core#",
"status": "PayResult/status",
"transactionPaymentUrl": "PayResult/transactionPaymentUrl"
},
"@type": "PayResult",
"@id": "/api/v2/.well-known/genid/@string@",
"status": "pending",
"transactionPaymentUrl": "@string@"
}
Loading

0 comments on commit 4901882

Please sign in to comment.