Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Blik Level 0 payments #14

Merged
merged 11 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions config/config/events.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->extension('sylius_ui', [
'events' => [
'sylius.shop.checkout.complete.summary' => [
'blocks' => [
'blik' => [
'template' => '@CommerceWeaversSyliusTpayPlugin/blik.html.twig',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Templates like that should be rather placed in some subfolder. Shop prefix should be placed at least

'priority' => 5,
],
],
],
],
]);
};
8 changes: 8 additions & 0 deletions config/services/form.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use CommerceWeavers\SyliusTpayPlugin\Form\EventListener\PreventSavingEmptyClientSecretListener;
use CommerceWeavers\SyliusTpayPlugin\Form\Extension\CompleteTypeExtension;
use CommerceWeavers\SyliusTpayPlugin\Form\Type\TpayGatewayConfigurationType;
use CommerceWeavers\SyliusTpayPlugin\Payum\Factory\TpayGatewayFactory;

Expand All @@ -23,4 +24,11 @@
;

$services->set(PreventSavingEmptyClientSecretListener::class);

$services->set(CompleteTypeExtension::class)
->tag(
'form.type_extension',
['extended_type' => CompleteType::class]
)
;
arti0090 marked this conversation as resolved.
Show resolved Hide resolved
};
12 changes: 12 additions & 0 deletions config/services/payum/action.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\Payum\Action\Api\CreateBlik0TransactionAction;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's adjust naming to @coldic3 suggestion

Suggested change
use CommerceWeavers\SyliusTpayPlugin\Payum\Action\Api\CreateBlik0TransactionAction;
use CommerceWeavers\SyliusTpayPlugin\Payum\Action\Api\CreateBlikLevelZeroTransactionAction;

if @jakubtobiasz is fine with that

use CommerceWeavers\SyliusTpayPlugin\Payum\Action\Api\CreateTransactionAction;
use CommerceWeavers\SyliusTpayPlugin\Payum\Action\Api\NotifyAction;
use CommerceWeavers\SyliusTpayPlugin\Payum\Action\CaptureAction;
Expand All @@ -20,6 +21,7 @@
$services->set(CaptureAction::class)
->args([
service('commerce_weavers.tpay.payum.factory.create_transaction'),
service('commerce_weavers.tpay.payum.factory.create_blik0_transaction'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or at least we can adjust service names

Suggested change
service('commerce_weavers.tpay.payum.factory.create_blik0_transaction'),
service('commerce_weavers.tpay.payum.factory.create_blik_level_zero_transaction'),

])
->tag('payum.action', ['factory' => TpayGatewayFactory::NAME, 'alias' => 'cw.tpay.capture'])
;
Expand All @@ -44,4 +46,14 @@

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

$services->set(CreateBlik0TransactionAction::class)
->args([
service('router'),
param('commerce_weavers_tpay.payum.create_transaction.success_route'),
param('commerce_weavers_tpay.payum.create_transaction.error_route'),
param('commerce_weavers_tpay.payum.create_transaction.notify_route'),
])
->tag('payum.action', ['factory' => TpayGatewayFactory::NAME, 'alias' => 'cw.tpay.create_blik0_transaction'])
;
};
5 changes: 5 additions & 0 deletions config/services/payum/factory.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\Payum\Factory\CreateBlik0TransactionFactory;
use CommerceWeavers\SyliusTpayPlugin\Payum\Factory\CreateTransactionFactory;
use CommerceWeavers\SyliusTpayPlugin\Payum\Factory\CreateTransactionFactoryInterface;
use CommerceWeavers\SyliusTpayPlugin\Payum\Factory\NotifyFactory;
Expand All @@ -28,4 +29,8 @@
$services->set('commerce_weavers.tpay.payum.factory.create_transaction', CreateTransactionFactory::class)
->alias(CreateTransactionFactoryInterface::class, 'commerce_weavers.tpay.payum.factory.create_transaction')
;

$services->set('commerce_weavers.tpay.payum.factory.create_blik0_transaction', CreateBlik0TransactionFactory::class)
->alias(CreateTransactionFactoryInterface::class, 'commerce_weavers.tpay.payum.factory.create_blik0_transaction')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

;
};
3 changes: 2 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
parameters:
level: max
reportUnmatchedIgnoredErrors: false
checkMissingIterableValueType: false
paths:
- src

Expand All @@ -11,6 +10,8 @@ parameters:
- 'tests/Application/src/**.php'

ignoreErrors:
- identifier: missingType.iterableValue
- identifier: missingType.generics
- '/Parameter #1 \$configuration of method Symfony\\Component\\DependencyInjection\\Extension\\Extension::processConfiguration\(\) expects Symfony\\Component\\Config\\Definition\\ConfigurationInterface, Symfony\\Component\\Config\\Definition\\ConfigurationInterface\|null given\./'
- '/Parameter \#1 \$request \([^)]+\) of method [^:]+::execute\(\) should be contravariant with parameter \$request \(mixed\) of method Payum\\Core\\Action\\ActionInterface::execute\(\)/'
- '/Parameter \$event of method CommerceWeavers\\SyliusTpayPlugin\\Refunding\\Workflow\\Listener\\DispatchRefundListener::__invoke\(\) has invalid type Symfony\\Component\\Workflow\\Event\\TransitionEvent\./'
Expand Down
12 changes: 12 additions & 0 deletions src/Entity/OrderLastNewPaymentAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Entity;

use Sylius\Component\Core\Model\PaymentInterface;

interface OrderLastNewPaymentAwareInterface
{
public function getLastNewPayment(): ?PaymentInterface;
}
15 changes: 15 additions & 0 deletions src/Entity/OrderLastNewPaymentAwareTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Entity;

use Sylius\Component\Core\Model\PaymentInterface;

trait OrderLastNewPaymentAwareTrait
{
public function getLastNewPayment(): ?PaymentInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misleading naming. Payment on its own has new state and according to name I would expect such a payment.

Suggested change
public function getLastNewPayment(): ?PaymentInterface
public function getLastCartPayment(): ?PaymentInterface

class name should be changed as well

{
return $this->getLastPayment('cart');
}
}
26 changes: 26 additions & 0 deletions src/Form/DataTransformer/PaymentDetailsTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Webmozart\Assert\Assert;

class PaymentDetailsTransformer implements DataTransformerInterface
{
public function transform(mixed $value): string
{
Assert::isArray($value);
if ($value === [] || !array_key_exists('blik', $value)) {
return '';
}

return $value['blik'];
}

public function reverseTransform($value): array
{
return ['blik' => $value];
}
}
31 changes: 31 additions & 0 deletions src/Form/Extension/CompleteTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Form\Extension;

use CommerceWeavers\SyliusTpayPlugin\Form\Type\PaymentDetailsType;
use Sylius\Bundle\CoreBundle\Form\Type\Checkout\CompleteType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;

final class CompleteTypeExtension extends AbstractTypeExtension
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('others', PaymentDetailsType::class, [
'label' => 'commerce_weavers_sylius_tpay.payment.blik.token',
// TODO some validation that works becuase this kind does not
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No todos nor comments should be committed to the codebase. You are more than welcome to open an issue and add it to board

// 'constraints' => [
// new Length(['value' => 6, 'min' => 6, 'max' => 6, 'groups' => ['sylius']]),
// ],
'property_path' => 'last_new_payment.details[tpay]',
'required' => false,
]);
}

public static function getExtendedTypes(): iterable
{
return [CompleteType::class];
}
}
23 changes: 23 additions & 0 deletions src/Form/Type/PaymentDetailsType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Form\Type;

use CommerceWeavers\SyliusTpayPlugin\Form\DataTransformer\PaymentDetailsTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class PaymentDetailsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addModelTransformer(new PaymentDetailsTransformer());
}

public function getParent(): string
{
return TextType::class;
}
}
102 changes: 102 additions & 0 deletions src/Payum/Action/Api/CreateBlik0TransactionAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Payum\Action\Api;

use CommerceWeavers\SyliusTpayPlugin\Payum\Request\Api\CreateBlik0Transaction;
use CommerceWeavers\SyliusTpayPlugin\Payum\Request\Api\CreateTransaction;
use Payum\Core\Security\GenericTokenFactoryAwareInterface;
use Payum\Core\Security\GenericTokenFactoryAwareTrait;
use Payum\Core\Security\TokenInterface;
use Sylius\Component\Core\Model\PaymentInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Tpay\OpenApi\Api\TpayApi;
use Webmozart\Assert\Assert;

/**
* @property TpayApi $api
*/
final class CreateBlik0TransactionAction extends BaseApiAwareAction implements GenericTokenFactoryAwareInterface
{
use GenericTokenFactoryAwareTrait;

public function __construct(
private RouterInterface $router,
private string $successRoute,
private string $errorRoute,
private string $notifyRoute,
) {
parent::__construct();
}

/**
* @param CreateTransaction $request
*/
public function execute($request): void
{
/** @var PaymentInterface $model */
$model = $request->getModel();
$details = $model->getDetails();
$token = $request->getToken();
Assert::notNull($token);

$order = $model->getOrder();
Assert::notNull($order);
$customer = $order->getCustomer();
Assert::notNull($customer);
$localeCode = $order->getLocaleCode();
Assert::notNull($localeCode);
$billingAddress = $order->getBillingAddress();
Assert::notNull($billingAddress);
$notifyToken = $this->createNotifyToken($model, $token, $localeCode);
$amount = $model->getAmount();
Assert::notNull($amount);

$blikToken = $model->getDetails()['tpay']['blik'];

$response = $this->api->transactions()->createTransaction([
'amount' => number_format($amount / 100, 2, thousands_separator: ''),
'description' => sprintf('zamówienie #%s', $order->getNumber()), // TODO: Introduce translations
'payer' => [
'email' => $customer->getEmail(),
'name' => $billingAddress->getFullName(),
],
'pay' => [
'groupId' => 150,
'blikPaymentData' => [
'blikToken' => $blikToken,
],
],
'callbacks' => [
'payerUrls' => [
'success' => $this->router->generate($this->successRoute, ['_locale' => $localeCode], UrlGeneratorInterface::ABSOLUTE_URL),
'error' => $this->router->generate($this->errorRoute, ['_locale' => $localeCode], UrlGeneratorInterface::ABSOLUTE_URL),
],
'notification' => [
'url' => $notifyToken->getTargetUrl(),
],
],
]);

$details['tpay']['transaction_id'] = $response['transactionId'];
$details['tpay']['status'] = $response['status'];

$model->setDetails($details);
}

public function supports($request): bool
{
return $request instanceof CreateBlik0Transaction && $request->getModel() instanceof PaymentInterface;
}

private function createNotifyToken(PaymentInterface $payment, TokenInterface $token, string $localeCode): TokenInterface
{
return $this->tokenFactory->createToken(
$token->getGatewayName(),
$payment,
$this->router->generate($this->notifyRoute, ['_locale' => $localeCode], UrlGeneratorInterface::ABSOLUTE_URL),
);
}
}
16 changes: 16 additions & 0 deletions src/Payum/Action/CaptureAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class CaptureAction implements ActionInterface, GatewayAwareInterface

public function __construct(
private CreateTransactionFactoryInterface $createTransactionFactory,
private CreateTransactionFactoryInterface $createBlik0TransactionFactory,
) {
}

Expand All @@ -29,6 +30,14 @@ public function execute($request): void
/** @var PaymentInterface $model */
$model = $request->getModel();

if ($this->transactionIsBlik($model)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'mon, it is widely accepted practice to put is and other verbs at the beginning of functions, please adjust code to informal standard

Suggested change
if ($this->transactionIsBlik($model)) {
if ($this->isBlikTransaction($model)) {

$this->gateway->execute(
$this->createBlik0TransactionFactory->createNewWithModel($request->getToken()),
);

return;
}

$this->gateway->execute(
$this->createTransactionFactory->createNewWithModel($request->getToken()),
);
Expand All @@ -42,4 +51,11 @@ public function supports($request): bool
{
return $request instanceof Capture && $request->getModel() instanceof PaymentInterface;
}

private function transactionIsBlik(PaymentInterface $model): bool
{
return array_key_exists('tpay', $model->getDetails()) &&
array_key_exists('blik', $model->getDetails()['tpay'])
;
}
}
15 changes: 15 additions & 0 deletions src/Payum/Factory/CreateBlik0TransactionFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace CommerceWeavers\SyliusTpayPlugin\Payum\Factory;

use CommerceWeavers\SyliusTpayPlugin\Payum\Request\Api\CreateBlik0Transaction;

final class CreateBlik0TransactionFactory implements CreateTransactionFactoryInterface
{
public function createNewWithModel(mixed $model): CreateBlik0Transaction
{
return new CreateBlik0Transaction($model);
}
}
4 changes: 2 additions & 2 deletions src/Payum/Factory/CreateTransactionFactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

namespace CommerceWeavers\SyliusTpayPlugin\Payum\Factory;

use CommerceWeavers\SyliusTpayPlugin\Payum\Request\Api\CreateTransaction;
use Payum\Core\Request\Generic;

interface CreateTransactionFactoryInterface
{
public function createNewWithModel(mixed $model): CreateTransaction;
public function createNewWithModel(mixed $model): Generic;
}
Loading
Loading