Completed
Push — master ( 2d293e...b89557 )
by Vuong
02:12
created

BasePaymentGateway   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 358
Duplicated Lines 13.69 %

Coupling/Cohesion

Components 3
Dependencies 6

Test Coverage

Coverage 82.56%

Importance

Changes 0
Metric Value
wmc 30
lcom 3
cbo 6
dl 49
loc 358
ccs 71
cts 86
cp 0.8256
rs 10
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 8 2
initSandboxEnvironment() 0 1 ?
A verifyRequestCommands() 0 17 4
A purchase() 0 4 1
A queryDR() 0 4 1
A refund() 0 4 1
A queryRefund() 0 4 1
A verifyRequestPurchaseSuccess() 0 4 1
A verifyRequestIPN() 0 4 1
A verifyRequest() 7 34 5
getVerifyRequestData() 0 1 ?
A verifiedRequest() 0 10 3
A beforeRequest() 21 21 5
A afterRequest() 21 21 5

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * @link https://github.com/yiiviet/yii2-payment
4
 * @copyright Copyright (c) 2017 Yii Viet
5
 * @license [New BSD License](http://www.opensource.org/licenses/bsd-license.php)
6
 */
7
8
namespace yiiviet\payment;
9
10
use Yii;
11
use ReflectionClass;
12
13
use GatewayClients\DataInterface;
14
15
use yii\base\InvalidArgumentException;
16
use yii\base\InvalidConfigException;
17
18
use vxm\gatewayclients\BaseGateway;
19
use vxm\gatewayclients\RequestEvent;
20
use vxm\gatewayclients\ResponseData;
21
22
/**
23
 * Lớp BasePaymentGateway thực thi mẫu trừu tượng [[PaymentGatewayInterface]] giúp cho việc xây dựng các lợp thực thi được tối giản.
24
 *
25
 * @property BasePaymentClient $client
26
 * @property BasePaymentClient $defaultClient
27
 *
28
 * @author Vuong Minh <[email protected]>
29
 * @since 1.0
30
 */
31
abstract class BasePaymentGateway extends BaseGateway implements PaymentGatewayInterface
32
{
33
    /**
34
     * Lệnh `purchase` sử dụng cho việc khởi tạo truy vấn thanh toán.
35
     */
36
    const RC_PURCHASE = 'purchase';
37
38
    /**
39
     * Lệnh `queryDR` sử dụng cho việc truy vấn thông tin giao dịch.
40
     */
41
    const RC_QUERY_DR = 'queryDR';
42
43
    /**
44
     * Lệnh `refund` sử dụng cho việc tạo [[request()]] yêu cầu hoàn trả tiền.
45
     */
46
    const RC_REFUND = 'refund';
47
48
    /**
49
     * Lệnh `queryRefund` sử dụng cho việc tạo [[request()]] để kiểm tra trang thái của lệnh `refund` đã tạo.
50
     */
51
    const RC_QUERY_REFUND = 'queryRefund';
52
53
    /**
54
     * Lệnh `purchaseSuccess` sử dụng cho việc yêu cấu xác thực tính hợp lệ
55
     * của dữ liệu khi khách hàng thanh toán thành công (cổng thanh toán redirect khách hàng về server).
56
     */
57
    const VRC_PURCHASE_SUCCESS = 'purchaseSuccess';
58
59
    /**
60
     * Lệnh `IPN` sử dụng cho việc yêu cấu xác thực tính hợp lệ
61
     * của dữ liệu khi khách hàng thanh toán thành công (cổng thanh toán bắn request về server).
62
     */
63
    const VRC_IPN = 'IPN';
64
65
    /**
66
     * @event RequestEvent được gọi khi dữ liệu truy vấn đã được xác thực.
67
     * Lưu ý sự kiện này luôn luôn được gọi khi xác thực dữ liệu truy vấn.
68
     */
69
    const EVENT_VERIFIED_REQUEST = 'verifiedRequest';
70
71
    /**
72
     * @event VerifiedRequestEvent được gọi khi dữ liệu truy vấn sau khi khách hàng thanh toán thành công,
73
     * được cổng thanh toán dẫn về hệ thống đã xác thực.
74
     */
75
    const EVENT_VERIFIED_REQUEST_PURCHASE_SUCCESS = 'verifiedRequestPurchaseSuccess';
76
77
    /**
78
     * @event VerifiedRequestEvent được gọi khi dữ liệu truy vấn sau khi khách hàng thanh toán thành công,
79
     * được cổng thanh toán bắn `request` sang hệ thống đã xác thực.
80
     */
81
    const EVENT_VERIFIED_REQUEST_IPN = 'verifiedRequestIPN';
82
83
    /**
84
     * @event RequestEvent được gọi trước khi khởi tạo lệnh [[RC_PURCHASE]] ở phương thức [[request()]].
85
     */
86
    const EVENT_BEFORE_PURCHASE = 'beforePurchase';
87
88
    /**
89
     * @event RequestEvent được gọi sau khi khởi tạo lệnh [[RC_PURCHASE]] ở phương thức [[request()]].
90
     */
91
    const EVENT_AFTER_PURCHASE = 'afterPurchase';
92
93
    /**
94
     * @event RequestEvent được gọi trước khi khởi tạo lệnh [[RC_QUERY_DR]] ở phương thức [[request()]].
95
     */
96
    const EVENT_BEFORE_QUERY_DR = 'beforeQueryDR';
97
98
    /**
99
     * @event RequestEvent được gọi sau khi khởi tạo lệnh [[RC_QUERY_DR]] ở phương thức [[request()]].
100
     */
101
    const EVENT_AFTER_QUERY_DR = 'afterQueryDR';
102
103
    /**
104
     * @event RequestEvent được gọi trước khi khởi tạo lệnh [[RC_REFUND]] ở phương thức [[request()]].
105
     * @since 1.0.3
106
     */
107
    const EVENT_BEFORE_REFUND = 'beforeRefund';
108
109
    /**
110
     * @event RequestEvent được gọi sau khi khởi tạo lệnh [[RC_REFUND]] ở phương thức [[request()]].
111
     * @since 1.0.3
112
     */
113
    const EVENT_AFTER_REFUND = 'afterRefund';
114
115
    /**
116
     * @event RequestEvent được gọi trước khi khởi tạo lệnh [[RC_QUERY_REFUND]] ở phương thức [[request()]].
117
     * @since 1.0.3
118
     */
119
    const EVENT_BEFORE_QUERY_REFUND = 'beforeQueryRefund';
120
121
    /**
122
     * @event RequestEvent được gọi sau khi khởi tạo lệnh [[RC_QUERY_REFUND]] ở phương thức [[request()]].
123
     * @since 1.0.3
124
     */
125
    const EVENT_AFTER_QUERY_REFUND = 'afterQueryRefund';
126
127
    /**
128
     * @var bool nếu là môi trường test thì thiết lập là TRUE và ngược lại.
129
     */
130
    public $sandbox = false;
131
132
    /**
133
     * @var array
134
     */
135
    public $verifiedDataConfig = [];
136
137
    /**
138
     * @inheritdoc
139
     */
140 55
    public function init()
141
    {
142 55
        if ($this->sandbox) {
143 55
            $this->initSandboxEnvironment();
144
        }
145
146 55
        parent::init();
147 55
    }
148
149
    /**
150
     * Phương thức khởi tạo môi trường thử nghiệm.
151
     * Nó chỉ được gọi khi thuộc tính `sandbox` được thiết lập là TRUE.
152
     */
153
    abstract protected function initSandboxEnvironment();
154
155
    /**
156
     * Thuộc tính để cache lại tiến trình của [[verifyRequestCommands()]] nhầm tối ưu tốc độ khi gọi nhiều lần.
157
     *
158
     * @see verifyRequestCommands
159
     * @var array
160
     */
161
    private $_verifyRequestCommands;
162
163
    /**
164
     * Phương thức này tự động thu thập các lệnh xác thực thông qua các hằng được khai bảo bằng tiền tố `VRC_`.
165
     * `VRC` có nghĩa là Verify Request Command.
166
     *
167
     * @inheritdoc
168
     * @throws \ReflectionException
169
     */
170 11
    public function verifyRequestCommands(): array
171
    {
172 11
        if ($this->_verifyRequestCommands === null) {
173 11
            $reflection = new ReflectionClass($this);
174
175 11
            $commands = [];
176 11
            foreach ($reflection->getConstants() as $name => $value) {
177 11
                if (strpos($name, 'VRC_') === 0) {
178 11
                    $commands[] = $value;
179
                }
180
            }
181
182 11
            return $this->_verifyRequestCommands = $commands;
183
        } else {
184
            return $this->_verifyRequestCommands;
185
        }
186
    }
187
188
    /**
189
     * Phương thức này là phương thức ánh xạ của [[request()]] nó sẽ tạo lệnh [[RC_PURCHASE]] để tạo yêu cầu giao dịch tới cổng thanh toán.
190
     *
191
     * @param array $data dữ liệu dùng để yêu cầu tạo giao dịch thanh toán bên trong thường có giá tiền, địa chỉ giao hàng...
192
     * @param string|int $clientId PaymentClient id dùng để tạo yêu cầu thanh toán.
193
     * @return ResponseData|DataInterface Phương thức sẽ trả về mẫu trừu tượng [[DataInterface]] để lấy thông tin trả về từ cổng thanh toán.
194
     * @throws InvalidConfigException|\ReflectionException
195
     */
196 2
    public function purchase(array $data, $clientId = null): DataInterface
197
    {
198 2
        return $this->request(self::RC_PURCHASE, $data, $clientId);
199
    }
200
201
    /**
202
     * Phương thức này là phương thức ánh xạ của [[request()]] nó sẽ tạo lệnh [[RC_QUERY_DR]] để tạo yêu cầu truy vấn giao dịch tới cổng thanh toán.
203
     *
204
     * @param array $data dữ liệu dùng để truy vấn thông tin giao dịch bên trong thường có mã giao dịch từ cổng thanh toán...
205
     * @param string|int $clientId PaymentClient id dùng để tạo yêu cầu truy vấn giao dịch.
206
     * @return ResponseData|DataInterface Phương thức sẽ trả về mẫu trừu tượng [[DataInterface]] để lấy thông tin trả về từ cổng thanh toán.
207
     * @throws InvalidConfigException|\ReflectionException
208
     */
209
    public function queryDR(array $data, $clientId = null): DataInterface
210
    {
211
        return $this->request(self::RC_QUERY_DR, $data, $clientId);
212
    }
213
214
    /**
215
     * Phương thức này là phương thức ánh xạ của [[request()]] nó sẽ tạo lệnh [[RC_REFUND]] để tạo yêu cầu hoàn tiền tới cổng thanh toán.
216
     *
217
     * @param array $data dữ liệu yêu cầu hoàn trả.
218
     * @param null $clientId Client id sử dụng để tạo yêu cầu.
219
     * Nếu không thiết lập [[getDefaultClient()]] sẽ được gọi để xác định client.
220
     * @return ResponseData|DataInterface Trả về [[DataInterface]] là dữ liệu tổng hợp từ MOMO phản hồi.
221
     * @throws \ReflectionException|\yii\base\InvalidConfigException|\yii\base\InvalidArgumentException
222
     * @since 1.0.3
223
     */
224
    public function refund(array $data, $clientId = null): DataInterface
225
    {
226
        return $this->request(self::RC_REFUND, $data, $clientId);
227
    }
228
229
    /**
230
     * Phương thức này là phương thức ánh xạ của [[request()]] nó sẽ tạo lệnh [[RC_QUERY_REFUND]] để tạo yêu cầu truy vấn trạng thái hoàn tiền tới cổng thanh toán.
231
     *
232
     * @param array $data dữ liệu trạng thái hoàn tiền.
233
     * @param null $clientId Client id sử dụng để tạo yêu cầu truy vấn trạng thái.
234
     * Nếu không thiết lập [[getDefaultClient()]] sẽ được gọi để xác định client.
235
     * @return ResponseData|DataInterface Trả về [[DataInterface]] là dữ liệu tổng hợp từ MOMO phản hồi.
236
     * @throws \ReflectionException|\yii\base\InvalidConfigException|\yii\base\InvalidArgumentException
237
     * @since 1.0.3
238
     */
239
    public function queryRefund(array $data, $clientId = null): DataInterface
240
    {
241
        return $this->request(self::RC_QUERY_REFUND, $data, $clientId);
242
    }
243
244
    /**
245
     * @inheritdoc
246
     */
247 23 View Code Duplication
    public function beforeRequest(RequestEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
248
    {
249 23
        switch ($event->command) {
250 23
            case self::RC_PURCHASE:
251 10
                $this->trigger(self::EVENT_BEFORE_PURCHASE, $event);
252 10
                break;
253 13
            case self::RC_QUERY_DR:
254 5
                $this->trigger(self::EVENT_BEFORE_QUERY_DR, $event);
255 5
                break;
256 8
            case self::RC_REFUND:
257 1
                $this->trigger(self::EVENT_BEFORE_REFUND, $event);
258 1
                break;
259 7
            case self::RC_QUERY_REFUND:
260
                $this->trigger(self::EVENT_BEFORE_QUERY_REFUND, $event);
261
                break;
262
            default:
263 7
                break;
264
        }
265
266 23
        parent::beforeRequest($event);
267 23
    }
268
269
    /**
270
     * @inheritdoc
271
     */
272 21 View Code Duplication
    public function afterRequest(RequestEvent $event)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
273
    {
274 21
        switch ($event->command) {
275 21
            case self::RC_PURCHASE:
276 8
                $this->trigger(self::EVENT_AFTER_PURCHASE, $event);
277 8
                break;
278 13
            case self::RC_QUERY_DR:
279 5
                $this->trigger(self::EVENT_AFTER_QUERY_DR, $event);
280 5
                break;
281 8
            case self::RC_REFUND:
282 1
                $this->trigger(self::EVENT_AFTER_REFUND, $event);
283 1
                break;
284 7
            case self::RC_QUERY_REFUND:
285
                $this->trigger(self::EVENT_AFTER_QUERY_REFUND, $event);
286
                break;
287
            default:
288 7
                break;
289
        }
290
291 21
        parent::afterRequest($event);
292 21
    }
293
294
    /**
295
     * Phương thức này là phương thức ánh xạ của [[verifyRequest()]] nó sẽ tạo lệnh [[VRC_PURCHASE_SUCCESS]]
296
     * để tạo yêu cầu xác minh tính hợp lệ của dữ liệu trả về từ máy khách đến máy chủ.
297
     *
298
     * @param \yii\web\Request|null $request Đối tượng `request` thực hiện truy cập hệ thống.
299
     * @param null|int|string $clientId PaymentClient id dùng để xác thực tính hợp lệ của dữ liệu.
300
     * @return bool|VerifiedData|DataInterface Sẽ trả về FALSE nếu như dữ liệu không hợp lệ ngược lại sẽ trả về thông tin đơn hàng đã được xác thực.
301
     * @throws InvalidConfigException|\ReflectionException
302
     */
303 1
    public function verifyRequestPurchaseSuccess(\yii\web\Request $request = null, $clientId = null)
304
    {
305 1
        return $this->verifyRequest(self::VRC_PURCHASE_SUCCESS, $request, $clientId);
306
    }
307
308
    /**
309
     * Phương thức này là phương thức ánh xạ của [[verifyRequest()]] nó sẽ tạo lệnh [[VRC_IPN]]
310
     * để tạo yêu cầu xác minh tính hợp lệ của dữ liệu trả về từ cổng thanh toán đến máy chủ.
311
     *
312
     * @param \yii\web\Request|null $request Đối tượng `request` thực hiện truy cập hệ thống.
313
     * @param null|int|string $clientId PaymentClient id dùng để xác thực tính hợp lệ của dữ liệu.
314
     * @return bool|VerifiedData|DataInterface Sẽ trả về FALSE nếu như dữ liệu không hợp lệ ngược lại sẽ trả về thông tin đơn hàng đã được xác thực.
315
     * @throws InvalidConfigException|\ReflectionException
316
     */
317 2
    public function verifyRequestIPN(\yii\web\Request $request = null, $clientId = null)
318
    {
319 2
        return $this->verifyRequest(self::VRC_IPN, $request, $clientId);
320
    }
321
322
    /**
323
     * @inheritdoc
324
     * @throws InvalidConfigException|\ReflectionException
325
     */
326 11
    public function verifyRequest($command, \yii\web\Request $request = null, $clientId = null)
327
    {
328 11
        if (in_array($command, $this->verifyRequestCommands(), true)) {
329 11
            $client = $this->getClient($clientId);
330
331 11 View Code Duplication
            if ($request === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
332 9
                if (Yii::$app instanceof \yii\web\Application) {
333 9
                    $request = Yii::$app->getRequest();
334
                } else {
335
                    throw new InvalidArgumentException('Request instance arg must be set to verify return request is valid or not!');
336
                }
337
            }
338
339 11
            $data = $this->getVerifyRequestData($command, $request);
340
            /** @var VerifiedData $requestData */
341 11
            $verifyData = Yii::createObject($this->verifiedDataConfig, [$command, $data, $client]);
342 11
            if ($verifyData->validate()) {
343
                /** @var VerifiedRequestEvent $event */
344 2
                $event = Yii::createObject([
345 2
                    'class' => VerifiedRequestEvent::class,
346 2
                    'verifiedData' => $verifyData,
347 2
                    'client' => $client,
348 2
                    'command' => $command
349
                ]);
350 2
                $this->verifiedRequest($event);
351
352 2
                return $verifyData;
353
            } else {
354 9
                return false;
355
            }
356
        } else {
357
            throw new InvalidArgumentException("Verify request command: `$command` invalid in " . __CLASS__);
358
        }
359
    }
360
361
    /**
362
     * Phương thúc lấy dữ liệu cần xác minh từ lệnh yêu cầu và đối tượng `request`.
363
     *
364
     * @param int|string $command Lệnh yêu cầu lấy dữ liệu cần được xác minh.
365
     * @param \yii\web\Request $request Đối tượng `request` được yêu cầu lấy dữ liệu.
366
     * @return array Trả về mảng dữ liệu cần được xác minh.
367
     */
368
    abstract protected function getVerifyRequestData($command, \yii\web\Request $request): array;
369
370
    /**
371
     * Phương thức được gọi sau khi việc xác minh tính hợp hệ của dữ liệu thành công.
372
     * Nó được xây dựng để kích hoạt các sự kiện liên quan khi dữ liệu đã được xác minh.
373
     *
374
     * @param VerifiedRequestEvent $event
375
     */
376 2
    public function verifiedRequest(VerifiedRequestEvent $event)
377
    {
378 2
        if ($event->command === self::VRC_IPN) {
379 2
            $this->trigger(self::EVENT_VERIFIED_REQUEST_IPN, $event);
380
        } elseif ($event->command === self::VRC_PURCHASE_SUCCESS) {
381
            $this->trigger(self::EVENT_VERIFIED_REQUEST_PURCHASE_SUCCESS, $event);
382
        }
383
384 2
        $this->trigger(self::EVENT_VERIFIED_REQUEST, $event);
385 2
    }
386
387
388
}
389