1
|
|
|
<?php |
2
|
|
|
namespace Goodoneuz\PayUz\Http\Classes\Payme; |
3
|
|
|
|
4
|
|
|
use Goodoneuz\PayUz\Models\Transaction; |
5
|
|
|
use Goodoneuz\PayUz\Models\PaymentSystem; |
6
|
|
|
use Goodoneuz\PayUz\Services\PaymentService; |
7
|
|
|
use Goodoneuz\PayUz\Http\Classes\DataFormat; |
8
|
|
|
use Goodoneuz\PayUz\Http\Classes\BaseGateway; |
9
|
|
|
use Goodoneuz\PayUz\Models\PaymentSystemParam; |
10
|
|
|
use Goodoneuz\PayUz\Http\Classes\PaymentException; |
11
|
|
|
use Goodoneuz\PayUz\Services\PaymentSystemService; |
12
|
|
|
|
13
|
|
|
class Payme extends BaseGateway { |
14
|
|
|
public $config; |
15
|
|
|
public $request; |
16
|
|
|
public $response; |
17
|
|
|
public $merchant; |
18
|
|
|
public $payment_system; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Payme constructor. |
22
|
|
|
*/ |
23
|
|
|
public function __construct() |
24
|
|
|
{ |
25
|
|
|
$this->config = PaymentSystemService::getPaymentSystemParamsCollect(PaymentSystem::PAYME); |
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
public function run() |
29
|
|
|
{ |
30
|
|
|
$this->response = new Response(); |
31
|
|
|
$this->request = new Request($this->response); |
32
|
|
|
$this->response->setRequest($this->request); |
33
|
|
|
$this->merchant = new Merchant($this->config, $this->response); |
34
|
|
|
// authorize session |
35
|
|
|
$this->merchant->Authorize(); |
36
|
|
|
|
37
|
|
|
// handle request |
38
|
|
|
switch ($this->request->method) { |
39
|
|
|
case 'CheckPerformTransaction': |
40
|
|
|
$this->CheckPerformTransaction(); |
41
|
|
|
break; |
42
|
|
|
case 'CheckTransaction': |
43
|
|
|
$this->CheckTransaction(); |
44
|
|
|
break; |
45
|
|
|
case 'CreateTransaction': |
46
|
|
|
$this->CreateTransaction(); |
47
|
|
|
break; |
48
|
|
|
case 'PerformTransaction': |
49
|
|
|
$this->PerformTransaction(); |
50
|
|
|
break; |
51
|
|
|
case 'CancelTransaction': |
52
|
|
|
$this->CancelTransaction(); |
53
|
|
|
break; |
54
|
|
|
case 'ChangePassword': |
55
|
|
|
$this->ChangePassword(); |
56
|
|
|
break; |
57
|
|
|
case 'GetStatement': |
58
|
|
|
$this->GetStatement(); |
59
|
|
|
break; |
60
|
|
|
default: |
61
|
|
|
$this->response->error( |
62
|
|
|
Response::ERROR_METHOD_NOT_FOUND, |
63
|
|
|
'Method not found.', |
64
|
|
|
$this->request->method |
65
|
|
|
); |
66
|
|
|
} |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
private function CheckPerformTransaction() |
70
|
|
|
{ |
71
|
|
|
$this->validateParams($this->request->params); |
72
|
|
|
$model = PaymentService::convertKeyToModel($this->request->params['account'][$this->config['key']]); |
73
|
|
|
if ($model == null){ |
74
|
|
|
$this->response->error( |
75
|
|
|
Response::ERROR_INVALID_ACCOUNT, |
76
|
|
|
'Object not fount.' |
77
|
|
|
); |
78
|
|
|
} |
79
|
|
|
if (!PaymentService::isProperModelAndAmount($model, $this->request->params['amount'])){ |
80
|
|
|
$this->response->error( |
81
|
|
|
Response::ERROR_INVALID_AMOUNT, |
82
|
|
|
'Invalid amount for this object.' |
83
|
|
|
); |
84
|
|
|
} |
85
|
|
|
$active_transactions = $this->getModelTransactions($model, true); |
86
|
|
|
\Log::info([ |
87
|
|
|
'active transactions' => count($active_transactions), |
88
|
|
|
'multi' => config('payuz')['multi_transaction'] |
89
|
|
|
]); |
90
|
|
|
if ((count($active_transactions) > 0) && (config('payuz')['multi_transaction'] == false)){ |
91
|
|
|
$this->response->error( |
92
|
|
|
Response::ERROR_INVALID_TRANSACTION, |
93
|
|
|
'There is other active/completed transaction for this object.' |
94
|
|
|
); |
95
|
|
|
} |
96
|
|
|
PaymentService::payListener($model,null,'before-pay'); |
97
|
|
|
|
98
|
|
|
$this->response->success(['allow' => true]); |
99
|
|
|
} |
100
|
|
|
private function CheckTransaction() |
101
|
|
|
{ |
102
|
|
|
$transaction = $this->findTransactionByParams($this->request->params); |
103
|
|
|
if (!$transaction) { |
104
|
|
|
$this->response->error( |
105
|
|
|
Response::ERROR_TRANSACTION_NOT_FOUND, |
106
|
|
|
'Transaction not found.' |
107
|
|
|
); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
$detail = json_decode($transaction->detail,true); |
111
|
|
|
$this->response->success([ |
112
|
|
|
'create_time' => 1*$detail['create_time'], |
113
|
|
|
'perform_time' => 1*$detail['perform_time'], |
114
|
|
|
'cancel_time' => 1*$detail['cancel_time'], |
115
|
|
|
'transaction' => (string)$transaction->id, |
116
|
|
|
'state' => 1*$transaction->state, |
117
|
|
|
'reason' => ($transaction->comment && is_numeric($transaction->comment)) ? 1 * $transaction->comment : null, |
118
|
|
|
]); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
public function validateParams(array $params) |
122
|
|
|
{ |
123
|
|
|
// for example, check amount is numeric |
124
|
|
|
if (!is_numeric($params['amount'])) { |
125
|
|
|
$this->response->error( Response::ERROR_INVALID_AMOUNT, 'Incorrect amount.'); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
// assume, we should have order_id |
129
|
|
|
if (!isset($params['account'][$this->config['key']])) { |
130
|
|
|
$this->response->error( |
131
|
|
|
Response::ERROR_INVALID_ACCOUNT, |
132
|
|
|
Response::message( 'Неверный код Счет.', 'Billing kodida xatolik.', 'Incorrect object code.'), |
133
|
|
|
'key' |
134
|
|
|
); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
return true; |
138
|
|
|
} |
139
|
|
|
private function CreateTransaction() |
140
|
|
|
{ |
141
|
|
|
|
142
|
|
|
$this->validateParams($this->request->params); |
143
|
|
|
$model = PaymentService::convertKeyToModel($this->request->params['account'][$this->config['key']]); |
144
|
|
|
//todo alert if model is null |
145
|
|
|
$transaction = $this->findTransactionByParams($this->request->params); |
146
|
|
|
if ($transaction) { |
147
|
|
|
if ($transaction->state != Transaction::STATE_CREATED) { |
148
|
|
|
$this->response->error( |
149
|
|
|
Response::ERROR_COULD_NOT_PERFORM, |
150
|
|
|
'Transaction found, but is not active.' |
151
|
|
|
); |
152
|
|
|
} elseif ($transaction->isExpired()) { |
153
|
|
|
$transaction->cancel(Transaction::REASON_CANCELLED_BY_TIMEOUT); |
154
|
|
|
$this->response->error( |
155
|
|
|
Response::ERROR_COULD_NOT_PERFORM, |
156
|
|
|
'Transaction is expired.' |
157
|
|
|
); |
158
|
|
|
} |
159
|
|
|
} else { |
160
|
|
|
|
161
|
|
|
try{ |
162
|
|
|
$this->CheckPerformTransaction(); |
163
|
|
|
} catch(PaymentException $e){ |
164
|
|
|
if ($e->response->response['error'] != null) |
165
|
|
|
throw $e; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
if (DataFormat::timestamp2milliseconds(1 * $this->request->params['time']) - DataFormat::timestamp(true) >= Transaction::TIMEOUT) { |
169
|
|
|
$this->response->error( |
170
|
|
|
Response::ERROR_INVALID_ACCOUNT, |
171
|
|
|
Response::message( |
172
|
|
|
'С даты создания транзакции прошло ' . Transaction::TIMEOUT . 'мс', |
173
|
|
|
'Tranzaksiya yaratilgan vaqtdan ' . Transaction::TIMEOUT . 'ms o`tgan', |
174
|
|
|
'Since create time of the transaction passed ' . Transaction::TIMEOUT . 'ms' |
175
|
|
|
), |
176
|
|
|
'time' |
177
|
|
|
); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
$create_time = DataFormat::timestamp(true); |
181
|
|
|
|
182
|
|
|
$detail = json_encode(array( |
183
|
|
|
'create_time' => $create_time, |
184
|
|
|
'perform_time' => null, |
185
|
|
|
'cancel_time' => null, |
186
|
|
|
'system_time_datetime' => DataFormat::timestamp2datetime($this->request->params['time']) |
187
|
|
|
)); |
188
|
|
|
|
189
|
|
|
$transaction = Transaction::create([ |
190
|
|
|
'payment_system' => PaymentSystem::PAYME, |
191
|
|
|
'system_transaction_id' => $this->request->params['id'], |
192
|
|
|
'amount' =>1*($this->request->amount)/100, |
193
|
|
|
'currency_code' => Transaction::CURRENCY_CODE_UZS, |
194
|
|
|
'state' => Transaction::STATE_CREATED, |
195
|
|
|
'updated_time' => 1*$create_time, |
196
|
|
|
'comment' => (isset($this->request->params['error_note'])?$this->request->params['error_note']:''), |
197
|
|
|
'detail' => $detail, |
198
|
|
|
'transactionable_type' => get_class($model), |
199
|
|
|
'transactionable_id' => $model->id |
200
|
|
|
]); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
PaymentService::payListener($model,$transaction,'paying'); |
204
|
|
|
|
205
|
|
|
$this->response->success([ |
206
|
|
|
'create_time' => 1*$transaction->updated_time, |
207
|
|
|
'transaction' => (string)$transaction->id, |
208
|
|
|
'state' => 1*$transaction->state, |
209
|
|
|
'receivers' => $transaction->receivers, |
210
|
|
|
]); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
private function PerformTransaction() |
214
|
|
|
{ |
215
|
|
|
$transaction = $this->findTransactionByParams($this->request->params); |
216
|
|
|
|
217
|
|
|
// if transaction not found, send error |
218
|
|
|
if (!$transaction) { |
219
|
|
|
$this->response->error(Response::ERROR_TRANSACTION_NOT_FOUND, 'Transaction not found.'); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
switch ($transaction->state) { |
223
|
|
|
case Transaction::STATE_CREATED: |
224
|
|
|
if ($transaction->isExpired()) { |
225
|
|
|
$transaction->cancel(Transaction::REASON_CANCELLED_BY_TIMEOUT); |
226
|
|
|
$this->response->error( |
227
|
|
|
Response::ERROR_COULD_NOT_PERFORM, |
228
|
|
|
'Transaction is expired.' |
229
|
|
|
); |
230
|
|
|
} else { |
231
|
|
|
|
232
|
|
|
$perform_time = DataFormat::timestamp(true); |
233
|
|
|
$transaction->state = Transaction::STATE_COMPLETED; |
234
|
|
|
$transaction->updated_time = $perform_time; |
235
|
|
|
|
236
|
|
|
$detail = json_decode($transaction->detail,true); |
237
|
|
|
$detail['perform_time'] = $perform_time; |
238
|
|
|
$detail = json_encode($detail); |
239
|
|
|
|
240
|
|
|
$transaction->detail = $detail; |
241
|
|
|
|
242
|
|
|
$transaction->update(); |
243
|
|
|
|
244
|
|
|
PaymentService::payListener(null,$transaction,'after-pay'); |
245
|
|
|
|
246
|
|
|
$this->response->success([ |
247
|
|
|
'transaction' => (string)$transaction->id, |
248
|
|
|
'perform_time' => 1*$perform_time, |
249
|
|
|
'state' => 1*$transaction->state, |
250
|
|
|
]); |
251
|
|
|
} |
252
|
|
|
break; |
253
|
|
|
|
254
|
|
View Code Duplication |
case Transaction::STATE_COMPLETED: // handle complete transaction |
255
|
|
|
$detail = json_decode($transaction->detail,true); |
256
|
|
|
|
257
|
|
|
$this->response->success([ |
258
|
|
|
'transaction' => (string)$transaction->id, |
259
|
|
|
'perform_time' => 1*$detail['perform_time'], |
260
|
|
|
'state' => 1*$transaction->state, |
261
|
|
|
]); |
262
|
|
|
|
263
|
|
|
break; |
264
|
|
|
|
265
|
|
|
default: |
266
|
|
|
// unknown situation |
267
|
|
|
$this->response->error( |
268
|
|
|
Response::ERROR_COULD_NOT_PERFORM, |
269
|
|
|
'Could not perform this operation.' |
270
|
|
|
); |
271
|
|
|
break; |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
private function CancelTransaction() |
275
|
|
|
{ |
276
|
|
|
$transaction = $this->findTransactionByParams($this->request->params); |
277
|
|
|
|
278
|
|
|
// if transaction not found, send error |
279
|
|
|
if (!$transaction) { |
280
|
|
|
$this->response->error(Response::ERROR_TRANSACTION_NOT_FOUND, 'Transaction not found.'); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
switch ($transaction->state) { |
284
|
|
|
// if already cancelled, just send it |
285
|
|
|
case Transaction::STATE_CANCELLED: |
286
|
|
View Code Duplication |
case Transaction::STATE_CANCELLED_AFTER_COMPLETE: |
287
|
|
|
$detail = json_decode($transaction->detail,true); |
288
|
|
|
$this->response->success([ |
289
|
|
|
'transaction' => (string)$transaction->id, |
290
|
|
|
'cancel_time' => 1*$detail['cancel_time'], |
291
|
|
|
'state' => 1*$transaction->state, |
292
|
|
|
]); |
293
|
|
|
break; |
294
|
|
|
|
295
|
|
|
// cancel active transaction |
296
|
|
|
case Transaction::STATE_CREATED: |
297
|
|
|
// cancel transaction with given reason |
298
|
|
|
$transaction->cancel(1 * $this->request->params['reason']); |
299
|
|
|
|
300
|
|
|
$cancel_time = DataFormat::timestamp(true); |
301
|
|
|
|
302
|
|
|
$detail = json_decode($transaction->detail,true); |
303
|
|
|
$detail['cancel_time'] = $cancel_time; |
304
|
|
|
|
305
|
|
|
$transaction->update([ |
306
|
|
|
'updated_time'=> $cancel_time, |
307
|
|
|
'detail' => json_encode($detail)]); |
308
|
|
|
|
309
|
|
|
|
310
|
|
|
PaymentService::payListener(null,$transaction,'cancel-pay'); |
311
|
|
|
|
312
|
|
|
$this->response->success([ |
313
|
|
|
'transaction' => (string)$transaction->id, |
314
|
|
|
'cancel_time' => 1*$cancel_time, |
315
|
|
|
'state' => 1*$transaction->state, |
316
|
|
|
]); |
317
|
|
|
break; |
318
|
|
|
|
319
|
|
|
case Transaction::STATE_COMPLETED: |
320
|
|
|
if (true) { |
321
|
|
|
$transaction->cancel(1 * $this->request->params['reason']); |
322
|
|
|
|
323
|
|
|
$detail = json_decode($transaction->detail,true); |
324
|
|
|
|
325
|
|
|
PaymentService::payListener(null,$transaction,'cancel-pay'); |
326
|
|
|
|
327
|
|
|
$this->response->success([ |
328
|
|
|
'transaction' => (string)$transaction->id, |
329
|
|
|
'cancel_time' => 1*$detail['cancel_time'], |
330
|
|
|
'state' => 1*$transaction->state, |
331
|
|
|
]); |
332
|
|
|
} else { |
333
|
|
|
$this->response->error( |
334
|
|
|
Response::ERROR_COULD_NOT_CANCEL, |
335
|
|
|
'Could not cancel transaction. Order is delivered/Service is completed.' |
336
|
|
|
); |
337
|
|
|
} |
338
|
|
|
break; |
339
|
|
|
} |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
public function findTransactionByParams($params) |
343
|
|
|
{ |
344
|
|
|
$transaction = Transaction::where('payment_system', PaymentSystem::PAYME)->where('system_transaction_id', $params['id'])->first(); |
345
|
|
|
return $transaction; |
346
|
|
|
} |
347
|
|
|
public function getModelTransactions($model, $active = false) |
348
|
|
|
{ |
349
|
|
|
$transactions = Transaction::where('payment_system', PaymentSystem::PAYME) |
350
|
|
|
->where('transactionable_type',get_class($model)) |
351
|
|
|
->where('transactionable_id',$model->id); |
352
|
|
|
if ($active) |
353
|
|
|
$transactions = $transactions->where('state', Transaction::STATE_CREATED); |
354
|
|
|
return $transactions->get(); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
/** |
358
|
|
|
* @throws \Goodoneuz\PayUz\Http\Classes\PaymentException |
359
|
|
|
*/ |
360
|
|
|
private function ChangePassword() |
361
|
|
|
{ |
362
|
|
|
// validate, password is specified, otherwise send error |
363
|
|
|
if (!isset($this->request->params['password']) || !trim($this->request->params['password'])) { |
364
|
|
|
$this->response->error(Response::ERROR_INVALID_ACCOUNT, 'New password not specified.', 'password'); |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
// if current password specified as new, then send error |
368
|
|
|
if ($this->merchant->config['password'] == $this->request->params['password']) { |
369
|
|
|
$this->response->error(Response::ERROR_INSUFFICIENT_PRIVILEGE, 'Insufficient privilege. Incorrect new password.'); |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
$completed = false; |
373
|
|
|
$params = PaymentSystemParam::where('system',PaymentSystem::PAYME)->get(); |
374
|
|
|
foreach($params as $param){ |
375
|
|
|
if($param->name == 'password'){ |
376
|
|
|
$param->update([ |
377
|
|
|
'value' => $this->request->params['password'] |
378
|
|
|
]); |
379
|
|
|
$completed = true; |
380
|
|
|
} |
381
|
|
|
} |
382
|
|
|
if (!$completed){ |
383
|
|
|
$this->response->error(Response::ERROR_INTERNAL_SYSTEM, 'Internal System Error.'); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
$this->response->success(['success' => true]); |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
private function GetStatement() |
390
|
|
|
{ |
391
|
|
|
// validate 'from' |
392
|
|
|
if (!isset($this->request->params['from'])) { |
393
|
|
|
$this->response->error(Response::ERROR_INVALID_ACCOUNT, 'Incorrect period.', 'from'); |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
// validate 'to' |
397
|
|
|
if (!isset($this->request->params['to'])) { |
398
|
|
|
$this->response->error(Response::ERROR_INVALID_ACCOUNT, 'Incorrect period.', 'to'); |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
// validate period |
402
|
|
|
if (1 * $this->request->params['from'] >= 1 * $this->request->params['to']) { |
403
|
|
|
$this->response->error(Response::ERROR_INVALID_ACCOUNT, 'Incorrect period. (from >= to)', 'from'); |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
// get list of transactions for specified period |
407
|
|
|
$transactions = $this->getReport($this->request->params['from'], $this->request->params['to']); |
408
|
|
|
|
409
|
|
|
// send results back |
410
|
|
|
$this->response->success(['transactions' => $transactions]); |
411
|
|
|
} |
412
|
|
|
public function getReport($from_date, $to_date) |
413
|
|
|
{ |
414
|
|
|
$from_date = DataFormat::timestamp2datetime($from_date); |
415
|
|
|
$to_date = DataFormat::timestamp2datetime($to_date); |
416
|
|
|
|
417
|
|
|
$transactions = Transaction::where('payment_system',PaymentSystem::PAYME) |
418
|
|
|
->where('state',Transaction::STATE_COMPLETED) |
419
|
|
|
->where('created_at','>=',$from_date) |
420
|
|
|
->where('created_at','<=',$to_date)->get(); |
421
|
|
|
// assume, here we have $rows variable that is populated with transactions from data store |
422
|
|
|
// normalize data for response |
423
|
|
|
$result = []; |
424
|
|
|
foreach ($transactions as $row) { |
425
|
|
|
$detail = json_decode($row['detail'],true); |
426
|
|
|
|
427
|
|
|
$result[] = [ |
428
|
|
|
'id' => (string)$row['system_transaction_id'], // paycom transaction id |
429
|
|
|
'time' => 1 * $detail['system_time_datetime'], // paycom transaction timestamp as is |
430
|
|
|
'amount' => 1 * $row['amount'], |
431
|
|
|
'account' => [ |
432
|
|
|
'key' => 1 * $row[$this->config['key']], // account parameters to identify client/order/service |
433
|
|
|
// ... additional parameters may be listed here, which are belongs to the account |
434
|
|
|
], |
435
|
|
|
'create_time' => DataFormat::datetime2timestamp($detail['create_time']), |
436
|
|
|
'perform_time' => DataFormat::datetime2timestamp($detail['perform_time']), |
437
|
|
|
'cancel_time' => DataFormat::datetime2timestamp($detail['cancel_time']), |
438
|
|
|
'transaction' => (string)$row['id'], |
439
|
|
|
'state' => 1 * $row['state'], |
440
|
|
|
'reason' => isset($row['comment']) ? 1 * $row['comment'] : null, |
441
|
|
|
'receivers' => isset($row['receivers']) ? json_decode($row['receivers'], true) : null, |
442
|
|
|
]; |
443
|
|
|
} |
444
|
|
|
return $result; |
445
|
|
|
|
446
|
|
|
} |
447
|
|
|
public function getRedirectParams($model, $amount, $currency, $url){ |
448
|
|
|
return [ |
449
|
|
|
'merchant' => $this->config['merchant_id'], |
450
|
|
|
'amount' => $amount*100, |
451
|
|
|
'account[key]' => PaymentService::convertModelToKey($model), |
452
|
|
|
'lang' => 'ru', |
453
|
|
|
'currency' => $currency, |
454
|
|
|
'callback' => $url, |
455
|
|
|
'callback_timeout' => 20000, |
456
|
|
|
'url' => "https://checkout.paycom.uz/", |
457
|
|
|
]; |
458
|
|
|
} |
459
|
|
|
} |
460
|
|
|
|