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
|
64 |
|
public function init() |
141
|
|
|
{ |
142
|
64 |
|
if ($this->sandbox) { |
143
|
64 |
|
$this->initSandboxEnvironment(); |
144
|
|
|
} |
145
|
|
|
|
146
|
64 |
|
parent::init(); |
147
|
64 |
|
} |
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
|
13 |
|
public function verifyRequestCommands(): array |
171
|
|
|
{ |
172
|
13 |
|
if ($this->_verifyRequestCommands === null) { |
173
|
13 |
|
$reflection = new ReflectionClass($this); |
174
|
|
|
|
175
|
13 |
|
$commands = []; |
176
|
13 |
|
foreach ($reflection->getConstants() as $name => $value) { |
177
|
13 |
|
if (strpos($name, 'VRC_') === 0) { |
178
|
13 |
|
$commands[] = $value; |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
13 |
|
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
|
30 |
View Code Duplication |
public function beforeRequest(RequestEvent $event) |
|
|
|
|
248
|
|
|
{ |
249
|
30 |
|
switch ($event->command) { |
250
|
30 |
|
case self::RC_PURCHASE: |
251
|
14 |
|
$this->trigger(self::EVENT_BEFORE_PURCHASE, $event); |
252
|
14 |
|
break; |
253
|
19 |
|
case self::RC_QUERY_DR: |
254
|
7 |
|
$this->trigger(self::EVENT_BEFORE_QUERY_DR, $event); |
255
|
7 |
|
break; |
256
|
12 |
|
case self::RC_REFUND: |
257
|
3 |
|
$this->trigger(self::EVENT_BEFORE_REFUND, $event); |
258
|
3 |
|
break; |
259
|
9 |
|
case self::RC_QUERY_REFUND: |
260
|
2 |
|
$this->trigger(self::EVENT_BEFORE_QUERY_REFUND, $event); |
261
|
2 |
|
break; |
262
|
|
|
default: |
263
|
7 |
|
break; |
264
|
|
|
} |
265
|
|
|
|
266
|
30 |
|
parent::beforeRequest($event); |
267
|
30 |
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* @inheritdoc |
271
|
|
|
*/ |
272
|
24 |
View Code Duplication |
public function afterRequest(RequestEvent $event) |
|
|
|
|
273
|
|
|
{ |
274
|
24 |
|
switch ($event->command) { |
275
|
24 |
|
case self::RC_PURCHASE: |
276
|
11 |
|
$this->trigger(self::EVENT_AFTER_PURCHASE, $event); |
277
|
11 |
|
break; |
278
|
16 |
|
case self::RC_QUERY_DR: |
279
|
6 |
|
$this->trigger(self::EVENT_AFTER_QUERY_DR, $event); |
280
|
6 |
|
break; |
281
|
10 |
|
case self::RC_REFUND: |
282
|
2 |
|
$this->trigger(self::EVENT_AFTER_REFUND, $event); |
283
|
2 |
|
break; |
284
|
8 |
|
case self::RC_QUERY_REFUND: |
285
|
1 |
|
$this->trigger(self::EVENT_AFTER_QUERY_REFUND, $event); |
286
|
1 |
|
break; |
287
|
|
|
default: |
288
|
7 |
|
break; |
289
|
|
|
} |
290
|
|
|
|
291
|
24 |
|
parent::afterRequest($event); |
292
|
24 |
|
} |
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
|
13 |
|
public function verifyRequest($command, \yii\web\Request $request = null, $clientId = null) |
327
|
|
|
{ |
328
|
13 |
|
if (in_array($command, $this->verifyRequestCommands(), true)) { |
329
|
13 |
|
$client = $this->getClient($clientId); |
330
|
|
|
|
331
|
13 |
View Code Duplication |
if ($request === null) { |
|
|
|
|
332
|
11 |
|
if (Yii::$app instanceof \yii\web\Application) { |
333
|
11 |
|
$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
|
13 |
|
$data = $this->getVerifyRequestData($command, $request); |
340
|
|
|
/** @var VerifiedData $requestData */ |
341
|
13 |
|
$verifyData = Yii::createObject($this->verifiedDataConfig, [$command, $data, $client]); |
342
|
13 |
|
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
|
11 |
|
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
|
|
|
|
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.