1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
handler.php
См. документацию.
1<?php
2namespace Sale\Handlers\PaySystem;
3
4use Bitrix\Main,
5 Bitrix\Main\Web\HttpClient,
6 Bitrix\Main\Localization\Loc,
7 Bitrix\Sale,
8 Bitrix\Sale\PaySystem,
9 Bitrix\Main\Request,
10 Bitrix\Sale\Payment,
11 Bitrix\Sale\PaySystem\ServiceResult,
12 Bitrix\Sale\PaymentCollection,
13 Bitrix\Sale\PriceMaths;
14
15Loc::loadMessages(__FILE__);
16
21class BePaidHandler extends PaySystem\ServiceHandler implements
24{
25 private const MODE_CHECKOUT = 'checkout';
26 private const MODE_WIDGET = 'widget';
27
28 private const CHECKOUT_API_URL = 'https://checkout.bepaid.by';
29 private const GATEWAY_API_URL = 'https://gateway.bepaid.by';
30 private const API_VERSION = '2.1';
31
32 private const TRACKING_ID_DELIMITER = '#';
33
34 private const STATUS_SUCCESSFUL_CODE = 'successful';
35 private const STATUS_ERROR_CODE = 'error';
36
37 private const SEND_METHOD_HTTP_POST = "POST";
38 private const SEND_METHOD_HTTP_GET = "GET";
39
43 public static function getHandlerModeList(): array
44 {
45 return PaySystem\Manager::getHandlerDescription('bePaid')['HANDLER_MODE_LIST'];
46 }
47
53 public function initiatePay(Payment $payment, Request $request = null): ServiceResult
54 {
55 $result = new ServiceResult();
56
57 $createPaymentTokenResult = $this->createPaymentToken($payment);
58 if (!$createPaymentTokenResult->isSuccess())
59 {
60 $result->addErrors($createPaymentTokenResult->getErrors());
61 return $result;
62 }
63
64 $createPaymentTokenData = $createPaymentTokenResult->getData();
65 if (!empty($createPaymentTokenData['checkout']['token']))
66 {
67 $result->setPsData(['PS_INVOICE_ID' => $createPaymentTokenData['checkout']['token']]);
68 }
69
70 if ($this->isCheckoutMode())
71 {
72 $result->setPaymentUrl($createPaymentTokenData['checkout']['redirect_url']);
73 }
74
75 $this->setExtraParams($this->getTemplateParams($payment, $createPaymentTokenData));
76
77 $showTemplateResult = $this->showTemplate($payment, $this->getTemplateName());
78 if ($showTemplateResult->isSuccess())
79 {
80 $result->setTemplate($showTemplateResult->getTemplate());
81 }
82 else
83 {
84 $result->addErrors($showTemplateResult->getErrors());
85 }
86
87 return $result;
88 }
89
93 private function getTemplateName(): string
94 {
95 return (string)$this->service->getField('PS_MODE');
96 }
97
103 private function getTemplateParams(Payment $payment, array $paymentTokenData): array
104 {
105 $params = [
106 'sum' => (string)(PriceMaths::roundPrecision($payment->getSum())),
107 'currency' => $payment->getField('CURRENCY'),
108 ];
109
110 if ($this->isWidgetMode())
111 {
112 $params['checkout_url'] = self::CHECKOUT_API_URL;
113 $params['token'] = $paymentTokenData['checkout']['token'];
114 $params['checkout'] = [
115 'iframe' => true,
116 'test' => $this->isTestMode($payment),
117 'transaction_type' => 'payment',
118 'order' => [
119 'amount' => (string)(PriceMaths::roundPrecision($payment->getSum()) * 100),
120 'currency' => $payment->getField('CURRENCY'),
121 'description' => $this->getPaymentDescription($payment),
122 'tracking_id' => $payment->getId().self::TRACKING_ID_DELIMITER.$this->service->getField('ID'),
123 'additional_data' => self::getAdditionalData(),
124 ],
125 'settings' => [
126 'success_url' => $this->getSuccessUrl($payment),
127 'decline_url' => $this->getDeclineUrl($payment),
128 'notification_url' => $this->getBusinessValue($payment, 'BEPAID_NOTIFICATION_URL'),
129 'language' => LANGUAGE_ID,
130 ],
131 ];
132 }
133 else
134 {
135 $params['url'] = $paymentTokenData['checkout']['redirect_url'];
136 }
137
138 return $params;
139 }
140
145 private function createPaymentToken(Payment $payment): ServiceResult
146 {
147 $result = new ServiceResult();
148
149 $url = $this->getUrl($payment, 'getPaymentToken');
150 $params = [
151 'checkout' => [
152 'version' => self::API_VERSION,
153 'test' => $this->isTestMode($payment),
154 'transaction_type' => 'payment',
155 'order' => [
156 'amount' => (string)(PriceMaths::roundPrecision($payment->getSum()) * 100),
157 'currency' => $payment->getField('CURRENCY'),
158 'description' => $this->getPaymentDescription($payment),
159 'tracking_id' => $payment->getId().self::TRACKING_ID_DELIMITER.$this->service->getField('ID'),
160 'additional_data' => self::getAdditionalData(),
161 ],
162 'settings' => [
163 'success_url' => $this->getSuccessUrl($payment),
164 'decline_url' => $this->getDeclineUrl($payment),
165 'fail_url' => $this->getFailUrl($payment),
166 'cancel_url' => $this->getCancelUrl($payment),
167 'notification_url' => $this->getBusinessValue($payment, 'BEPAID_NOTIFICATION_URL'),
168 'language' => LANGUAGE_ID,
169 ],
170 ],
171 ];
172 $headers = $this->getHeaders($payment);
173
174 $sendResult = $this->send(self::SEND_METHOD_HTTP_POST, $url, $params, $headers);
175 if ($sendResult->isSuccess())
176 {
177 $paymentTokenData = $sendResult->getData();
178 $verifyResponseResult = $this->verifyResponse($paymentTokenData);
179 if ($verifyResponseResult->isSuccess())
180 {
181 $result->setData($paymentTokenData);
182 }
183 else
184 {
185 $result->addErrors($verifyResponseResult->getErrors());
186 }
187 }
188 else
189 {
190 $result->addErrors($sendResult->getErrors());
191 }
192
193 return $result;
194 }
195
200 private function getBePaidPayment(Payment $payment): ServiceResult
201 {
202 $result = new ServiceResult();
203
204 $url = $this->getUrl($payment, 'getPaymentStatus');
205 $headers = $this->getHeaders($payment);
206
207 $sendResult = $this->send(self::SEND_METHOD_HTTP_GET, $url, [], $headers);
208 if ($sendResult->isSuccess())
209 {
210 $paymentData = $sendResult->getData();
211 $verifyResponseResult = $this->verifyResponse($paymentData);
212 if ($verifyResponseResult->isSuccess())
213 {
214 $result->setData($paymentData);
215 }
216 else
217 {
218 $result->addErrors($verifyResponseResult->getErrors());
219 }
220 }
221 else
222 {
223 $result->addErrors($sendResult->getErrors());
224 }
225
226 return $result;
227 }
228
234 public function refund(Payment $payment, $refundableSum): ServiceResult
235 {
236 $result = new ServiceResult();
237
238 $bePaidPaymentResult = $this->getBePaidPayment($payment);
239 if (!$bePaidPaymentResult->isSuccess())
240 {
241 $result->addErrors($bePaidPaymentResult->getErrors());
242 return $result;
243 }
244
245 $bePaidPaymentData = $bePaidPaymentResult->getData();
246
247 $url = $this->getUrl($payment, 'refund');
248 $params = [
249 'request' => [
250 'parent_uid' => $bePaidPaymentData['checkout']['gateway_response']['payment']['uid'],
251 'amount' => (string)(PriceMaths::roundPrecision($refundableSum) * 100),
252 'reason' => $payment->getField('PAY_RETURN_COMMENT') ?: Loc::getMessage('SALE_HPS_BEPAID_REFUND_REASON'),
253 ],
254 ];
255 $headers = $this->getHeaders($payment);
256
257 $sendResult = $this->send(self::SEND_METHOD_HTTP_POST, $url, $params, $headers);
258 if (!$sendResult->isSuccess())
259 {
260 $result->addErrors($sendResult->getErrors());
261 return $result;
262 }
263
264 $refundData = $sendResult->getData();
265 $verifyResponseResult = $this->verifyResponse($refundData);
266 if ($verifyResponseResult->isSuccess())
267 {
268 if ($refundData['transaction']['status'] === static::STATUS_SUCCESSFUL_CODE
269 && PriceMaths::roundPrecision($refundData['transaction']['amount'] / 100) === PriceMaths::roundPrecision($refundableSum)
270 )
271 {
272 $result->setOperationType(PaySystem\ServiceResult::MONEY_LEAVING);
273 }
274 }
275 else
276 {
277 $result->addErrors($verifyResponseResult->getErrors());
278 }
279
280 return $result;
281 }
282
290 private function send(string $method, string $url, array $params = [], array $headers = []): ServiceResult
291 {
292 $result = new ServiceResult();
293
294 $httpClient = new HttpClient();
295 foreach ($headers as $name => $value)
296 {
297 $httpClient->setHeader($name, $value);
298 }
299
300 if ($method === self::SEND_METHOD_HTTP_GET)
301 {
302 $response = $httpClient->get($url);
303 }
304 else
305 {
306 $postData = null;
307 if ($params)
308 {
309 $postData = static::encode($params);
310 }
311
312 PaySystem\Logger::addDebugInfo(__CLASS__.': request data: '.$postData);
313
314 $response = $httpClient->post($url, $postData);
315 }
316
317 if ($response === false)
318 {
319 $errors = $httpClient->getError();
320 foreach ($errors as $code => $message)
321 {
322 $result->addError(PaySystem\Error::create($message, $code));
323 }
324
325 return $result;
326 }
327
328 PaySystem\Logger::addDebugInfo(__CLASS__.': response data: '.$response);
329
330 $response = static::decode($response);
331 if ($response)
332 {
333 $result->setData($response);
334 }
335 else
336 {
337 $result->addError(PaySystem\Error::create(Loc::getMessage('SALE_HPS_BEPAID_RESPONSE_DECODE_ERROR')));
338 }
339
340 return $result;
341 }
342
347 private function verifyResponse(array $response): ServiceResult
348 {
349 $result = new ServiceResult();
350
351 if (!empty($response['errors']))
352 {
353 $result->addError(PaySystem\Error::create($response['message']));
354 }
355 elseif (!empty($response['response']['errors']))
356 {
357 $result->addError(PaySystem\Error::create($response['response']['message']));
358 }
359 elseif (!empty($response['response']['status']) && $response['response']['status'] === self::STATUS_ERROR_CODE)
360 {
361 $result->addError(PaySystem\Error::create($response['response']['message']));
362 }
363 elseif (!empty($response['checkout']['status']) && $response['checkout']['status'] === self::STATUS_ERROR_CODE)
364 {
365 $result->addError(PaySystem\Error::create($response['checkout']['message']));
366 }
367
368 return $result;
369 }
370
374 public function getCurrencyList(): array
375 {
376 return ['BYN', 'USD', 'EUR', 'RUB'];
377 }
378
384 public function processRequest(Payment $payment, Request $request): ServiceResult
385 {
386 $result = new ServiceResult();
387
388 $inputStream = static::readFromStream();
389 $data = static::decode($inputStream);
390 $transaction = $data['transaction'];
391
392 $bePaidPaymentResult = $this->getBePaidPayment($payment);
393 if ($bePaidPaymentResult->isSuccess())
394 {
395 $bePaidPaymentData = $bePaidPaymentResult->getData();
396 if ($bePaidPaymentData['checkout']['status'] === self::STATUS_SUCCESSFUL_CODE)
397 {
398 $description = Loc::getMessage('SALE_HPS_BEPAID_TRANSACTION', [
399 '#ID#' => $transaction['uid'],
400 ]);
401 $fields = [
402 'PS_STATUS_CODE' => $transaction['status'],
403 'PS_STATUS_DESCRIPTION' => $description,
404 'PS_SUM' => $transaction['amount'] / 100,
405 'PS_STATUS' => 'N',
406 'PS_CURRENCY' => $transaction['currency'],
407 'PS_RESPONSE_DATE' => new Main\Type\DateTime()
408 ];
409
410 if ($this->isSumCorrect($payment, $transaction['amount'] / 100))
411 {
412 $fields['PS_STATUS'] = 'Y';
413
414 PaySystem\Logger::addDebugInfo(
415 __CLASS__.': PS_CHANGE_STATUS_PAY='.$this->getBusinessValue($payment, 'PS_CHANGE_STATUS_PAY')
416 );
417
418 if ($this->getBusinessValue($payment, 'PS_CHANGE_STATUS_PAY') === 'Y')
419 {
420 $result->setOperationType(PaySystem\ServiceResult::MONEY_COMING);
421 }
422 }
423 else
424 {
425 $error = Loc::getMessage('SALE_HPS_BEPAID_ERROR_SUM');
426 $fields['PS_STATUS_DESCRIPTION'] .= '. '.$error;
427 $result->addError(PaySystem\Error::create($error));
428 }
429
430 $result->setPsData($fields);
431 }
432 }
433 else
434 {
435 $result->addErrors($bePaidPaymentResult->getErrors());
436 }
437
438 return $result;
439 }
440
446 private function isSumCorrect(Payment $payment, $sum): bool
447 {
448 PaySystem\Logger::addDebugInfo(
449 __CLASS__.': bePaidSum='.PriceMaths::roundPrecision($sum)."; paymentSum=".PriceMaths::roundPrecision($payment->getSum())
450 );
451
452 return PriceMaths::roundPrecision($sum) === PriceMaths::roundPrecision($payment->getSum());
453 }
454
460 public static function isMyResponse(Request $request, $paySystemId): bool
461 {
462 $inputStream = static::readFromStream();
463 if ($inputStream)
464 {
465 $data = static::decode($inputStream);
466 if ($data === false)
467 {
468 return false;
469 }
470
471 if (isset($data['transaction']['tracking_id']))
472 {
473 [, $trackingPaySystemId] = explode(self::TRACKING_ID_DELIMITER, $data['transaction']['tracking_id']);
474 return (int)$trackingPaySystemId === (int)$paySystemId;
475 }
476 }
477
478 return false;
479 }
480
485 public function getPaymentIdFromRequest(Request $request)
486 {
487 $inputStream = static::readFromStream();
488 if ($inputStream)
489 {
490 $data = static::decode($inputStream);
491 if (isset($data['transaction']['tracking_id']))
492 {
493 [$trackingPaymentId] = explode(self::TRACKING_ID_DELIMITER, $data['transaction']['tracking_id']);
494 return (int)$trackingPaymentId;
495 }
496 }
497
498 return false;
499 }
500
505 private function getPaymentDescription(Payment $payment)
506 {
508 $collection = $payment->getCollection();
509 $order = $collection->getOrder();
510 $userEmail = $order->getPropertyCollection()->getUserEmail();
511
512 $description = str_replace(
513 [
514 '#PAYMENT_NUMBER#',
515 '#ORDER_NUMBER#',
516 '#PAYMENT_ID#',
517 '#ORDER_ID#',
518 '#USER_EMAIL#'
519 ],
520 [
521 $payment->getField('ACCOUNT_NUMBER'),
522 $order->getField('ACCOUNT_NUMBER'),
523 $payment->getId(),
524 $order->getId(),
525 ($userEmail) ? $userEmail->getValue() : ''
526 ],
527 $this->getBusinessValue($payment, 'BEPAID_PAYMENT_DESCRIPTION')
528 );
529
530 return $description;
531 }
532
537 private function getSuccessUrl(Payment $payment)
538 {
539 return $this->getBusinessValue($payment, 'BEPAID_SUCCESS_URL') ?: $this->service->getContext()->getUrl();
540 }
541
546 private function getDeclineUrl(Payment $payment)
547 {
548 return $this->getBusinessValue($payment, 'BEPAID_DECLINE_URL') ?: $this->service->getContext()->getUrl();
549 }
550
555 private function getFailUrl(Payment $payment)
556 {
557 return $this->getBusinessValue($payment, 'BEPAID_FAIL_URL') ?: $this->service->getContext()->getUrl();
558 }
559
564 private function getCancelUrl(Payment $payment)
565 {
566 return $this->getBusinessValue($payment, 'BEPAID_CANCEL_URL') ?: $this->service->getContext()->getUrl();
567 }
568
573 private function getHeaders(Payment $payment): array
574 {
575 $headers = [
576 'Content-Type' => 'application/json',
577 'Accept' => 'application/json',
578 'Authorization' => 'Basic '.$this->getBasicAuthString($payment),
579 'RequestID' => $this->getIdempotenceKey(),
580 ];
581
582 if ($this->isWidgetMode())
583 {
584 $headers['X-API-Version'] = 2;
585 }
586
587 return $headers;
588 }
589
594 private function getBasicAuthString(Payment $payment): string
595 {
596 return base64_encode(
597 $this->getBusinessValue($payment, 'BEPAID_ID')
598 . ':'
599 . $this->getBusinessValue($payment, 'BEPAID_SECRET_KEY')
600 );
601 }
602
606 private function isWidgetMode(): bool
607 {
608 return $this->service->getField('PS_MODE') === self::MODE_WIDGET;
609 }
610
614 private function isCheckoutMode(): bool
615 {
616 return $this->service->getField('PS_MODE') === self::MODE_CHECKOUT;
617 }
618
622 private function getIdempotenceKey(): string
623 {
624 return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
625 mt_rand(0, 0xffff), mt_rand(0, 0xffff),
626 mt_rand(0, 0xffff),
627 mt_rand(0, 0x0fff) | 0x4000,
628 mt_rand(0, 0x3fff) | 0x8000,
629 mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
630 );
631 }
632
638 protected function getUrl(Payment $payment = null, $action): string
639 {
640 $url = parent::getUrl($payment, $action);
641 if ($payment !== null && $action === 'getPaymentStatus')
642 {
643 $url = str_replace('#payment_token#', $payment->getField('PS_INVOICE_ID'), $url);
644 }
645
646 return $url;
647 }
648
652 protected function getUrlList(): array
653 {
654 return [
655 'getPaymentToken' => self::CHECKOUT_API_URL.'/ctp/api/checkouts',
656 'getPaymentStatus' => self::CHECKOUT_API_URL.'/ctp/api/checkouts/#payment_token#',
657 'refund' => self::GATEWAY_API_URL.'/transactions/refunds',
658 ];
659 }
660
665 protected function isTestMode(Payment $payment = null): bool
666 {
667 return ($this->getBusinessValue($payment, 'PS_IS_TEST') === 'Y');
668 }
669
673 private static function readFromStream()
674 {
675 return file_get_contents('php://input');
676 }
677
682 private static function encode(array $data)
683 {
684 return Main\Web\Json::encode($data, JSON_UNESCAPED_UNICODE);
685 }
686
691 private static function decode($data)
692 {
693 try
694 {
695 return Main\Web\Json::decode($data);
696 }
697 catch (Main\ArgumentException $exception)
698 {
699 return false;
700 }
701 }
702
703 private static function getAdditionalData(): array
704 {
705 $additionalData = [
706 'platform_data' => self::getPlatformData(),
707 ];
708
709 $integrationData = self::getIntegrationData();
710 if ($integrationData)
711 {
712 $additionalData['integration_data'] = $integrationData;
713 }
714
715 return $additionalData;
716 }
717
718 private static function getPlatformData(): string
719 {
720 if (Main\ModuleManager::isModuleInstalled('bitrix24'))
721 {
722 $platformType = 'Bitrix24';
723 }
724 elseif (Main\ModuleManager::isModuleInstalled('intranet'))
725 {
726 $platformType = 'Self-hosted';
727 }
728 else
729 {
730 $platformType = 'Bitrix Site Manager';
731 }
732
733 return $platformType;
734 }
735
736 private static function getIntegrationData(): ?string
737 {
738 $version = self::getSaleModuleVersion();
739 if (!$version)
740 {
741 return null;
742 }
743
744 return 'bePaid system module v' . $version;
745 }
746
747 private static function getSaleModuleVersion(): ?string
748 {
749 $modulePath = getLocalPath('modules/sale/install/version.php');
750 if ($modulePath === false)
751 {
752 return null;
753 }
754
755 $arModuleVersion = array();
756 include $_SERVER['DOCUMENT_ROOT'].$modulePath;
757 return (isset($arModuleVersion['VERSION']) ? (string)$arModuleVersion['VERSION'] : null);
758 }
759
760 public static function getModeList(): array
761 {
762 return array_keys(self::getHandlerModeList());
763 }
764}
$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
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
$_SERVER["DOCUMENT_ROOT"]
Определения cron_frame.php:9
if(!is_null($config))($config as $configItem)(! $configItem->isVisible()) $code
Определения options.php:195
getLocalPath($path, $baseFolder="/bitrix")
Определения tools.php:5092
$name
Определения menu_edit.php:35
$payment
Определения payment.php:14
$order
Определения payment.php:8
$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
$method
Определения index.php:27
$postData
Определения index.php:29
$error
Определения subscription_card_product.php:20
$action
Определения file_dialog.php:21
$url
Определения iframe.php:7
$fields
Определения yandex_run.php:501