1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
handler.php
См. документацию.
1<?php
2
3namespace Sale\Handlers\PaySystem;
4
5use Bitrix\Main\Localization;
6use Bitrix\Main;
7use Bitrix\Main\Localization\Loc;
8use Bitrix\Main\Request;
9use Bitrix\Main\Web\HttpClient;
10use Bitrix\Sale\Cashbox;
11use Bitrix\Sale\Payment;
12use Bitrix\Sale\PaymentCollection;
13use Bitrix\Sale\PaySystem;
14use Bitrix\Sale\PriceMaths;
15use Bitrix\Sale\Services\Base\RestrictionInfo;
16use Bitrix\Sale\Services\Base\RestrictionInfoCollection;
17use Bitrix\Seo;
18
20
25class YandexCheckoutHandler
27 implements
33{
34 const CMS_NAME = 'api_1c-bitrix';
35
39 public const PAYMENT_STATUS_WAITING_FOR_CAPTURE = 'waiting_for_capture';
43 public const PAYMENT_STATUS_PENDING = 'pending';
47 public const AUTH_TYPE = 'yandex';
48
49 public const PAYMENT_STATUS_SUCCEEDED = 'succeeded';
50 public const PAYMENT_STATUS_CANCELED = 'canceled';
51
52 public const PAYMENT_METHOD_SMART = '';
53 public const PAYMENT_METHOD_ALFABANK = 'alfabank';
54 public const PAYMENT_METHOD_BANK_CARD = 'bank_card';
55 public const PAYMENT_METHOD_YANDEX_MONEY = 'yoo_money';
56 public const PAYMENT_METHOD_SBERBANK = 'sberbank';
57 public const PAYMENT_METHOD_CASH = 'cash';
58 public const PAYMENT_METHOD_EMBEDDED = 'embedded';
59 public const PAYMENT_METHOD_TINKOFF_BANK = 'tinkoff_bank';
60 public const PAYMENT_METHOD_SBP = 'sbp';
61 public const PAYMENT_METHOD_SBER_LOAN = 'sber_loan';
62
63 public const MODE_SMART = '';
64 public const MODE_ALFABANK = 'alfabank';
65 public const MODE_BANK_CARD = 'bank_card';
66 public const MODE_YANDEX_MONEY = 'yoo_money';
67 public const MODE_SBERBANK = 'sberbank';
68 public const MODE_SBERBANK_SMS = 'sberbank_sms';
69 public const MODE_SBERBANK_QR = 'sberbank_qr';
70 public const MODE_CASH = 'cash';
71 public const MODE_MOBILE_BALANCE = 'mobile_balance';
72 public const MODE_EMBEDDED = 'embedded';
73 public const MODE_TINKOFF_BANK = 'tinkoff_bank';
74 public const MODE_SBP = 'sbp';
75 public const MODE_SBER_LOAN = 'sber_loan';
76
77 public const URL = 'https://api.yookassa.ru/v3';
78
79 private const CALLBACK_IP_LIST = [
80 '185.71.76.0/27',
81 '185.71.77.0/27',
82 '77.75.153.0/25',
83 '77.75.154.128/25',
84 '77.75.156.11',
85 '77.75.156.35',
86 ];
87
88 private const CONFIRMATION_TYPE_REDIRECT = "redirect";
89 private const CONFIRMATION_TYPE_EXTERNAL = "external";
90 private const CONFIRMATION_TYPE_EMBEDDED = "embedded";
91 private const CONFIRMATION_TYPE_QR = 'qr';
92
93 private const SEND_METHOD_HTTP_POST = "POST";
94 private const SEND_METHOD_HTTP_GET = "GET";
95
96 use PaySystem\Cashbox\CheckTrait;
97
111 public function initiatePay(Payment $payment, Request $request = null)
112 {
113 if ($request === null)
114 {
115 $request = Main\Context::getCurrent()->getRequest();
116 }
117
118 $result = new PaySystem\ServiceResult();
119
120 $yandexPaymentData = [];
121
122 if ($payment->getField("PS_INVOICE_ID"))
123 {
124 $yandexPaymentResult = $this->getYandexPayment($payment);
125 if ($yandexPaymentResult->isSuccess())
126 {
127 $yandexPaymentData = $yandexPaymentResult->getData();
128 }
129 }
130
131 $isNeedCreate = $this->needCreateYandexPayment($payment, $request, $yandexPaymentData);
132 if ($isNeedCreate)
133 {
134 $createYandexPaymentResult = $this->createYandexPayment($payment, $request);
135 if (!$createYandexPaymentResult->isSuccess())
136 {
137 return $createYandexPaymentResult;
138 }
139
140 $yandexPaymentData = $createYandexPaymentResult->getData();
141
142 if (isset($yandexPaymentData['id']))
143 {
144 $result->setPsData(['PS_INVOICE_ID' => $yandexPaymentData['id']]);
145 }
146 }
147
148 $template = $this->getTemplateName($request, $yandexPaymentData);
149
150 $this->setExtraParams(
151 $this->getTemplateParams($payment, $template, $yandexPaymentData)
152 );
153
154 $showTemplateResult = $this->showTemplate($payment, $template);
155 if ($showTemplateResult->isSuccess())
156 {
157 $result->setTemplate($showTemplateResult->getTemplate());
158 }
159 else
160 {
161 $result->addErrors($showTemplateResult->getErrors());
162 }
163
164 if ($isNeedCreate && !empty($yandexPaymentData['confirmation']['confirmation_url']))
165 {
166 $result->setPaymentUrl($yandexPaymentData['confirmation']['confirmation_url']);
167 }
168
169 if (!empty($yandexPaymentData['confirmation']['confirmation_data']) && $this->isQrPaymentType())
170 {
171 $qrCode = self::generateQrCode($yandexPaymentData['confirmation']['confirmation_data']);
172 if ($qrCode)
173 {
174 $result->setQr(base64_encode($qrCode));
175 }
176 }
177
178 return $result;
179 }
180
187 protected function needCreateYandexPayment(Payment $payment, Request $request, $additionalParams = []): bool
188 {
189 if (($additionalParams['status'] ?? '') === self::PAYMENT_STATUS_SUCCEEDED)
190 {
191 return false;
192 }
193
194 $template = $this->getTemplateName($request, $additionalParams);
195
196 return $template !== 'template_query';
197 }
198
212 protected function getTemplateParams(Payment $payment, $template, $additionalParams = []) : array
213 {
214 $params = [
215 'SUM' => PriceMaths::roundPrecision($payment->getSum()),
216 'CURRENCY' => $payment->getField('CURRENCY'),
217 ];
218
219 if ($template === 'template')
220 {
221 $params['URL'] = $additionalParams['confirmation']['confirmation_url'] ?? '';
222 }
223 elseif ($template === 'template_query')
224 {
225 $phoneFields = $this->getPhoneFields();
226 $phoneFields = $phoneFields[$this->service->getField('PS_MODE')] ?? [];
227
228 $params['FIELDS'] = $this->getPaymentMethodFields();
229 $params['PHONE_FIELDS'] = $phoneFields;
230 $params['PHONE_NUMBER'] = $this->getPhoneNumber($payment) ?? "";
231 $params['PAYMENT_METHOD'] = $this->service->getField('PS_MODE');
232 $params['PAYMENT_ID'] = $payment->getId();
233 $params['PAYSYSTEM_ID'] = $this->service->getField('ID');
234 $params['RETURN_URL'] = $this->getReturnUrl($payment);
235 }
236 elseif ($template === 'template_embedded')
237 {
238 $params['CONFIRMATION_TOKEN'] = $additionalParams['confirmation']['confirmation_token'] ?? '';
239 $params['RETURN_URL'] = $this->getReturnUrl($payment);
240 }
241 elseif ($template === 'template_qr')
242 {
243 $params['URL'] = $additionalParams['confirmation']['confirmation_data'] ?? '';
244 $params['QR_CODE_IMAGE'] = '';
245
246 $qrCode = self::generateQrCode($params['URL']);
247 if ($qrCode)
248 {
249 $params['QR_CODE_IMAGE'] = base64_encode($qrCode);
250 }
251 }
252
253 return $params;
254 }
255
261 protected function getTemplateName(Request $request, $additionalParams = []): string
262 {
263 $template = null;
264
265 if (isset($additionalParams["status"])
266 && $additionalParams["status"] === self::PAYMENT_STATUS_SUCCEEDED
267 )
268 {
269 return "template_success";
270 }
271
272 if ($this->hasPaymentMethodFields() &&
273 !$this->isFillPaymentMethodFields($request)
274 )
275 {
276 $template = "template_query";
277 }
278 elseif ($this->isSetExternalPaymentType())
279 {
280 $template = "template_success";
281 }
282 elseif ($this->isSetEmbeddedPaymentType())
283 {
284 $template = "template_embedded";
285 }
286 elseif ($this->isQrPaymentType())
287 {
288 $template = "template_qr";
289 }
290
291 return $template ?? "template";
292 }
293
297 private function isSetExternalPaymentType(): bool
298 {
299 $externalPayment = [
300 static::MODE_ALFABANK,
301 static::MODE_SBERBANK_SMS
302 ];
303
304 return in_array($this->service->getField('PS_MODE'), $externalPayment, true);
305 }
306
310 private function isSetEmbeddedPaymentType(): bool
311 {
312 return $this->service->getField('PS_MODE') === static::MODE_EMBEDDED;
313 }
314
315 private function isQrPaymentType(): bool
316 {
317 $qrPayment = [
318 static::MODE_SBP,
319 static::MODE_SBERBANK_QR,
320 ];
321
322 return in_array($this->service->getField('PS_MODE'), $qrPayment, true);
323 }
324
338 private function createYandexPayment(Payment $payment, Request $request, bool $isRepeated = false): PaySystem\ServiceResult
339 {
340 $result = new PaySystem\ServiceResult();
341
342 $url = $this->getUrl($payment, 'pay');
343 $headers = $this->getHeaders($payment);
344
345 if ($isRepeated)
346 {
347 $params = $this->getYandexRepeatedPaymentQueryParams($payment, $request);
348 }
349 else
350 {
351 $params = $this->getYandexPaymentQueryParams($payment, $request);
352 }
353
354 if ($this->service->canPrintCheckSelf($payment))
355 {
356 $receiptResult = $this->getReceipt($payment);
357 if (!$receiptResult->isSuccess())
358 {
359 $result->addErrors($receiptResult->getErrors());
360 return $result;
361 }
362
363 $receiptData = $receiptResult->getData();
364 $params['receipt'] = $receiptData['receipt'];
365
366 PaySystem\Logger::addDebugInfo(__CLASS__ . ": receipt = " . self::encode($receiptData['receipt']));
367 }
368
369 $sendResult = $this->send(self::SEND_METHOD_HTTP_POST, $url, $headers, $params);
370 if (!$sendResult->isSuccess())
371 {
372 $result->addErrors($sendResult->getErrors());
373 return $result;
374 }
375
376 $response = $sendResult->getData();
377
378 $verificationResult = $this->verifyYandexPayment($response);
379 if ($verificationResult->isSuccess())
380 {
381 $result->setData($response);
382 }
383 else
384 {
385 $result->addErrors($verificationResult->getErrors());
386 }
387
388 return $result;
389 }
390
395 private function verifyYandexPayment($response): PaySystem\ServiceResult
396 {
397 $result = new PaySystem\ServiceResult();
398
399 if ($response['status'] === static::PAYMENT_STATUS_CANCELED)
400 {
401 $error = Localization\Loc::getMessage(
402 'SALE_HPS_YANDEX_CHECKOUT_RESPONSE_ERROR_' . mb_strtoupper($response['cancellation_details']['reason'])
403 );
404 if ($error)
405 {
406 $result->addError(
407 PaySystem\Error::createForBuyer($error, $response['cancellation_details']['party'])
408 );
409 }
410 else
411 {
412 $result->addError(
413 PaySystem\Error::create(Localization\Loc::getMessage('SALE_HPS_YANDEX_CHECKOUT_ERROR_PAYMENT_CANCELED'))
414 );
415 }
416 }
417
418 return $result;
419 }
420
424 private function getIdempotenceKey(): string
425 {
426 return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
427 mt_rand(0, 0xffff), mt_rand(0, 0xffff),
428 mt_rand(0, 0xffff),
429 mt_rand(0, 0x0fff) | 0x4000,
430 mt_rand(0, 0x3fff) | 0x8000,
431 mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
432 );
433 }
434
447 private function send($method, $url, array $headers, array $params = array()): PaySystem\ServiceResult
448 {
449 $result = new PaySystem\ServiceResult();
450
451 $httpClient = new HttpClient();
452 foreach ($headers as $name => $value)
453 {
454 $httpClient->setHeader($name, $value);
455 }
456
457 if ($method === self::SEND_METHOD_HTTP_GET)
458 {
459 $response = $httpClient->get($url);
460 }
461 else
462 {
463 $postData = null;
464 if ($params)
465 {
466 $postData = static::encode($params);
467 }
468
469 PaySystem\Logger::addDebugInfo(__CLASS__.': request data: '.$postData);
470
471 $response = $httpClient->post($url, $postData);
472 }
473
474 if ($response === false)
475 {
476 $errors = $httpClient->getError();
477 if ($errors)
478 {
479 $errorMessages = [];
480 foreach ($errors as $code => $message)
481 {
482 $errorMessages[] = "{$code}={$message}";
483 }
484
485 PaySystem\Logger::addDebugInfo(
486 __CLASS__ . ': response error: ' . implode(', ', $errorMessages)
487 );
488 }
489
490 $result->addError(new Main\Error(Localization\Loc::getMessage('SALE_HPS_YANDEX_CHECKOUT_ERROR_QUERY')));
491 return $result;
492 }
493
494 PaySystem\Logger::addDebugInfo(__CLASS__.': response data: '.$response);
495
496 $response = static::decode($response);
497
498 $httpStatus = $httpClient->getStatus();
499 if ($httpStatus === 200)
500 {
501 $result->setData($response);
502 }
503 elseif ($httpStatus !== 201)
504 {
505 if ($httpStatus === 401 && self::isOAuth())
506 {
507 $error = Localization\Loc::getMessage('SALE_HPS_YANDEX_CHECKOUT_HTTP_STATUS_OAUTH_'.$httpStatus.'');
508 }
509 else
510 {
511 $error = Localization\Loc::getMessage('SALE_HPS_YANDEX_CHECKOUT_HTTP_STATUS_'.$httpStatus);
512 }
513
514 if ($error)
515 {
516 $result->addError(PaySystem\Error::create($error));
517 }
518 elseif (isset($response['type']) && $response['type'] === 'error')
519 {
520 $result->addError(PaySystem\Error::create($response['description']));
521 }
522 }
523
524 return $result;
525 }
526
536 private function getYandexBasePaymentQueryParams(Payment $payment): array
537 {
538 return [
539 'description' => $this->getPaymentDescription($payment),
540 'amount' => [
541 'value' => (string)PriceMaths::roundPrecision($payment->getSum()),
542 'currency' => $payment->getField('CURRENCY'),
543 ],
544 'capture' => true,
545 'metadata' => [
546 'BX_PAYMENT_NUMBER' => $payment->getId(),
547 'BX_PAYSYSTEM_CODE' => $this->service->getField('ID'),
548 'BX_HANDLER' => 'YANDEX_CHECKOUT',
549 'cms_name' => static::CMS_NAME,
550 ],
551 ];
552 }
553
564 protected function getYandexPaymentQueryParams(Payment $payment, Request $request)
565 {
566 $query = $this->getYandexBasePaymentQueryParams($payment);
567
568 $query['confirmation'] = [
569 'type' => self::CONFIRMATION_TYPE_REDIRECT,
570 'return_url' => $this->getReturnUrl($payment),
571 ];
572
573 $articleId = $this->getBusinessValue($payment, 'YANDEX_CHECKOUT_SHOP_ARTICLE_ID');
574 if ($articleId)
575 {
576 $query['recipient'] = ['gateway_id' => $articleId];
577 }
578
579 if ($this->isSetEmbeddedPaymentType())
580 {
581 $query['confirmation'] = [
582 'type' => self::CONFIRMATION_TYPE_EMBEDDED,
583 ];
584 }
585 elseif ($this->isQrPaymentType())
586 {
587 $query['confirmation'] = [
588 'type' => self::CONFIRMATION_TYPE_QR,
589 ];
590 $query['payment_method_data'] = [
591 'type' => $this->getYandexHandlerType($this->service->getField('PS_MODE')),
592 ];
593 }
594 elseif ($this->service->getField('PS_MODE') !== static::MODE_SMART)
595 {
596 $query['capture'] = true;
597 $query['payment_method_data'] = [
598 'type' => $this->getYandexHandlerType($this->service->getField('PS_MODE'))
599 ];
600
601 if ($this->isSetExternalPaymentType())
602 {
603 $query['confirmation'] = [
604 'type' => self::CONFIRMATION_TYPE_EXTERNAL,
605 ];
606 }
607
608 if ($this->hasPaymentMethodFields())
609 {
610 $fields = $this->getPaymentMethodFields();
611 if ($fields)
612 {
613 foreach ($fields as $field)
614 {
615 $fieldValue = $request->get($field);
616 if ($this->isPhone($field))
617 {
618 $fieldValue = $this->normalizePhone($request->get($field));
619 }
620 $query['payment_method_data'][$field] = $fieldValue;
621 }
622 }
623 }
624 }
625
626 if ($this->isRecurring($payment) && !self::isOAuth())
627 {
628 $query['save_payment_method'] = true;
629 }
630
631 return $query;
632 }
633
634 private function getReceipt(Payment $payment): PaySystem\ServiceResult
635 {
636 $result = new PaySystem\ServiceResult();
637
638 $checkQueryResult = $this->buildCheckQuery($payment);
639 if ($checkQueryResult->isSuccess())
640 {
641 $receiptData = $checkQueryResult->getData();
642 if (!empty($receiptData['items']) && !empty($receiptData['customer']))
643 {
644 $result->setData([
645 'receipt' => $receiptData,
646 ]);
647 }
648 else
649 {
650 $result->addError(PaySystem\Error::create(Loc::getMessage('SALE_HPS_YANDEX_CHECKOUT_ERROR_EMPTY_RECEIPT')));
651 }
652 }
653 else
654 {
655 $result->addErrors($checkQueryResult->getErrors());
656 }
657
658 return $result;
659 }
660
671 private function getYandexRepeatedPaymentQueryParams(Payment $payment, Request $request): array
672 {
673 $query = $this->getYandexBasePaymentQueryParams($payment);
674 $query['payment_method_id'] = $payment->getField('PS_RECURRING_TOKEN');
675
676 return $query;
677 }
678
683 private function getReturnUrl(Payment $payment)
684 {
685 return $this->getBusinessValue($payment, 'YANDEX_CHECKOUT_RETURN_URL') ?: $this->service->getContext()->getUrl();
686 }
687
697 protected function getPaymentDescription(Payment $payment)
698 {
700 $collection = $payment->getCollection();
701 $order = $collection->getOrder();
702 $userEmail = $order->getPropertyCollection()->getUserEmail();
703
704 $description = str_replace(
705 [
706 '#PAYMENT_NUMBER#',
707 '#ORDER_NUMBER#',
708 '#PAYMENT_ID#',
709 '#ORDER_ID#',
710 '#USER_EMAIL#'
711 ],
712 [
713 $payment->getField('ACCOUNT_NUMBER'),
714 $order->getField('ACCOUNT_NUMBER'),
715 $payment->getId(),
716 $order->getId(),
717 ($userEmail) ? $userEmail->getValue() : ''
718 ],
719 $this->getBusinessValue($payment, 'YANDEX_CHECKOUT_DESCRIPTION')
720 );
721
722 return mb_substr($description, 0, 128);
723 }
724
735 private function getPhoneNumber(Payment $payment): ?string
736 {
737 $phoneNumber = null;
738
740 $collection = $payment->getCollection();
741 $order = $collection->getOrder();
742
743 if ($order instanceof \Bitrix\Crm\Order\Order
744 && $clientCollection = $order->getContactCompanyCollection()
745 )
746 {
747 $primaryClient = $clientCollection->getPrimaryContact();
748 $entityId = \CCrmOwnerType::ContactName;
749
750 if ($primaryClient === null)
751 {
752 $primaryClient = $clientCollection->getPrimaryCompany();
753 $entityId = \CCrmOwnerType::CompanyName;
754 }
755
756 if ($primaryClient)
757 {
758 $clientId = $primaryClient->getField('ENTITY_ID');
759 $crmFieldMultiResult = \CCrmFieldMulti::GetList(
760 ['ID' => 'desc'],
761 [
762 'ENTITY_ID' => $entityId,
763 'ELEMENT_ID' => $clientId,
764 'TYPE_ID' => 'PHONE',
765 ]
766 );
767 while ($crmFieldMultiData = $crmFieldMultiResult->Fetch())
768 {
769 $phoneNumber = $crmFieldMultiData['VALUE'];
770 if ($phoneNumber)
771 {
772 break;
773 }
774 }
775 }
776 }
777
778 if (!$phoneNumber)
779 {
780 $phoneNumberProp = $order->getPropertyCollection()->getPhone();
781 if ($phoneNumberProp)
782 {
783 $phoneNumber = $phoneNumberProp->getValue();
784 }
785 }
786
787 return $phoneNumber ? $this->normalizePhone($phoneNumber) : null;
788 }
789
794 private function getBasicAuthString(Payment $payment): string
795 {
796 return base64_encode(
797 trim((string)$this->getBusinessValue($payment, 'YANDEX_CHECKOUT_SHOP_ID'))
798 . ':'
799 . trim((string)$this->getBusinessValue($payment, 'YANDEX_CHECKOUT_SECRET_KEY'))
800 );
801 }
802
808 private static function encode(array $data)
809 {
810 return Main\Web\Json::encode($data, JSON_UNESCAPED_UNICODE);
811 }
812
817 private static function decode($data)
818 {
819 try
820 {
821 return Main\Web\Json::decode($data);
822 }
823 catch (Main\ArgumentException $exception)
824 {
825 return false;
826 }
827 }
828
832 public function getCurrencyList()
833 {
834 return ['RUB'];
835 }
836
842 public function processRequest(Payment $payment, Request $request)
843 {
844 $result = new PaySystem\ServiceResult();
845
846 $checkIpResult = $this->checkIpAddress();
847 if (!$checkIpResult->isSuccess())
848 {
849 $result->addErrors($checkIpResult->getErrors());
850 return $result;
851 }
852
853 $inputStream = static::readFromStream();
854
855 $data = static::decode($inputStream);
856 if ($data !== false)
857 {
858 $response = $data['object'];
859 if ($response['status'] === static::PAYMENT_STATUS_SUCCEEDED)
860 {
861 $this->processSuccessRequest($payment, $response, $result);
862 }
863 elseif (
864 $response['status'] === static::PAYMENT_STATUS_CANCELED
865 && $payment->getField('PS_INVOICE_ID') === $response['id']
866 )
867 {
868 $this->processCancelRequest($response, $result);
869 }
870 }
871 else
872 {
873 $result->addError(PaySystem\Error::create(Localization\Loc::getMessage('SALE_HPS_YANDEX_CHECKOUT_ERROR_QUERY')));
874 }
875
876 return $result;
877 }
878
879 private function processSuccessRequest(Payment $payment, array $response, PaySystem\ServiceResult $result): void
880 {
881 $description = Localization\Loc::getMessage('SALE_HPS_YANDEX_CHECKOUT_TRANSACTION') . $response['id'];
882 $fields = [
883 'PS_INVOICE_ID' => $response['id'],
884 'PS_STATUS_CODE' => $response['status'],
885 'PS_STATUS_DESCRIPTION' => $description,
886 'PS_SUM' => $response['amount']['value'],
887 'PS_STATUS' => 'N',
888 'PS_CURRENCY' => $response['amount']['currency'],
889 'PS_RESPONSE_DATE' => new Main\Type\DateTime()
890 ];
891
892 if ($response['payment_method']['saved'])
893 {
894 $fields['PS_RECURRING_TOKEN'] = $response['payment_method']['id'];
895 }
896
897 if ($this->isSumCorrect($payment, $response))
898 {
899 $fields["PS_STATUS"] = 'Y';
900
901 PaySystem\Logger::addDebugInfo(
902 __CLASS__ . ': PS_CHANGE_STATUS_PAY=' . $this->getBusinessValue($payment, 'PS_CHANGE_STATUS_PAY')
903 );
904
905 if ($this->getBusinessValue($payment, 'PS_CHANGE_STATUS_PAY') === 'Y')
906 {
907 $result->setOperationType(PaySystem\ServiceResult::MONEY_COMING);
908 }
909 }
910 else
911 {
912 $error = Localization\Loc::getMessage('SALE_HPS_YANDEX_CHECKOUT_ERROR_SUM');
913 $fields['PS_STATUS_DESCRIPTION'] .= ' ' . $error;
914 $result->addError(PaySystem\Error::create($error));
915 }
916
917 $result->setPsData($fields);
918 }
919
920 private function processCancelRequest(array $response, PaySystem\ServiceResult $result): void
921 {
922 $cancellationParty = $response['cancellation_details']['party'];
923 $cancellationReason = $response['cancellation_details']['reason'];
924
925 $party = Localization\Loc::getMessage(
926 'SALE_HPS_YANDEX_CHECKOUT_REQUEST_CANCEL_PARTY_' . mb_strtoupper($cancellationParty)
927 );
928 if (!$party)
929 {
930 $party = $cancellationParty;
931 }
932
933 $reason = Localization\Loc::getMessage(
934 'SALE_HPS_YANDEX_CHECKOUT_REQUEST_CANCEL_REASON_' . mb_strtoupper($cancellationReason)
935 );
936 if (!$reason)
937 {
938 $reason = $cancellationReason;
939 }
940
941 $description = implode(
942 '. ',
943 [
944 Localization\Loc::getMessage(
945 'SALE_HPS_YANDEX_CHECKOUT_REQUEST_CANCEL_PARTY',
946 [
947 '#PARTY#' => $party,
948 ]
949 ),
950 Localization\Loc::getMessage(
951 'SALE_HPS_YANDEX_CHECKOUT_REQUEST_CANCEL_REASON',
952 [
953 '#REASON#' => $reason,
954 ]
955 )
956 ]
957 );
958
959 $fields = [
960 'PS_STATUS_CODE' => $response['status'],
961 'PS_STATUS' => 'N',
962 'PS_RESPONSE_DATE' => new Main\Type\DateTime(),
963 'PS_STATUS_DESCRIPTION' => $description,
964 ];
965
966 $result->setPsData($fields);
967 $result->addError(
968 PaySystem\Error::create(Localization\Loc::getMessage('SALE_HPS_YANDEX_CHECKOUT_ERROR_PAYMENT_CANCELED'))
969 );
970 }
971
981 private function isSumCorrect(Payment $payment, array $paymentData)
982 {
983 PaySystem\Logger::addDebugInfo(
984 __CLASS__.': yandexSum='.PriceMaths::roundPrecision($paymentData['amount']['value'])."; paymentSum=".PriceMaths::roundPrecision($payment->getSum())
985 );
986
987 return PriceMaths::roundPrecision($paymentData['amount']['value']) === PriceMaths::roundPrecision($payment->getSum());
988 }
989
999 private function getYandexPayment(Payment $payment): PaySystem\ServiceResult
1000 {
1001 $result = new PaySystem\ServiceResult();
1002
1003 $url = $this->getUrl($payment, 'payment');
1004 $headers = $this->getHeaders($payment);
1005
1006 $sendResult = $this->send(self::SEND_METHOD_HTTP_GET, $url, $headers);
1007 if ($sendResult->isSuccess())
1008 {
1009 $result->setData($sendResult->getData());
1010 }
1011 else
1012 {
1013 $result->addErrors($sendResult->getErrors());
1014 }
1015
1016 return $result;
1017 }
1018
1026 public function refund(Payment $payment, $refundableSum)
1027 {
1028 $result = new PaySystem\ServiceResult();
1029
1030 $url = $this->getUrl($payment, 'refund');
1031 $params = $this->getRefundQueryParams($payment, $refundableSum);
1032 $headers = $this->getHeaders($payment);
1033
1034 $sendResult = $this->send(self::SEND_METHOD_HTTP_POST, $url, $headers, $params);
1035 if (!$sendResult->isSuccess())
1036 {
1037 $result->addErrors($sendResult->getErrors());
1038 return $result;
1039 }
1040
1041 $response = $sendResult->getData();
1042
1043 if ($response['status'] === static::PAYMENT_STATUS_SUCCEEDED
1044 && PriceMaths::roundPrecision($response['amount']['value']) === PriceMaths::roundPrecision($refundableSum)
1045 )
1046 {
1047 $result->setOperationType(PaySystem\ServiceResult::MONEY_LEAVING);
1048 }
1049
1050 return $result;
1051 }
1052
1058 public function cancel(Payment $payment)
1059 {
1060 $url = $this->getUrl($payment, 'cancel');
1061 $headers = $this->getHeaders($payment);
1062
1063 $sendResult = $this->send(self::SEND_METHOD_HTTP_POST, $url, $headers);
1064 if (!$sendResult->isSuccess())
1065 {
1066 $error = __CLASS__.': cancel: '.implode("\n", $sendResult->getErrorMessages());
1067 PaySystem\Logger::addError($error);
1068 }
1069
1070 return $sendResult;
1071 }
1072
1083 public function confirm(Payment $payment, $sum = 0)
1084 {
1085 $result = new PaySystem\ServiceResult();
1086
1087 $url = $this->getUrl($payment, 'confirm');
1088 $headers = $this->getHeaders($payment);
1089
1090 if ($sum == 0)
1091 {
1092 $sum = $payment->getSum();
1093 }
1094
1095 $params = array(
1096 'amount' => array(
1097 'value' => (string)PriceMaths::roundPrecision($sum),
1098 'currency' => $payment->getField('CURRENCY')
1099 )
1100 );
1101
1102 $sendResult = $this->send(self::SEND_METHOD_HTTP_POST, $url, $headers, $params);
1103 if ($sendResult->isSuccess())
1104 {
1105 $response = $sendResult->getData();
1106 $description = Localization\Loc::getMessage('SALE_HPS_YANDEX_CHECKOUT_TRANSACTION').$response['id'];
1107
1108 $fields = array(
1109 "PS_STATUS_CODE" => mb_substr($response['status'], 0, 5),
1110 "PS_STATUS_DESCRIPTION" => $description,
1111 "PS_SUM" => $response['amount']['value'],
1112 "PS_CURRENCY" => $response['amount']['currency'],
1113 "PS_RESPONSE_DATE" => new Main\Type\DateTime()
1114 );
1115
1116 if ($response['status'] === static::PAYMENT_STATUS_SUCCEEDED)
1117 {
1118 $fields["PS_STATUS"] = "Y";
1119 $result->setOperationType(PaySystem\ServiceResult::MONEY_COMING);
1120 }
1121 else
1122 {
1123 $fields["PS_STATUS"] = "N";
1124 }
1125
1126 $result->setPsData($fields);
1127 }
1128 else
1129 {
1130 $error = __CLASS__.': confirm: '.join("\n", $sendResult->getErrorMessages());
1131 PaySystem\Logger::addError($error);
1132 }
1133
1134 return $result;
1135 }
1136
1141 private function getHeaders(Payment $payment): array
1142 {
1143 $headers = [
1144 'Content-Type' => 'application/json',
1145 'Idempotence-Key' => $this->getIdempotenceKey(),
1146 ];
1147
1148 try
1149 {
1150 $headers['Authorization'] = $this->getAuthorizationHeader($payment);
1151 }
1152 catch (\Exception $ex)
1153 {
1154 $headers['Authorization'] = 'Basic '.$this->getBasicAuthString($payment);
1155 }
1156
1157 return $headers;
1158 }
1159
1166 private function getAuthorizationHeader(Payment $payment): string
1167 {
1168 if (self::isOAuth())
1169 {
1170 $token = $this->getYandexToken();
1171 return 'Bearer '.$token;
1172 }
1173
1174 return 'Basic '.$this->getBasicAuthString($payment);
1175 }
1176
1182 private function getYandexToken()
1183 {
1184 if (!Main\Loader::includeModule('seo'))
1185 {
1186 return null;
1187 }
1188
1189 $authAdapter = Seo\Checkout\Service::getAuthAdapter(Seo\Checkout\Service::TYPE_YOOKASSA);
1190 $token = $authAdapter->getToken();
1191 if (!$token)
1192 {
1193 $authAdapter = Seo\Checkout\Service::getAuthAdapter(Seo\Checkout\Service::TYPE_YANDEX);
1194 $token = $authAdapter->getToken();
1195 }
1196
1197 return $token;
1198 }
1199
1206 private function getRefundQueryParams(Payment $payment, $refundableSum)
1207 {
1208 return array(
1209 'payment_id' => $payment->getField('PS_INVOICE_ID'),
1210 'amount' => array(
1211 'value' => (string)PriceMaths::roundPrecision($refundableSum),
1212 'currency' => $payment->getField('CURRENCY'),
1213 ),
1214 );
1215 }
1216
1221 public function getPaymentIdFromRequest(Request $request)
1222 {
1223 $inputStream = static::readFromStream();
1224
1225 if ($inputStream)
1226 {
1227 $data = static::decode($inputStream);
1228 if ($data === false)
1229 {
1230 return false;
1231 }
1232
1233 return $data['object']['metadata']['BX_PAYMENT_NUMBER'];
1234
1235 }
1236
1237 return false;
1238 }
1239
1243 public static function getHandlerModeList()
1244 {
1245 return PaySystem\Manager::getHandlerDescription('YandexCheckout')['HANDLER_MODE_LIST'];
1246 }
1247
1252 protected function getYandexHandlerType($psMode)
1253 {
1254 $handlersMap = [
1255 static::MODE_SMART => static::PAYMENT_METHOD_SMART,
1256 static::MODE_ALFABANK => static::PAYMENT_METHOD_ALFABANK,
1257 static::MODE_BANK_CARD => static::PAYMENT_METHOD_BANK_CARD,
1258 static::MODE_YANDEX_MONEY => static::PAYMENT_METHOD_YANDEX_MONEY,
1259 static::MODE_SBERBANK => static::PAYMENT_METHOD_SBERBANK,
1260 static::MODE_SBERBANK_SMS => static::PAYMENT_METHOD_SBERBANK,
1261 static::MODE_SBERBANK_QR => static::PAYMENT_METHOD_SBERBANK,
1262 static::MODE_CASH => static::PAYMENT_METHOD_CASH,
1263 static::MODE_EMBEDDED => static::PAYMENT_METHOD_EMBEDDED,
1264 static::MODE_TINKOFF_BANK => static::PAYMENT_METHOD_TINKOFF_BANK,
1265 static::MODE_SBER_LOAN => static::PAYMENT_METHOD_SBER_LOAN,
1266 static::MODE_SBP => static::PAYMENT_METHOD_SBP,
1267 ];
1268
1269 if (array_key_exists($psMode, $handlersMap))
1270 {
1271 return $handlersMap[$psMode];
1272 }
1273
1274 return $psMode;
1275 }
1276
1280 protected function getUrlList()
1281 {
1282 return array(
1283 'pay' => static::URL.'/payments',
1284 'refund' => static::URL.'/refunds',
1285 'confirm' => static::URL.'/payments/#payment_id#/capture',
1286 'cancel' => static::URL.'/payments/#payment_id#/cancel',
1287 'payment' => static::URL.'/payments/#payment_id#',
1288 'settings' => static::URL.'/me',
1289 );
1290 }
1291
1297 public static function isMyResponse(Request $request, $paySystemId)
1298 {
1299 $inputStream = static::readFromStream();
1300
1301 if ($inputStream)
1302 {
1303 $data = static::decode($inputStream);
1304 if ($data === false)
1305 {
1306 return false;
1307 }
1308
1309 if (isset($data['object']['metadata']['BX_HANDLER'])
1310 && $data['object']['metadata']['BX_HANDLER'] === 'YANDEX_CHECKOUT'
1311 && isset($data['object']['metadata']['BX_PAYSYSTEM_CODE'])
1312 && (int)$data['object']['metadata']['BX_PAYSYSTEM_CODE'] === (int)$paySystemId
1313 )
1314 {
1315 return true;
1316 }
1317 }
1318
1319 return false;
1320 }
1321
1325 private static function readFromStream()
1326 {
1327 return file_get_contents('php://input');
1328 }
1329
1335 protected function getUrl(Payment $payment = null, $action)
1336 {
1337 $url = parent::getUrl($payment, $action);
1338 if ($payment !== null &&
1339 (
1340 $action === 'cancel'
1341 || $action === 'confirm'
1342 || $action === 'payment'
1343 )
1344 )
1345 {
1346 $url = str_replace('#payment_id#', $payment->getField('PS_INVOICE_ID'), $url);
1347 }
1348
1349 return $url;
1350 }
1351
1355 private function getPaymentMethodFields(): array
1356 {
1357 $paymentMethodFields = array(
1358 static::MODE_ALFABANK => array('login'),
1359 static::MODE_MOBILE_BALANCE => array('phone'),
1360 static::MODE_SBERBANK_SMS => array('phone'),
1361 );
1362
1363 if (isset($paymentMethodFields[$this->service->getField('PS_MODE')]))
1364 {
1365 return $paymentMethodFields[$this->service->getField('PS_MODE')];
1366 }
1367
1368 return [];
1369 }
1370
1375 private function isFillPaymentMethodFields(Request $request): bool
1376 {
1377 $fields = $this->getPaymentMethodFields();
1378 if ($fields)
1379 {
1380 foreach ($fields as $field)
1381 {
1382 if (!$request->get($field))
1383 {
1384 return false;
1385 }
1386 }
1387 }
1388
1389 return true;
1390 }
1391
1396 private function isPhone($field): bool
1397 {
1398 $paymentMethodPhoneFields = $this->getPhoneFields();
1399
1400 $phoneFields = [];
1401 if (isset($paymentMethodPhoneFields[$this->service->getField('PS_MODE')]))
1402 {
1403 $phoneFields = $paymentMethodPhoneFields[$this->service->getField('PS_MODE')];
1404 }
1405
1406 return in_array($field, $phoneFields);
1407 }
1408
1412 private function getPhoneFields(): array
1413 {
1414 return [
1415 static::MODE_MOBILE_BALANCE => ['phone'],
1416 static::MODE_SBERBANK_SMS => ['phone'],
1417 ];
1418 }
1419
1424 private function normalizePhone($number)
1425 {
1426 $normalizedNumber = \NormalizePhone($number);
1427
1428 if ($normalizedNumber)
1429 {
1430 return $normalizedNumber;
1431 }
1432
1433 return $number;
1434 }
1435
1439 private function hasPaymentMethodFields(): bool
1440 {
1441 $fields = $this->getPaymentMethodFields();
1442 return (bool)$fields;
1443 }
1444
1457 public function repeatRecurrent(Payment $payment, Request $request = null): PaySystem\ServiceResult
1458 {
1459 if ($request === null)
1460 {
1461 $request = Main\Context::getCurrent()->getRequest();
1462 }
1463
1464 return $this->createYandexPayment($payment, $request, true);
1465 }
1466
1472 public function cancelRecurrent(Payment $payment, Request $request = null): PaySystem\ServiceResult
1473 {
1474 return (new PaySystem\ServiceResult());
1475 }
1476
1481 public function isRecurring(Payment $payment): bool
1482 {
1483 $modeList = [
1484 self::MODE_BANK_CARD,
1485 self::MODE_YANDEX_MONEY,
1486 self::MODE_EMBEDDED,
1487 ];
1488
1489 $isPsModeSupport = in_array($this->service->getField("PS_MODE"), $modeList, true);
1490
1491 return $this->getBusinessValue($payment, 'YANDEX_CHECKOUT_RECURRING') === 'Y'
1492 && $isPsModeSupport;
1493 }
1494
1498 private function checkIpAddress(): PaySystem\ServiceResult
1499 {
1500 $result = new PaySystem\ServiceResult();
1501
1502 $isFound = false;
1503 $yandexIp = Main\Context::getCurrent()->getRequest()->getRemoteAddress();
1504 foreach (self::CALLBACK_IP_LIST as $callbackIp)
1505 {
1506 $ipAddress = new Main\Web\IpAddress($yandexIp);
1507 if ($ipAddress->matchRange($callbackIp))
1508 {
1509 $isFound = true;
1510 break;
1511 }
1512 }
1513
1514 if (!$isFound)
1515 {
1516 $result->addError(
1517 PaySystem\Error::create(
1518 Localization\Loc::getMessage('SALE_HPS_YANDEX_CHECKOUT_ERROR_CHECK_IP', [
1519 '#IP_ADDRESS#' => $yandexIp,
1520 ])
1521 )
1522 );
1523 return $result;
1524 }
1525
1526 return $result;
1527 }
1528
1534 private static function isOAuth(): bool
1535 {
1537 return Main\Config\Option::get('sale', 'YANDEX_CHECKOUT_OAUTH', false) == true;
1538 }
1539
1540 public function getRestrictionList(): RestrictionInfoCollection
1541 {
1542 $psMode = $this->service->getField('PS_MODE');
1543
1544 $baseRestrictions = parent::getRestrictionList();
1545 if ($psMode === self::PAYMENT_METHOD_SBER_LOAN)
1546 {
1547 $restrictionInfo = new RestrictionInfo('Price', [
1548 'MIN_VALUE' => 3000,
1549 'MAX_VALUE' => 600000,
1550 ]);
1551
1552 $baseRestrictions->add($restrictionInfo);
1553 }
1554 elseif (
1555 $psMode === self::PAYMENT_METHOD_SBP
1556 || $this->getYandexHandlerType($psMode) === self::PAYMENT_METHOD_SBERBANK
1557 )
1558 {
1559 $restrictionInfo = new RestrictionInfo('Price', [
1560 'MIN_VALUE' => 1,
1561 'MAX_VALUE' => 1000000,
1562 ]);
1563
1564 $baseRestrictions->add($restrictionInfo);
1565 }
1566
1567 return $baseRestrictions;
1568 }
1569
1570 private static function generateQrCode(string $data): ?string
1571 {
1572 return (new PaySystem\BarcodeGenerator())->generate($data);
1573 }
1574
1575 public static function getCashboxClass(): string
1576 {
1577 return '\\'.Cashbox\CashboxYooKassa::class;
1578 }
1579
1580 public function isFiscalizationEnabled(Payment $payment): bool
1581 {
1582 $url = $this->getUrl($payment, 'settings');
1583 $headers = $this->getHeaders($payment);
1584
1585 $sendResult = $this->send(self::SEND_METHOD_HTTP_GET, $url, $headers);
1586 if ($sendResult->isSuccess())
1587 {
1588 $data = $sendResult->getData();
1589
1590 return $data['fiscalization']['enabled'] ?? false;
1591 }
1592
1593 return false;
1594 }
1595}
$sum
Определения checkout.php:6
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
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
$query
Определения get_search.php:11
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
$name
Определения menu_edit.php:35
Order
Определения order.php:6
trait Error
Определения error.php:11
$payment
Определения payment.php:14
$order
Определения payment.php:8
$entityId
Определения payment.php:4
$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
$modeList
Определения options.php:2219
$response
Определения result.php:21
$method
Определения index.php:27
$postData
Определения index.php:29
$clientId
Определения seo_client.php:18
$error
Определения subscription_card_product.php:20
$action
Определения file_dialog.php:21
$url
Определения iframe.php:7
$fields
Определения yandex_run.php:501