1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
handler.php
См. документацию.
1<?php
2
3namespace Sale\Handlers\PaySystem;
4
5use Bitrix\Main,
6 Bitrix\Main\Request,
7 Bitrix\Main\Localization,
8 Bitrix\Main\Web\HttpClient,
9 Bitrix\Sale\Payment,
10 Bitrix\Sale\PaySystem,
11 Bitrix\Sale\PriceMaths,
12 Bitrix\Sale\PaymentCollection,
13 Bitrix\Currency;
14
16
21class SberbankOnlineHandler extends PaySystem\ServiceHandler implements PaySystem\IRefund
22{
23 protected const PAYMENT_OPERATION_DEPOSITED = 'deposited';
24 protected const PAYMENT_STATUS_SUCCESS = 1;
25
26 protected const RESPONSE_CODE_SUCCESS = 0;
27
28 protected const PAYMENT_STATE_CREATED = 'CREATED';
29
30 protected const PAYMENT_DELIMITER = '_';
31
45 public function initiatePay(Payment $payment, Request $request = null): PaySystem\ServiceResult
46 {
47 $result = new PaySystem\ServiceResult();
48 $params = [];
49 $checkOrderData = [];
50
51 if ($payment->getField('PS_INVOICE_ID'))
52 {
53 $checkOrderResult = $this->checkOrder($payment);
54 $checkOrderData = $checkOrderResult->getData();
55 }
56
57 if (!isset($checkOrderData['URL']))
58 {
59 $orderAttemptNumber = 0;
60 if (isset($checkOrderData['ORDER_ATTEMPT_NUMBER']))
61 {
62 $orderAttemptNumber = $checkOrderData['ORDER_ATTEMPT_NUMBER'];
63 $orderAttemptNumber = (int)$orderAttemptNumber + 1;
64 }
65
66 $createOrderResult = $this->createOrder($payment, $orderAttemptNumber);
67 if (!$createOrderResult->isSuccess())
68 {
69 $result->addErrors($createOrderResult->getErrors());
70 return $result;
71 }
72
73 $createOrderData = $createOrderResult->getData();
74 $params['URL'] = $createOrderData['URL'];
75 $result->setPsData($createOrderResult->getPsData());
76 }
77 else
78 {
79 $params['URL'] = $checkOrderData['URL'];
80 }
81
82 $urlComponentList = parse_url($params['URL']);
83 parse_str($urlComponentList['query'], $formParams);
84 $params['FORM_PARAMS'] = $formParams;
85
86 $params['CURRENCY'] = $payment->getField('CURRENCY');
87 $params['SUM'] = PriceMaths::roundPrecision($payment->getSum());
88 $this->setExtraParams($params);
89
90 $template = 'template_bank_card';
91 $showTemplateResult = $this->showTemplate($payment, $template);
92 if ($showTemplateResult->isSuccess())
93 {
94 $result->setTemplate($showTemplateResult->getTemplate());
95 }
96 else
97 {
98 $result->addErrors($showTemplateResult->getErrors());
99 }
100
101 if ($params['URL'])
102 {
103 $result->setPaymentUrl($params['URL']);
104 }
105
106 return $result;
107 }
108
118 private function checkOrder(Payment $payment): PaySystem\ServiceResult
119 {
120 $result = new PaySystem\ServiceResult();
121
122 $orderStatus = $this->getOrderStatus($payment);
123 if ($orderStatus->isSuccess())
124 {
125 $orderStatusData = $orderStatus->getData();
126 if (isset($orderStatusData['paymentAmountInfo']['paymentState']))
127 {
128 $paymentState = $orderStatusData['paymentAmountInfo']['paymentState'];
129 if (($paymentState === self::PAYMENT_STATE_CREATED)
130 && ((int)$payment->getSum() === (int)($orderStatusData['amount'] / 100))
131 )
132 {
133 $formUrl = $this->getUrl($payment, 'formUrl');
134 $params['URL'] = $formUrl.$payment->getField('PS_INVOICE_ID');
135 }
136 }
137
138 $orderNumber = $orderStatusData['orderNumber'];
139 $orderNumber = explode(self::PAYMENT_DELIMITER, $orderNumber);
140 $params['ORDER_ATTEMPT_NUMBER'] = $orderNumber[2] ?? 0;
141
142 $result->setData($params);
143 }
144
145 return $result;
146 }
147
161 private function createOrder(Payment $payment, $orderAttemptNumber): PaySystem\ServiceResult
162 {
163 $result = new PaySystem\ServiceResult();
164
165 $registerOrderResult = $this->registerOrder($payment, $orderAttemptNumber);
166 if (!$registerOrderResult->isSuccess())
167 {
168 $result->addErrors($registerOrderResult->getErrors());
169 return $result;
170 }
171
172 $sberbankResultData = $registerOrderResult->getData();
173 $result->setPsData(['PS_INVOICE_ID' => $sberbankResultData['orderId']]);
174 $params['URL'] = $sberbankResultData['formUrl'];
175
176 $result->setData($params);
177 return $result;
178 }
179
183 public function getCurrencyList(): array
184 {
185 return ['RUB'];
186 }
187
198 public function processRequest(Payment $payment, Request $request): PaySystem\ServiceResult
199 {
200 $result = new PaySystem\ServiceResult();
201
202 $inputJson = self::encode($request->toArray());
203 PaySystem\Logger::addDebugInfo(static::class.': request: '.$inputJson);
204
205 $secretKey = $this->getBusinessValue($payment, static::getDescriptionCode('SECRET_KEY'));
206 if ($secretKey && !$this->isCheckSumCorrect($request, $secretKey))
207 {
208 $result->addError(new Main\Error(Localization\Loc::getMessage('SALE_HPS_SBERBANK_ERROR_CHECK_SUM')));
209 return $result;
210 }
211
212 if ($request->get('operation') === static::PAYMENT_OPERATION_DEPOSITED
213 && (int)$request->get('status') === static::PAYMENT_STATUS_SUCCESS
214 )
215 {
216 $orderStatus = $this->getOrderStatus($payment);
217 if ($orderStatus->isSuccess())
218 {
219 $orderStatusData = $orderStatus->getData();
220 $description = Localization\Loc::getMessage('SALE_HPS_SBERBANK_ORDER_ID', [
221 '#ORDER_ID#' => $request->get('mdOrder')
222 ]);
223 $fields = [
224 'PS_INVOICE_ID' => $request->get('mdOrder'),
225 'PS_STATUS_CODE' => $request->get('operation'),
226 'PS_STATUS_DESCRIPTION' => $description,
227 'PS_SUM' => $orderStatusData['amount'] / 100,
228 'PS_STATUS' => 'N',
229 'PS_CURRENCY' => $orderStatusData['currency'],
230 'PS_RESPONSE_DATE' => new Main\Type\DateTime()
231 ];
232
233 if ($this->isSumCorrect($payment, $orderStatusData))
234 {
235 $fields['PS_STATUS'] = 'Y';
236
237 PaySystem\Logger::addDebugInfo(
238 static::class.': PS_CHANGE_STATUS_PAY='.$this->getBusinessValue($payment, 'PS_CHANGE_STATUS_PAY')
239 );
240
241 if ($this->getBusinessValue($payment, 'PS_CHANGE_STATUS_PAY') === 'Y')
242 {
243 $result->setOperationType(PaySystem\ServiceResult::MONEY_COMING);
244 }
245 }
246 else
247 {
248 $error = Localization\Loc::getMessage('SALE_HPS_SBERBANK_ERROR_SUM');
249 $fields['PS_STATUS_DESCRIPTION'] .= '. '.$error;
250 $result->addError(new Main\Error($error));
251 }
252
253 $result->setPsData($fields);
254 }
255 else
256 {
257 $result->addErrors($orderStatus->getErrors());
258 }
259 }
260 else
261 {
262 $error = Localization\Loc::getMessage('SALE_HPS_SBERBANK_ERROR_OPERATION', [
263 '#OPERATION#' => $request->get('operation'),
264 '#STATUS#' => $request->get('status')
265 ]);
266 $result->addError(new Main\Error($error));
267 }
268
269 return $result;
270 }
271
276 public function getPaymentIdFromRequest(Request $request)
277 {
278 $paymentId = $request->get('orderNumber');
279 [$paymentId] = explode(self::PAYMENT_DELIMITER, $paymentId);
280
281 return $paymentId;
282 }
283
287 public static function getIndicativeFields(): array
288 {
289 return ['mdOrder', 'orderNumber', 'checksum', 'operation', 'status'];
290 }
291
297 protected static function isMyResponseExtended(Request $request, $paySystemId): bool
298 {
299 // bx_paysystem_code for compatibility
300 $bxPaySystemCode = (int)$request->get('bx_paysystem_code');
301 if ($bxPaySystemCode)
302 {
303 return (int)$paySystemId === $bxPaySystemCode;
304 }
305
306 $orderNumber = $request->get('orderNumber');
307 if ($orderNumber)
308 {
309 $orderNumberPart = explode(self::PAYMENT_DELIMITER, $orderNumber);
310 $paySystemIdFromOrderNumber = (int)($orderNumberPart[1] ?? 0);
311
312 return (int)$paySystemId === $paySystemIdFromOrderNumber;
313 }
314
315 return false;
316 }
317
327 private function isSumCorrect(Payment $payment, array $paymentData): bool
328 {
329 $sberbankAmount = $paymentData['amount'] / 100;
330 PaySystem\Logger::addDebugInfo(
331 static::class.': requestSum='.PriceMaths::roundPrecision($sberbankAmount).'; paymentSum='.PriceMaths::roundPrecision($payment->getSum())
332 );
333
334 return PriceMaths::roundPrecision($sberbankAmount) === PriceMaths::roundPrecision($payment->getSum());
335 }
336
342 protected function isCheckSumCorrect(Request $request, $secretKey): bool
343 {
344 $requestParamList = $request->toArray();
345 $checksum = $requestParamList['checksum'];
346
347 unset($requestParamList['checksum']);
348 ksort($requestParamList);
349
350 $requestParam = '';
351 foreach ($requestParamList as $param => $value)
352 {
353 $requestParam .= $param.';'.$value.';';
354 }
355
356 $result = hash_hmac('sha256' , $requestParam , $secretKey);
357 if ($result === false)
358 {
359 return false;
360 }
361
362 $result = mb_strtoupper($result);
363 return ($result === mb_strtoupper($checksum));
364 }
365
379 protected function registerOrder(Payment $payment, $attempt = 0): PaySystem\ServiceResult
380 {
381 $result = new PaySystem\ServiceResult();
382
383 $url = $this->getUrl($payment, 'register.do');
384 $params = $this->getMerchantParams($payment);
385 $params = array_merge($params, $this->getRegisterOrderParams($payment, $attempt));
386
387 $sendResult = $this->send($url, $params);
388 if (!$sendResult->isSuccess())
389 {
390 $result->addErrors($sendResult->getErrors());
391 return $result;
392 }
393
394 $response = $sendResult->getData();
395 $result->setData($response);
396
397 return $result;
398 }
399
409 protected function getOrderStatus(Payment $payment): PaySystem\ServiceResult
410 {
411 $result = new PaySystem\ServiceResult();
412
413 $url = $this->getUrl($payment, 'getOrderStatusExtended.do');
414 $params = $this->getMerchantParams($payment);
415 $params['orderId'] = $payment->getField('PS_INVOICE_ID');
416
417 $sendResult = $this->send($url, $params);
418 if (!$sendResult->isSuccess())
419 {
420 $result->addErrors($sendResult->getErrors());
421 return $result;
422 }
423
424 $response = $sendResult->getData();
425 $result->setData($response);
426
427 return $result;
428 }
429
440 public function refund(Payment $payment, $refundableSum): PaySystem\ServiceResult
441 {
442 $result = new PaySystem\ServiceResult();
443
444 $url = $this->getUrl($payment, 'refund.do');
445 $params = $this->getMerchantParams($payment);
446 $params['orderId'] = $payment->getField('PS_INVOICE_ID');
447 $params['amount'] = $refundableSum * 100;
448
449 $sendResult = $this->send($url, $params);
450 if (!$sendResult->isSuccess())
451 {
452 $result->addErrors($sendResult->getErrors());
453
454 $error = static::class.': refund: '.implode("\n", $sendResult->getErrorMessages());
455 PaySystem\Logger::addError($error);
456
457 return $result;
458 }
459
460 $result->setOperationType(PaySystem\ServiceResult::MONEY_LEAVING);
461
462 return $result;
463 }
464
475 private function send($url, array $params = []): PaySystem\ServiceResult
476 {
477 $result = new PaySystem\ServiceResult();
478
479 $httpClient = new HttpClient();
480 $httpClient->disableSslVerification();
481
482 $postData = static::encode($params);
483 PaySystem\Logger::addDebugInfo(
484 static::class.': request data: '.$postData
485 );
486
487 $response = $httpClient->post($url, $params);
488 if ($response === false)
489 {
490 $errors = $httpClient->getError();
491 foreach ($errors as $code => $message)
492 {
493 $result->addError(new Main\Error($message, $code));
494 }
495
496 return $result;
497 }
498
499 PaySystem\Logger::addDebugInfo(
500 static::class.': response data: '.$response
501 );
502
503 $response = static::decode($response);
504 if ($response)
505 {
506 if (!empty($response['errorCode']) && (int)$response['errorCode'] !== self::RESPONSE_CODE_SUCCESS)
507 {
508 $result->addError(new Main\Error($response['errorMessage'], $response['errorCode']));
509 }
510 else
511 {
512 $result->setData($response);
513 }
514 }
515 else
516 {
517 $result->addError(new Main\Error(Localization\Loc::getMessage('SALE_HPS_SBERBANK_ERROR_DECODE_RESPONSE')));
518 }
519
520 return $result;
521 }
522
527 protected function isTestMode(Payment $payment = null): bool
528 {
529 return $this->getBusinessValue($payment, static::getDescriptionCode('TEST_MODE')) === 'Y';
530 }
531
535 protected function getUrlList()
536 {
537 $testUrl = 'https://3dsec.sberbank.ru/payment/';
538 $activeUrl = 'https://securepayments.sberbank.ru/payment/';
539
540 return [
541 'register.do' => [
542 self::TEST_URL => $testUrl . 'rest/register.do',
543 self::ACTIVE_URL => $activeUrl . 'rest/register.do',
544 ],
545 'getOrderStatusExtended.do' => [
546 self::TEST_URL => $testUrl . 'rest/getOrderStatusExtended.do',
547 self::ACTIVE_URL => $activeUrl . 'rest/getOrderStatusExtended.do',
548 ],
549 'refund.do' => [
550 self::TEST_URL => $testUrl . 'rest/refund.do',
551 self::ACTIVE_URL => $activeUrl . 'rest/refund.do',
552 ],
553 'formUrl' => [
554 self::TEST_URL => $testUrl . 'merchants/sbersafe_sberid/payment_ru.html?mdOrder=',
555 self::ACTIVE_URL => $activeUrl . 'merchants/sbersafe_sberid/payment_ru.html?mdOrder=',
556 ],
557 ];
558 }
559
564 protected function getMerchantParams(Payment $payment): array
565 {
566 return [
567 'userName' => $this->getBusinessValue($payment, static::getDescriptionCode('LOGIN')),
568 'password' => $this->getBusinessValue($payment, static::getDescriptionCode('PASSWORD')),
569 ];
570 }
571
583 protected function getRegisterOrderParams(Payment $payment, int $attempt): array
584 {
585 $jsonParams = [
586 // bx_paysystem_code for compatibility
587 'bx_paysystem_code' => $this->service->getField('ID'),
588 'bx_label' => $this->getLabelName(),
589 ];
590
591 $params = [
592 'orderNumber' => $this->getOrderNumber($payment, $attempt),
593 'amount' => (int)PriceMaths::roundPrecision($payment->getSum() * 100),
594 'returnUrl' => $this->getSuccessUrl($payment),
595 'failUrl' => $this->getFailUrl($payment),
596 'jsonParams' => self::encode($jsonParams)
597 ];
598
599 $currency = Currency\CurrencyTable::getById($payment->getField('CURRENCY'))->fetch();
600 if (!empty($currency['NUMCODE']))
601 {
602 $params['currency'] = $currency['NUMCODE'];
603 }
604
605 $params['language'] = LANGUAGE_ID;
606 $params['description'] = $this->getOrderDescription($payment);
607
608 return $params;
609 }
610
611 private function getOrderNumber(Payment $payment, int $attempt): string
612 {
613 $orderNumberPart = [
614 $payment->getId(),
615 $payment->getPaymentSystemId(),
616 ];
617
618 if ($attempt)
619 {
620 $orderNumberPart[] = $attempt;
621 }
622
623 return implode(self::PAYMENT_DELIMITER, $orderNumberPart);
624 }
625
629 private function getLabelName(): string
630 {
631 return '1c_bitrix_'.$this->service->getField('ACTION_FILE');
632 }
633
638 private function getSuccessUrl(Payment $payment)
639 {
640 return $this->getBusinessValue($payment, static::getDescriptionCode('RETURN_SUCCESS_URL'))
641 ?: $this->service->getContext()->getUrl();
642 }
643
648 private function getFailUrl(Payment $payment)
649 {
650 return $this->getBusinessValue($payment, static::getDescriptionCode('RETURN_FAIL_URL'))
651 ?: $this->service->getContext()->getUrl();
652 }
653
663 protected function getOrderDescription(Payment $payment)
664 {
666 $collection = $payment->getCollection();
667 $order = $collection->getOrder();
668 $userEmail = $order->getPropertyCollection()->getUserEmail();
669
670 return str_replace(
671 [
672 '#PAYMENT_NUMBER#',
673 '#ORDER_NUMBER#',
674 '#PAYMENT_ID#',
675 '#ORDER_ID#',
676 '#USER_EMAIL#'
677 ],
678 [
679 $payment->getField('ACCOUNT_NUMBER'),
680 $order->getField('ACCOUNT_NUMBER'),
681 $payment->getId(),
682 $order->getId(),
683 ($userEmail) ? $userEmail->getValue() : ''
684 ],
685 $this->getBusinessValue($payment, static::getDescriptionCode('ORDER_DESCRIPTION'))
686 );
687 }
688
694 private static function encode(array $data)
695 {
696 return Main\Web\Json::encode($data, JSON_UNESCAPED_UNICODE);
697 }
698
703 private static function decode($data)
704 {
705 try
706 {
707 return Main\Web\Json::decode($data);
708 }
709 catch (Main\ArgumentException $exception)
710 {
711 return false;
712 }
713 }
714
719 protected static function getDescriptionCode(string $code): ?string
720 {
721 return static::getDescriptionCodesMap()[$code] ?? null;
722 }
723
727 protected static function getDescriptionCodesMap(): array
728 {
729 return [
730 'LOGIN' => 'SBERBANK_LOGIN',
731 'PASSWORD' => 'SBERBANK_PASSWORD',
732 'SECRET_KEY' => 'SBERBANK_SECRET_KEY',
733 'RETURN_SUCCESS_URL' => 'SBERBANK_RETURN_SUCCESS_URL',
734 'RETURN_FAIL_URL' => 'SBERBANK_RETURN_FAIL_URL',
735 'ORDER_DESCRIPTION' => 'SBERBANK_ORDER_DESCRIPTION',
736 'TEST_MODE' => 'SBERBANK_TEST_MODE',
737 ];
738 }
739}
if(!Loader::includeModule('catalog')) if(!AccessController::getCurrent() ->check(ActionDictionary::ACTION_PRICE_EDIT)) if(!check_bitrix_sessid()) $request
Определения catalog_reindex.php:36
static loadMessages($file)
Определения loc.php:65
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
$template
Определения file_edit.php:49
</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
if(!is_null($config))($config as $configItem)(! $configItem->isVisible()) $code
Определения options.php:195
$value
Определения Param.php:39
trait Error
Определения error.php:11
$payment
Определения payment.php:14
$order
Определения payment.php:8
$message
Определения payment.php:8
$orderNumber
Определения payment.php:7
$currency
Определения template.php:266
if($inWords) echo htmlspecialcharsbx(Number2Word_Rus(roundEx($totalVatSum $params['CURRENCY']
Определения template.php:799
$response
Определения result.php:21
$postData
Определения index.php:29
$error
Определения subscription_card_product.php:20
$url
Определения iframe.php:7
$fields
Определения yandex_run.php:501