Completed
Push — master ( 0fda94...5b2f37 )
by Stefan
19s queued 13s
created

AbstractSaferpayTest::clickButton()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Karser\PayumSaferpay\Tests\Functional;
4
5
use Goutte\Client;
6
use Karser\PayumSaferpay\Model\CardAlias;
7
use Karser\PayumSaferpay\Model\CardAliasInterface;
8
use Karser\PayumSaferpay\Request\InsertCardAlias;
9
use Karser\PayumSaferpay\SaferpayGatewayFactory;
10
use Payum\Core\Bridge\PlainPhp\Security\TokenFactory;
11
use Payum\Core\GatewayFactoryInterface;
12
use Payum\Core\GatewayInterface;
13
use Payum\Core\Model\Payment;
14
use Payum\Core\Model\PaymentInterface;
15
use Payum\Core\Payum;
16
use Payum\Core\PayumBuilder;
17
use Payum\Core\Registry\StorageRegistryInterface;
18
use Payum\Core\Reply\HttpRedirect;
19
use Payum\Core\Request\Capture;
20
use Payum\Core\Request\GetHumanStatus;
21
use Payum\Core\Security\TokenInterface;
22
use Payum\Core\Storage\FilesystemStorage;
23
use Payum\Core\Storage\StorageInterface;
24
use PHPUnit\Framework\TestCase;
25
use Symfony\Component\BrowserKit\Exception\BadMethodCallException;
26
use Symfony\Component\BrowserKit\Response;
27
use Symfony\Component\DomCrawler\Crawler;
28
29
abstract class AbstractSaferpayTest extends TestCase
30
{
31
    protected const GATEWAY_NAME = 'saferpay';
32
    protected const HOST = 'http://localhost';
33
    protected const AMOUNT = 123; // 1.23
34
    protected const CURRENCY = 'USD';
35
    protected const DESCRIPTION = 'A description';
36
    protected const ALIAS_LIFETIME = 1600;
37
38
    //LS: Liability shift; AUTH: Authenticated; 3D: 3D Secure;
39
    //All cards are from Mastercard
40
    protected const CARD_SUCCESS_LS_AUTH_3D = '9030100052000000';
41
    protected const CARD_SUCCESS = '9030101152000007';
42
    protected const CARD_FAILED = '9030100152000009'; //'9010100152000003';
43
44
    /** @var Payum */
45
    protected $payum;
46
47
    /** @var GatewayInterface */
48
    protected $gateway;
49
50
    /** @var StorageInterface */
51
    protected $storage;
52
53
    /** @var StorageInterface */
54
    protected $cardAliasStorage;
55
56
    /** @var Client */
57
    protected $client;
58
59
    public function setUp(): void
60
    {
61
        $builder = (new PayumBuilder())
62
            ->addDefaultStorages()
63
            ->addStorage(CardAlias::class, new FilesystemStorage(sys_get_temp_dir(), CardAlias::class))
64
            ->setTokenFactory(static function(StorageInterface $tokenStorage, StorageRegistryInterface $registry) {
65
                return new TokenFactory($tokenStorage, $registry, self::HOST);
66
            })
67
            ->addGatewayFactory('saferpay', static function(array $config, GatewayFactoryInterface $coreGatewayFactory) {
68
                return new SaferpayGatewayFactory($config, $coreGatewayFactory);
69
            })
70
            ->addGateway(self::GATEWAY_NAME, [
71
                'factory' => 'saferpay',
72
                'username' => 'API_401860_80003225',
73
                'password' => 'C-y*bv8346Ze5-T8',
74
                'customerId' => '401860',
75
                'terminalId' => '17795278',
76
                'sandbox' => true,
77
            ]);
78
        $payum = $builder->getPayum();
79
80
        $this->payum = $payum;
81
        $this->gateway = $payum->getGateway(self::GATEWAY_NAME);
82
        $this->storage = $this->payum->getStorage(Payment::class);
83
        $this->cardAliasStorage = $this->payum->getStorage(CardAlias::class);
84
85
        $client = new Client();
86
        $client->followRedirects(false);
87
        $this->client = $client;
88
    }
89
90
    protected function submitForm(string $buttonSel, array $fieldValues = [], string $method = 'POST', array $serverParameters = []): Crawler
91
    {
92
        $crawler = $this->client->getCrawler();
93
        if (null === $crawler) {
94
            throw new BadMethodCallException(sprintf('The "request()" method must be called before "%s()".', __METHOD__));
95
        }
96
97
        $buttonNode = $crawler->filter($buttonSel)->first();
98
        $form = $buttonNode->form($fieldValues, $method);
99
100
        return $this->client->submit($form, [], $serverParameters);
101
    }
102
103
    protected function clickLink(string $linkSelector): Crawler
104
    {
105
        $crawler = $this->client->getCrawler();
106
        if (null === $crawler) {
107
            throw new BadMethodCallException(sprintf('The "request()" method must be called before "%s()".', __METHOD__));
108
        }
109
110
        return $this->client->click($crawler->filter($linkSelector)->link());
111
    }
112
113
    protected function clickButton(string $buttonSelector): Crawler
114
    {
115
        $crawler = $this->client->getCrawler();
116
        if (null === $crawler) {
117
            throw new BadMethodCallException(sprintf('The "request()" method must be called before "%s()".', __METHOD__));
118
        }
119
120
        $buttonNode = $crawler->filter($buttonSelector)->first();
121
        $form = $buttonNode->form([], 'POST');
122
123
        return $this->client->submit($form);
124
    }
125
126
    protected function composeFormData(string $card, bool $cvc = true): array
127
    {
128
        $data = [
129
            'CardNumber' => $card,
130
            'Expiry' => sprintf('01/%d', date('Y', strtotime('+1 year'))),
131
            'HolderName' => 'John Doe',
132
        ];
133
        if ($cvc) {
134
            $data['VerificationCode'] = '111';
135
        }
136
        return $data;
137
    }
138
139
    protected function capture(TokenInterface $token, PaymentInterface $payment): ?HttpRedirect
140
    {
141
        $captureRequest = new Capture($token);
142
        $captureRequest->setModel($payment);
143
        return $this->gateway->execute($captureRequest, true);
144
    }
145
146
    protected function assertStatus(string $expected, PaymentInterface $payment): void
147
    {
148
        $status = new GetHumanStatus($payment);
149
        $this->gateway->execute($status);
150
        self::assertEquals($expected, $status->getValue());
151
    }
152
153
    protected function createPayment(array $details = []): Payment
154
    {
155
        /** @var Payment $payment */
156
        $payment = $this->storage->create();
157
        $payment->setNumber(uniqid());
158
        $payment->setCurrencyCode(self::CURRENCY);
159
        $payment->setTotalAmount(self::AMOUNT);
160
        $payment->setDescription(self::DESCRIPTION);
161
        $payment->setDetails($details);
162
        $this->storage->update($payment);
163
        return $payment;
164
    }
165
166
    protected function getThroughCheckout(string $url, array $formData, string $action = 'submit'): string
167
    {
168
        $this->client->request('GET', $url);
169
        if (false !== strpos($url, '/vt2/api/PaymentPage')) {
170
            $this->client->followRedirect();
171
            $this->client->submitForm('MasterCard');
172
            $this->client->followRedirect();
173
        }
174
175
        if (
176
            false !== strpos($this->client->getCrawler()->getUri(), '/VT2/mpp/PaymentDataEntry/Index')
177
            || false !== strpos($this->client->getCrawler()->getUri(), '/vt2/Api/Post')
178
            || false !== strpos($this->client->getCrawler()->getUri(), '/vt2/api/register/card')
179
        ) {
180
            if ($action === 'abort') {
181
                $location = $this->client->getCrawler()->filter('button.btn-abort')->attr('formaction');
182
                if (0 === strpos($location, self::HOST)) {
183
                    return $location;
184
                }
185
                $this->clickButton('button.btn-abort');
186
            } else {
187
                $this->client->submitForm('SubmitToNext', $formData);
188
            }
189
            /** @var Response $response */
190
            $response = $this->client->getResponse();
191
            if ($response->getStatusCode() === 302) {
192
                $location = $response->getHeader('Location');
193
                if (0 === strpos($location, self::HOST)) {
194
                    return $location;
195
                }
196
                $this->client->followRedirect();
197
            }
198
        }
199
200
        if (false !== strpos($this->client->getCrawler()->getUri(), '/VT2/mpp/PaymentDataEntry/Index')) {
201
            self::assertSame(200, $this->client->getResponse()->getStatusCode());
202
            $this->client->submitForm( $action === 'submit' ? 'Buy' : 'Cancel');
203
            self::assertSame(302, $this->client->getResponse()->getStatusCode());
204
            $this->client->followRedirect();
205
        }
206
        if (
207
            false !== strpos($this->client->getCrawler()->getUri(), '/VT2/mpp/ThreeDS/Index')
208
            || false !== strpos($this->client->getCrawler()->getUri(), '/VT2/api/ThreeDs')
209
        ) {
210
            self::assertSame(200, $this->client->getResponse()->getStatusCode());
211
            $this->submitForm('[type="submit"]');
212
            self::assertSame(200, $this->client->getResponse()->getStatusCode());
213
214
            $this->client->submitForm($action === 'submit' ? 'Submit' : 'Cancel');
215
            self::assertSame(200, $this->client->getResponse()->getStatusCode());
216
217
            $this->client->submitForm('Submit');
218
            self::assertSame(200, $this->client->getResponse()->getStatusCode());
219
            $this->clickLink('a.btn-next');
220
221
            $response = $this->client->getResponse();
222
            self::assertSame(302, $response->getStatusCode());
223
            $location = $response->getHeader('Location');
224
            if (0 === strpos($location, self::HOST)) {
225
                return $location;
226
            }
227
            $this->client->followRedirect();
228
        }
229
        if (false !== strpos($this->client->getCrawler()->getUri(), '/VT2/mpp/Error/System')) {
230
            $this->client->submitForm('Cancel');
231
232
            $response = $this->client->getResponse();
233
            self::assertSame(302, $response->getStatusCode());
234
            $location = $response->getHeader('Location');
235
            if (0 === strpos($location, self::HOST)) {
236
                return $location;
237
            }
238
            $this->client->followRedirect();
239
        }
240
        return $this->client->getCrawler()->filter('a.btn-next')->first()->attr('href');
241
    }
242
243
    protected function createCardAlias(array $details): CardAliasInterface
244
    {
245
        /** @var CardAlias $alias */
246
        $alias = $this->cardAliasStorage->create();
247
        $alias->setDetails($details);
248
        $this->cardAliasStorage->update($alias);
249
        return $alias;
250
    }
251
252
    protected function createCapturedPayment(array $options): PaymentInterface
253
    {
254
        $payment = $this->createPayment($options);
255
256
        $token = $this->payum->getTokenFactory()->createCaptureToken(self::GATEWAY_NAME, $payment, 'done.php');
257
        $this->payum->getHttpRequestVerifier()->invalidate($token); //no need to store token
258
259
        # INIT transaction
260
        $reply = $this->capture($token, $payment);
261
        if ($reply instanceof HttpRedirect) {
262
            # submit form
263
            $iframeRedirect = $this->getThroughCheckout($reply->getUrl(), $this->composeFormData(self::CARD_SUCCESS));
264
            parse_str(parse_url($iframeRedirect, PHP_URL_QUERY), $_GET);
265
266
            # AUTHORIZE AND CAPTURE
267
            $this->capture($token, $payment);
268
        }
269
        return $payment;
270
    }
271
272
    protected function createInsertedCardAlias(array $options): CardAliasInterface
273
    {
274
        $cardAlias = $this->createCardAlias($options);
275
276
        $token = $this->payum->getTokenFactory()->createCaptureToken(self::GATEWAY_NAME, $cardAlias, 'done.php');
277
        $this->payum->getHttpRequestVerifier()->invalidate($token); //no need to store token
278
279
        $reply = $this->insertCardAlias($token, $cardAlias);
280
281
        # submit form
282
        $iframeRedirect = $this->getThroughCheckout($reply->getUrl(), $this->composeFormData(self::CARD_SUCCESS, $cvc = false));
283
        parse_str(parse_url($iframeRedirect, PHP_URL_QUERY), $_GET);
284
285
        $this->insertCardAlias($token, $cardAlias);
286
287
        return $cardAlias;
288
    }
289
290
    protected function insertCardAlias(TokenInterface $token, CardAliasInterface $cardAlias): ?HttpRedirect
291
    {
292
        $insertCardAliasRequest = new InsertCardAlias($token);
293
        $insertCardAliasRequest->setModel($cardAlias);
294
        return $this->gateway->execute($insertCardAliasRequest, true);
295
    }
296
}
297