Completed
Push — master ( 339fdb...9dc0a8 )
by Rafał
08:30 queued 04:25
created

PayumController   C

Complexity

Total Complexity 12

Size/Duplication

Total Lines 217
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 0
Metric Value
wmc 12
lcom 1
cbo 20
dl 0
loc 217
rs 6.4705
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 1
B prepareCaptureAction() 0 31 3
A afterCaptureAction() 0 22 2
B cancelAction() 0 28 3
A afterCancelAction() 0 13 1
A getTokenFactory() 0 4 1
A getHttpRequestVerifier() 0 4 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PH\Bundle\PayumBundle\Controller;
6
7
use FOS\RestBundle\View\View;
8
use Payum\Core\Payum;
9
use Payum\Core\Security\GenericTokenFactoryInterface;
10
use Payum\Core\Security\HttpRequestVerifierInterface;
11
use PH\Bundle\PayumBundle\Factory\ResolveNextUrlFactoryInterface;
12
use PH\Component\Core\Model\SubscriptionInterface;
13
use PH\Component\Core\Model\PaymentInterface;
14
use PH\Component\Core\Repository\SubscriptionRepositoryInterface;
15
use PH\Component\Core\Repository\PaymentRepositoryInterface;
16
use Sylius\Bundle\PayumBundle\Request\GetStatus;
17
use Sylius\Bundle\ResourceBundle\Controller\RequestConfigurationFactoryInterface;
18
use Sylius\Bundle\ResourceBundle\Controller\ViewHandlerInterface;
19
use Sylius\Component\Resource\Metadata\MetadataInterface;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpFoundation\Response;
22
use Symfony\Component\HttpKernel\Exception\HttpException;
23
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
24
use Symfony\Component\Routing\RouterInterface;
25
26
class PayumController
27
{
28
    /**
29
     * @var Payum
30
     */
31
    private $payum;
32
33
    /**
34
     * @var SubscriptionRepositoryInterface
35
     */
36
    private $subscriptionRepository;
37
38
    /**
39
     * @var MetadataInterface
40
     */
41
    private $subscriptionMetadata;
42
43
    /**
44
     * @var RequestConfigurationFactoryInterface
45
     */
46
    private $requestConfigurationFactory;
47
48
    /**
49
     * @var ViewHandlerInterface
50
     */
51
    private $viewHandler;
52
53
    /**
54
     * @var RouterInterface
55
     */
56
    private $router;
57
58
    /**
59
     * @var PaymentRepositoryInterface
60
     */
61
    private $paymentRepository;
62
63
    /**
64
     * @var ResolveNextUrlFactoryInterface
65
     */
66
    private $resolveNextUrlFactory;
67
68
    /**
69
     * @param Payum                                $payum
70
     * @param SubscriptionRepositoryInterface      $subscriptionRepository
71
     * @param MetadataInterface                    $subscriptionMetadata
72
     * @param RequestConfigurationFactoryInterface $requestConfigurationFactory
73
     * @param ViewHandlerInterface                 $viewHandler
74
     * @param RouterInterface                      $router
75
     * @param PaymentRepositoryInterface           $paymentRepository
76
     * @param ResolveNextUrlFactoryInterface       $resolveNextUrlFactory
77
     */
78
    public function __construct(
79
        Payum $payum,
80
        SubscriptionRepositoryInterface $subscriptionRepository,
81
        MetadataInterface $subscriptionMetadata,
82
        RequestConfigurationFactoryInterface $requestConfigurationFactory,
83
        ViewHandlerInterface $viewHandler,
84
        RouterInterface $router,
85
        PaymentRepositoryInterface $paymentRepository,
86
        ResolveNextUrlFactoryInterface $resolveNextUrlFactory
87
    ) {
88
        $this->payum = $payum;
89
        $this->subscriptionRepository = $subscriptionRepository;
90
        $this->subscriptionMetadata = $subscriptionMetadata;
91
        $this->requestConfigurationFactory = $requestConfigurationFactory;
92
        $this->viewHandler = $viewHandler;
93
        $this->router = $router;
94
        $this->paymentRepository = $paymentRepository;
95
        $this->resolveNextUrlFactory = $resolveNextUrlFactory;
96
    }
97
98
    /**
99
     * @param Request $request
100
     * @param mixed   $token
101
     *
102
     * @return Response
103
     */
104
    public function prepareCaptureAction(Request $request, string $token): Response
105
    {
106
        $configuration = $this->requestConfigurationFactory->create($this->subscriptionMetadata, $request);
107
108
        /** @var SubscriptionInterface $subscription */
109
        $subscription = $this->subscriptionRepository->findOneByTokenValue($token);
0 ignored issues
show
Bug introduced by
The method findOneByTokenValue() does not exist on PH\Component\Core\Reposi...tionRepositoryInterface. Did you maybe mean findOneBy()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
110
111
        if (null === $subscription) {
112
            throw new NotFoundHttpException(sprintf('Subscription with token "%s" does not exist.', $token));
113
        }
114
115
        $options = $configuration->getParameters()->get('redirect');
116
117
        /** @var PaymentInterface $payment */
118
        $payment = $subscription->getLastPayment(PaymentInterface::STATE_NEW);
119
120
        if (null === $payment) {
121
            return $this->viewHandler->handle($configuration, View::create([], 404));
122
        }
123
124
        $captureToken = $this->getTokenFactory()->createCaptureToken(
125
            $payment->getMethod()->getGatewayConfig()->getGatewayName(),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Payment...\PaymentMethodInterface as the method getGatewayConfig() does only exist in the following implementations of said interface: PH\Component\Core\Model\PaymentMethod, Sylius\Component\Core\Model\PaymentMethod.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
126
            $payment,
127
            $options['route'] ?? null,
128
            $options['parameters'] ?? []
129
        );
130
131
        $view = View::createRedirect($captureToken->getTargetUrl());
132
133
        return $this->viewHandler->handle($configuration, $view);
134
    }
135
136
    /**
137
     * @param Request $request
138
     *
139
     * @return Response
140
     *
141
     * @throws \InvalidArgumentException
142
     */
143
    public function afterCaptureAction(Request $request): Response
144
    {
145
        $configuration = $this->requestConfigurationFactory->create($this->subscriptionMetadata, $request);
146
        $token = $this->getHttpRequestVerifier()->verify($request);
147
        $status = new GetStatus($token);
148
149
        $this->payum->getGateway($token->getGatewayName())->execute($status);
150
151
        if (!$request->query->has('redirect')) {
152
            $resolveNextUrl = $this->resolveNextUrlFactory->createNewWithModel($status->getFirstModel());
153
            $this->payum->getGateway($token->getGatewayName())->execute($resolveNextUrl);
154
            $redirect = $resolveNextUrl->getUrl();
155
        } else {
156
            $redirect = $request->query->get('redirect');
157
        }
158
159
        $this->getHttpRequestVerifier()->invalidate($token);
160
161
        $view = View::createRedirect($redirect);
162
163
        return $this->viewHandler->handle($configuration, $view);
164
    }
165
166
    /**
167
     * @param Request $request
168
     * @param string  $subscriptionId
169
     * @param string  $id
170
     *
171
     * @return Response
172
     *
173
     * @throws \InvalidArgumentException
174
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
175
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
176
     */
177
    public function cancelAction(Request $request, string $subscriptionId, string $id): Response
178
    {
179
        $configuration = $this->requestConfigurationFactory->create($this->subscriptionMetadata, $request);
180
181
        /** @var PaymentInterface|null $payment */
182
        $payment = $this->paymentRepository->findOneBySubscriptionId($id, $subscriptionId);
183
184
        if (null === $payment) {
185
            throw new NotFoundHttpException(sprintf('Payment with id "%s" does not exist.', $id));
186
        }
187
188
        if (PaymentInterface::STATE_CANCELLED === $payment->getState()) {
189
            throw new HttpException(409, 'The payment has been already cancelled!');
190
        }
191
192
        $options = $configuration->getParameters()->get('redirect');
193
194
        $cancelToken = $this->getTokenFactory()->createCancelToken(
195
            $payment->getMethod()->getGatewayConfig()->getGatewayName(),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Payment...\PaymentMethodInterface as the method getGatewayConfig() does only exist in the following implementations of said interface: PH\Component\Core\Model\PaymentMethod, Sylius\Component\Core\Model\PaymentMethod.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
196
            $payment,
197
            $options['route'] ?? null,
198
            $options['parameters'] ?? []
199
        );
200
201
        $view = View::createRedirect($cancelToken->getTargetUrl());
202
203
        return $this->viewHandler->handle($configuration, $view);
204
    }
205
206
    /**
207
     * @param Request $request
208
     *
209
     * @return Response
210
     *
211
     * @throws \InvalidArgumentException
212
     */
213
    public function afterCancelAction(Request $request): Response
214
    {
215
        $configuration = $this->requestConfigurationFactory->create($this->subscriptionMetadata, $request);
216
        $token = $this->getHttpRequestVerifier()->verify($request);
217
        $status = new GetStatus($token);
218
219
        $this->payum->getGateway($token->getGatewayName())->execute($status);
220
        $this->getHttpRequestVerifier()->invalidate($token);
221
222
        $view = View::create([], 204);
223
224
        return $this->viewHandler->handle($configuration, $view);
225
    }
226
227
    /**
228
     * @return GenericTokenFactoryInterface
229
     */
230
    private function getTokenFactory(): GenericTokenFactoryInterface
231
    {
232
        return $this->payum->getTokenFactory();
233
    }
234
235
    /**
236
     * @return HttpRequestVerifierInterface
237
     */
238
    private function getHttpRequestVerifier(): HttpRequestVerifierInterface
239
    {
240
        return $this->payum->getHttpRequestVerifier();
241
    }
242
}
243