1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
handler.php
См. документацию.
1<?php
2
3namespace Sale\Handlers\PaySystem;
4
5use Bitrix\Main;
6use Bitrix\Main\Entity\Result;
7use Bitrix\Main\Localization\Loc;
8use Bitrix\Main\Request;
9use Bitrix\Main\Web\HttpClient;
10use Bitrix\Main\Web\Json;
11use Bitrix\Sale\Payment;
12use Bitrix\Sale\PaymentCollection;
13use Bitrix\Sale\PaySystem;
14use Bitrix\Sale\PaySystem\Logger;
15use Bitrix\Sale\PaySystem\ServiceResult;
16use Bitrix\Sale\PriceMaths;
17
18class PlatonHandler extends PaySystem\ServiceHandler implements PaySystem\IRefund
19{
20 private const PAYMENT_METHOD_CODE = 'CC';
21 private const REFUND_ACTION = 'CREDITVOID';
22
23 private const ANALYTICS_TAG = 'api_bitrix24ua';
24
25 /*
26 * order: payment id
27 * ext1: the name of the handler
28 * ext2: pay system service ID
29 * ext3: analytics tag
30 */
31 private const CALLBACK_ORDER_PARAM = 'order';
32 private const CALLBACK_EXT1_PARAM = 'ext1';
33 private const CALLBACK_EXT2_PARAM = 'ext2';
34
35 private const TRANSACTION_STATUS_SALE = 'SALE';
36 private const TRANSACTION_STATUS_REFUND = 'REFUND';
37
38 private const REFUND_STATUS_ACCEPTED = 'ACCEPTED';
39 private const REFUND_STATUS_ERROR = 'ERROR';
40
41 private const PS_MODE_BANK_CARD = 'bank_card';
42 private const PS_MODE_GOOGLE_PAY = 'google_pay';
43 private const PS_MODE_APPLE_PAY = 'apple_pay';
44 private const PS_MODE_PRIVAT24 = 'privat24';
45
49 public function initiatePay(Payment $payment, Request $request = null)
50 {
51 $result = new PaySystem\ServiceResult();
52
53 $params = [
54 'CURRENCY' => $payment->getField('CURRENCY'),
55 'SUM' => PriceMaths::roundPrecision($payment->getSum()),
56 'FORM_ACTION_URL' => $this->getUrl($payment, "formActionUrl"),
57 'FORM_DATA' => $this->getFormData($payment),
58 ];
59 $this->setExtraParams($params);
60
61 $showTemplateResult = $this->showTemplate($payment, 'template');
62 if ($showTemplateResult->isSuccess())
63 {
64 $result->setTemplate($showTemplateResult->getTemplate());
65 }
66 else
67 {
68 $result->addErrors($showTemplateResult->getErrors());
69 }
70
71 return $result;
72 }
73
79 private function getFormData(Payment $payment): array
80 {
81 $apiKey = $this->getBusinessValue($payment, 'PLATON_API_KEY');
82 $paymentMethodCode = self::PAYMENT_METHOD_CODE;
83 $paymentData = $this->getPaymentData($payment);
84 $encodedPaymentData = $this->encodePaymentData($paymentData);
85 $successUrl = $this->getSuccessUrl($payment);
86 $password = $this->getBusinessValue($payment, 'PLATON_PASSWORD');
87
88 $sign = $this->getPaymentFormSignature(
89 $apiKey,
90 $paymentMethodCode,
91 $encodedPaymentData,
92 $successUrl,
94 );
95
96 $paymentNumber = $payment->getField('ACCOUNT_NUMBER');
97 $paySystemId = $this->service->getField('ID');
98
99 $formData = [
100 'KEY' => $apiKey,
101 'PAYMENT' => $paymentMethodCode,
102 'DATA' => $encodedPaymentData,
103 'URL' => $successUrl,
104 'REQ_TOKEN' => 'N',
105 'SIGN' => $sign,
106 'ORDER' => $paymentNumber,
107 'EXT_1' => 'PLATON',
108 'EXT_2' => $paySystemId,
109 'EXT_3' => self::ANALYTICS_TAG,
110 ];
111
112 $email = $this->getUserEmailValue($payment);
113 if ($email)
114 {
115 $formData['EMAIL'] = $email;
116 }
117
118 return $formData;
119 }
120
126 private function encodePaymentData(array $paymentData): string
127 {
128 return base64_encode(Json::encode($paymentData, JSON_UNESCAPED_UNICODE));
129 }
130
135 private function decode($data)
136 {
137 try
138 {
139 return Main\Web\Json::decode($data);
140 }
141 catch (Main\ArgumentException $exception)
142 {
143 return false;
144 }
145 }
146
151 private function getPaymentData(Payment $payment)
152 {
153 $formattedPaymentSum = $this->getFormattedPaymentSum($payment);
154 $paymentDescription = $this->getPaymentDescription($payment);
155
156 return [
157 'amount' => $formattedPaymentSum,
158 'currency' => $payment->getField("CURRENCY"),
159 'description' => $paymentDescription,
160 'recurring' => 'N',
161 ];
162 }
163
168 private function getFormattedPaymentSum(Payment $payment)
169 {
170 $paymentSum = PriceMaths::roundPrecision($payment->getSum());
171 return $this->formatPaymentSum($paymentSum);
172 }
173
178 private function formatPaymentSum($paymentSum): string
179 {
180 return number_format($paymentSum, 2, '.', '');
181 }
182
188 private function getSuccessUrl(Payment $payment): string
189 {
190 return $this->getBusinessValue($payment, 'PLATON_SUCCESS_URL') ?: $this->service->getContext()->getUrl();
191 }
192
197 private function getPaymentDescription(Payment $payment)
198 {
200 $collection = $payment->getCollection();
201 $order = $collection->getOrder();
202 $userEmail = $order->getPropertyCollection()->getUserEmail();
203
204 $descriptionTemplate = $this->getBusinessValue($payment, 'PLATON_PAYMENT_DESCRIPTION');
205 $description = str_replace(
206 [
207 '#PAYMENT_NUMBER#',
208 '#ORDER_NUMBER#',
209 '#PAYMENT_ID#',
210 '#ORDER_ID#',
211 '#USER_EMAIL#',
212 ],
213 [
214 $payment->getField('ACCOUNT_NUMBER'),
215 $order->getField('ACCOUNT_NUMBER'),
216 $payment->getId(),
217 $order->getId(),
218 ($userEmail) ? $userEmail->getValue() : '',
219 ],
220 $descriptionTemplate
221 );
222
223 return $description;
224 }
225
235 private function getPaymentFormSignature(
236 string $apiKey,
237 string $paymentMethodCode,
238 string $encodedPaymentData,
239 string $successUrl,
240 string $password
241 ): string
242 {
243 return md5(
244 mb_strtoupper(
245 strrev($apiKey)
246 . strrev($paymentMethodCode)
247 . strrev($encodedPaymentData)
248 . strrev($successUrl)
249 . strrev($password)
250 )
251 );
252 }
253
257 public function getCurrencyList()
258 {
259 return ['UAH'];
260 }
261
265 public function processRequest(Payment $payment, Request $request)
266 {
267 $result = new PaySystem\ServiceResult();
268 Logger::addDebugInfo(__CLASS__ . ': request payload: ' . Json::encode($request->getValues(), JSON_UNESCAPED_UNICODE));
269
270 $signatureCheckResult = $this->checkCallbackSignature($payment, $request);
271 if ($signatureCheckResult->isSuccess())
272 {
273 $transactionId = $request->get('id');
274 $transactionStatus = $request->get('status');
275
276 if ($transactionId && $transactionStatus === self::TRANSACTION_STATUS_SALE)
277 {
278 $description = Loc::getMessage('SALE_HPS_PLATON_TRANSACTION_DESCRIPTION', [
279 '#ID#' => $request->get('id'),
280 '#PAYMENT_NUMBER#' => $request->get('order'),
281 ]);
282 $requestSum = $request->get('amount');
283 $paymentFields = [
284 'PS_INVOICE_ID' => $transactionId,
285 'PS_STATUS_CODE' => $transactionStatus,
286 'PS_STATUS_DESCRIPTION' => $description,
287 'PS_SUM' => $requestSum,
288 'PS_CURRENCY' => $request->get('currency'),
289 'PS_RESPONSE_DATE' => new Main\Type\DateTime(),
290 'PS_STATUS' => 'N',
291 'PS_CARD_NUMBER' => $request->get('card'),
292 ];
293
294 if ($this->checkPaymentSum($payment, $requestSum))
295 {
296 $paymentFields['PS_STATUS'] = 'Y';
297
298 $result->setOperationType(PaySystem\ServiceResult::MONEY_COMING);
299 $result->setPsData($paymentFields);
300 }
301 else
302 {
303 $errorMessage = Loc::getMessage('SALE_HPS_PLATON_SUM_MISMATCH');
304 $paymentFields['PS_STATUS_DESCRIPTION'] .= $description . ' ' . $errorMessage;
305 $result->addError(new Main\Error($errorMessage));
306 }
307 }
308 elseif ($transactionStatus === self::TRANSACTION_STATUS_REFUND)
309 {
310 $oldDescription = $payment->getField('PS_STATUS_DESCRIPTION');
311 $newDescription = str_replace(
312 ' ' . Loc::getMessage('SALE_HPS_PLATON_REFUND_IN_PROCESS'),
313 '',
314 $oldDescription
315 );
316 $payment->setField('PS_STATUS_DESCRIPTION', $newDescription);
317 $result->setOperationType(PaySystem\ServiceResult::MONEY_LEAVING);
318 }
319 else
320 {
321 $errorMessage = $request->get('error_message');
322 if (!isset($errorMessage))
323 {
324 $errorMessage = Loc::getMessage('SALE_HPS_PLATON_REQUEST_ERROR');
325 }
326 $result->addError(new Main\Error($errorMessage));
327 }
328 }
329 else
330 {
331 $result->addError(new Main\Error(Loc::getMessage('SALE_HPS_PLATON_SIGNATURE_MISMATCH')));
332 }
333
334 return $result;
335 }
336
342 private function checkCallbackSignature(Payment $payment, Request $request): PaySystem\ServiceResult
343 {
344 $result = new ServiceResult();
345
346 $callbackSignature = $request->get('sign');
347
348 $email = $this->getUserEmailValue($payment);
349
350 $password = $this->getBusinessValue($payment, 'PLATON_PASSWORD');
351 $order = $payment->getField('ACCOUNT_NUMBER');
352 $card = $request->get('card');
353 $localSignature = $this->getCallbackSignature($email, $password, $order, $card);
354
355 Logger::addDebugInfo(__CLASS__ . ": local signature: $localSignature, callback signature: $callbackSignature");
356 if ($callbackSignature !== $localSignature)
357 {
358 $result->addError(new Main\Error('signature mismatch'));
359 }
360
361 return $result;
362 }
363
370 private function checkPaymentSum(Payment $payment, $requestSum): bool
371 {
372 $roundedRequestSum = PriceMaths::roundPrecision($requestSum);
373 $roundedPaymentSum = PriceMaths::roundPrecision($payment->getSum());
374 Logger::addDebugInfo(__CLASS__ . ": request sum: $roundedRequestSum, payment sum: $roundedPaymentSum");
375
376 return $roundedRequestSum === $roundedPaymentSum;
377 }
378
382 public static function getIndicativeFields()
383 {
384 return [
385 self::CALLBACK_ORDER_PARAM,
386 self::CALLBACK_EXT1_PARAM,
387 self::CALLBACK_EXT2_PARAM,
388 ];
389 }
390
394 protected static function isMyResponseExtended(Request $request, $paySystemId)
395 {
396 return (int)$request->get(self::CALLBACK_EXT2_PARAM) === (int)$paySystemId;
397 }
398
402 public function getPaymentIdFromRequest(Request $request)
403 {
404 return $request->get('order');
405 }
406
410 public function refund(Payment $payment, $refundableSum)
411 {
412 $result = new PaySystem\ServiceResult();
413
414 $transactionId = $payment->getField('PS_INVOICE_ID');
415 $cardNumber = $payment->getField('PS_CARD_NUMBER');
416
417 if ($cardNumber)
418 {
419 $formattedPaymentSum = $this->getFormattedPaymentSum($payment);
420 $apiKey = $this->getBusinessValue($payment, 'PLATON_API_KEY');
421 $email = $this->getUserEmailValue($payment);
422
423 $password = $this->getBusinessValue($payment, 'PLATON_PASSWORD');
424
425 $signature = $this->getCallbackSignature($email, $password, $transactionId, $cardNumber);
426
427 $fields = [
428 'action' => self::REFUND_ACTION,
429 'client_key' => $apiKey,
430 'trans_id' => $transactionId,
431 'amount' => $formattedPaymentSum,
432 'hash' => $signature,
433 ];
434
435 $responseResult = $this->send($this->getUrl($payment, "requestUrl"), $fields);
436 if (!$responseResult->isSuccess())
437 {
438 $result->addErrors($responseResult->getErrors());
439 return $result;
440 }
441
442 $responseData = $responseResult->getData();
443
444 Logger::addDebugInfo(__CLASS__ . ': refund payload: ' . Json::encode($responseData, JSON_UNESCAPED_UNICODE));
445 switch ($responseData['result'])
446 {
447 case self::REFUND_STATUS_ACCEPTED:
448 $newDescription = $payment->getField('PS_STATUS_DESCRIPTION') . ' ' . Loc::getMessage('SALE_HPS_PLATON_REFUND_IN_PROCESS');
449 $payment->setField('PS_STATUS_DESCRIPTION', $newDescription);
450 $result->setData($responseData);
451 break;
452 case self::REFUND_STATUS_ERROR:
453 $result->addError(new Main\Error(Loc::getMessage('SALE_HPS_PLATON_RESPONSE_ERROR', [
454 '#PS_RESPONSE#' => $responseData['error_message'],
455 ])));
456 break;
457 default:
458 $result->addError(new Main\Error(Loc::getMessage('SALE_HPS_PLATON_REFUND_ERROR')));
459 }
460 }
461 else
462 {
463 $result->addError(new Main\Error(Loc::getMessage('SALE_HPS_PLATON_ERROR_CARD_NOT_FOUND')));
464 }
465
466 return $result;
467 }
468
475 private function send(string $url, array $params): Result
476 {
477 $result = new Result();
478
479 $httpClient = new HttpClient();
480 $response = $httpClient->post($url, $params);
481 if ($response === false)
482 {
483 $errors = $httpClient->getError();
484 foreach ($errors as $code =>$message)
485 {
486 $result->addError(new Main\Error($message, $code));
487 }
488 }
489 else
490 {
491 $responseData = $this->decode($response);
492 $result->setData($responseData);
493 }
494
495 return $result;
496 }
497
505 private function getCallbackSignature(string $email, string $password, string $transactionId, string $cardNumber): string
506 {
507 return md5(
508 mb_strtoupper(
509 strrev($email)
510 . $password
511 . $transactionId
512 . strrev(
513 mb_substr($cardNumber, 0, 6)
514 . mb_substr($cardNumber, -4)
515 )
516 )
517 );
518 }
519
524 private function getUserEmailValue(Payment $payment): string
525 {
526 $email = '';
527 $emailProperty = $payment->getOrder()->getPropertyCollection()->getUserEmail();
528 if ($emailProperty)
529 {
530 $email = $emailProperty->getValue();
531 }
532
533 return $email ?? '';
534 }
535
536 protected function getUrlList()
537 {
538 return [
539 'formActionUrl' => 'https://secure.platononline.com/payment/auth',
540 'requestUrl' => 'https://secure.platononline.com/post-unq/',
541 ];
542 }
543
547 public static function getHandlerModeList(): array
548 {
549 return PaySystem\Manager::getHandlerDescription('Platon')['HANDLER_MODE_LIST'];
550 }
551}
if(!Loader::includeModule('catalog')) if(!AccessController::getCurrent() ->check(ActionDictionary::ACTION_PRICE_EDIT)) if(!check_bitrix_sessid()) $request
Определения catalog_reindex.php:36
getUrl(Payment $payment=null, $action)
Определения baseservicehandler.php:343
showTemplate(Payment $payment=null, $template='')
Определения baseservicehandler.php:59
getBusinessValue(Payment $payment=null, $code)
Определения baseservicehandler.php:184
$data['IS_AVAILABLE']
Определения .description.php:13
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$result
Определения get_property_values.php:14
if(Loader::includeModule( 'bitrix24')) elseif(Loader::includeModule('intranet') &&CIntranetUtils::getPortalZone() !=='ru') $description
Определения .description.php:24
$errors
Определения iblock_catalog_edit.php:74
while($arParentIBlockProperty=$dbParentIBlockProperty->Fetch()) $errorMessage
if(!is_null($config))($config as $configItem)(! $configItem->isVisible()) $code
Определения options.php:195
$password
Определения mysql_to_pgsql.php:34
trait Error
Определения error.php:11
$payment
Определения payment.php:14
$order
Определения payment.php:8
$sign
Определения payment.php:69
$email
Определения payment.php:49
$message
Определения payment.php:8
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
if($inWords) echo htmlspecialcharsbx(Number2Word_Rus(roundEx($totalVatSum $params['CURRENCY']
Определения template.php:799
$response
Определения result.php:21
$url
Определения iframe.php:7
$fields
Определения yandex_run.php:501