2namespace Sale\Handlers\PaySystem;
5 Bitrix\Main\Web\HttpClient,
6 Bitrix\Main\Localization\Loc,
11 Bitrix\Sale\PaySystem\ServiceResult,
12 Bitrix\Sale\PaymentCollection,
13 Bitrix\Sale\PriceMaths;
15Loc::loadMessages(__FILE__);
25 private const MODE_CHECKOUT =
'checkout';
26 private const MODE_WIDGET =
'widget';
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';
32 private const TRACKING_ID_DELIMITER =
'#';
34 private const STATUS_SUCCESSFUL_CODE =
'successful';
35 private const STATUS_ERROR_CODE =
'error';
37 private const SEND_METHOD_HTTP_POST =
"POST";
38 private const SEND_METHOD_HTTP_GET =
"GET";
45 return PaySystem\Manager::getHandlerDescription(
'bePaid')[
'HANDLER_MODE_LIST'];
53 public function initiatePay(Payment
$payment, Request
$request =
null): ServiceResult
57 $createPaymentTokenResult = $this->createPaymentToken(
$payment);
58 if (!$createPaymentTokenResult->isSuccess())
60 $result->addErrors($createPaymentTokenResult->getErrors());
64 $createPaymentTokenData = $createPaymentTokenResult->getData();
65 if (!empty($createPaymentTokenData[
'checkout'][
'token']))
67 $result->setPsData([
'PS_INVOICE_ID' => $createPaymentTokenData[
'checkout'][
'token']]);
70 if ($this->isCheckoutMode())
72 $result->setPaymentUrl($createPaymentTokenData[
'checkout'][
'redirect_url']);
78 if ($showTemplateResult->isSuccess())
80 $result->setTemplate($showTemplateResult->getTemplate());
84 $result->addErrors($showTemplateResult->getErrors());
93 private function getTemplateName(): string
95 return (
string)$this->service->getField(
'PS_MODE');
106 'sum' => (string)(PriceMaths::roundPrecision(
$payment->getSum())),
107 'currency' =>
$payment->getField(
'CURRENCY'),
110 if ($this->isWidgetMode())
112 $params[
'checkout_url'] = self::CHECKOUT_API_URL;
113 $params[
'token'] = $paymentTokenData[
'checkout'][
'token'];
116 'test' => $this->isTestMode(
$payment),
117 'transaction_type' =>
'payment',
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(),
126 'success_url' => $this->getSuccessUrl(
$payment),
127 'decline_url' => $this->getDeclineUrl(
$payment),
129 'language' => LANGUAGE_ID,
135 $params[
'url'] = $paymentTokenData[
'checkout'][
'redirect_url'];
145 private function createPaymentToken(Payment
$payment): ServiceResult
152 'version' => self::API_VERSION,
153 'test' => $this->isTestMode(
$payment),
154 'transaction_type' =>
'payment',
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(),
163 'success_url' => $this->getSuccessUrl(
$payment),
164 'decline_url' => $this->getDeclineUrl(
$payment),
165 'fail_url' => $this->getFailUrl(
$payment),
166 'cancel_url' => $this->getCancelUrl(
$payment),
168 'language' => LANGUAGE_ID,
172 $headers = $this->getHeaders(
$payment);
174 $sendResult = $this->send(self::SEND_METHOD_HTTP_POST,
$url,
$params, $headers);
175 if ($sendResult->isSuccess())
177 $paymentTokenData = $sendResult->getData();
178 $verifyResponseResult = $this->verifyResponse($paymentTokenData);
179 if ($verifyResponseResult->isSuccess())
181 $result->setData($paymentTokenData);
185 $result->addErrors($verifyResponseResult->getErrors());
190 $result->addErrors($sendResult->getErrors());
200 private function getBePaidPayment(Payment
$payment): ServiceResult
205 $headers = $this->getHeaders(
$payment);
207 $sendResult = $this->send(self::SEND_METHOD_HTTP_GET,
$url, [], $headers);
208 if ($sendResult->isSuccess())
210 $paymentData = $sendResult->getData();
211 $verifyResponseResult = $this->verifyResponse($paymentData);
212 if ($verifyResponseResult->isSuccess())
214 $result->setData($paymentData);
218 $result->addErrors($verifyResponseResult->getErrors());
223 $result->addErrors($sendResult->getErrors());
234 public function refund(Payment
$payment, $refundableSum): ServiceResult
238 $bePaidPaymentResult = $this->getBePaidPayment(
$payment);
239 if (!$bePaidPaymentResult->isSuccess())
241 $result->addErrors($bePaidPaymentResult->getErrors());
245 $bePaidPaymentData = $bePaidPaymentResult->getData();
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'),
255 $headers = $this->getHeaders(
$payment);
257 $sendResult = $this->send(self::SEND_METHOD_HTTP_POST,
$url,
$params, $headers);
258 if (!$sendResult->isSuccess())
260 $result->addErrors($sendResult->getErrors());
264 $refundData = $sendResult->getData();
265 $verifyResponseResult = $this->verifyResponse($refundData);
266 if ($verifyResponseResult->isSuccess())
268 if ($refundData[
'transaction'][
'status'] === static::STATUS_SUCCESSFUL_CODE
269 && PriceMaths::roundPrecision($refundData[
'transaction'][
'amount'] / 100) === PriceMaths::roundPrecision($refundableSum)
272 $result->setOperationType(PaySystem\ServiceResult::MONEY_LEAVING);
277 $result->addErrors($verifyResponseResult->getErrors());
294 $httpClient =
new HttpClient();
295 foreach ($headers as
$name => $value)
297 $httpClient->setHeader(
$name, $value);
300 if (
$method === self::SEND_METHOD_HTTP_GET)
312 PaySystem\Logger::addDebugInfo(__CLASS__.
': request data: '.
$postData);
319 $errors = $httpClient->getError();
328 PaySystem\Logger::addDebugInfo(__CLASS__.
': response data: '.
$response);
337 $result->addError(PaySystem\Error::create(Loc::getMessage(
'SALE_HPS_BEPAID_RESPONSE_DECODE_ERROR')));
357 $result->addError(PaySystem\Error::create(
$response[
'response'][
'message']));
361 $result->addError(PaySystem\Error::create(
$response[
'response'][
'message']));
365 $result->addError(PaySystem\Error::create(
$response[
'checkout'][
'message']));
376 return [
'BYN',
'USD',
'EUR',
'RUB'];
384 public function processRequest(Payment
$payment, Request
$request): ServiceResult
388 $inputStream = static::readFromStream();
389 $data = static::decode($inputStream);
390 $transaction =
$data[
'transaction'];
392 $bePaidPaymentResult = $this->getBePaidPayment(
$payment);
393 if ($bePaidPaymentResult->isSuccess())
395 $bePaidPaymentData = $bePaidPaymentResult->getData();
396 if ($bePaidPaymentData[
'checkout'][
'status'] === self::STATUS_SUCCESSFUL_CODE)
398 $description = Loc::getMessage(
'SALE_HPS_BEPAID_TRANSACTION', [
399 '#ID#' => $transaction[
'uid'],
402 'PS_STATUS_CODE' => $transaction[
'status'],
404 'PS_SUM' => $transaction[
'amount'] / 100,
406 'PS_CURRENCY' => $transaction[
'currency'],
407 'PS_RESPONSE_DATE' =>
new Main\Type\DateTime()
410 if ($this->isSumCorrect(
$payment, $transaction[
'amount'] / 100))
414 PaySystem\Logger::addDebugInfo(
415 __CLASS__.
': PS_CHANGE_STATUS_PAY='.$this->getBusinessValue(
$payment,
'PS_CHANGE_STATUS_PAY')
420 $result->setOperationType(PaySystem\ServiceResult::MONEY_COMING);
425 $error = Loc::getMessage(
'SALE_HPS_BEPAID_ERROR_SUM');
426 $fields[
'PS_STATUS_DESCRIPTION'] .=
'. '.$error;
435 $result->addErrors($bePaidPaymentResult->getErrors());
446 private function isSumCorrect(Payment
$payment,
$sum): bool
448 PaySystem\Logger::addDebugInfo(
449 __CLASS__.
': bePaidSum='.PriceMaths::roundPrecision(
$sum).
"; paymentSum=".PriceMaths::roundPrecision(
$payment->getSum())
452 return PriceMaths::roundPrecision(
$sum) === PriceMaths::roundPrecision(
$payment->getSum());
460 public static function isMyResponse(Request
$request, $paySystemId): bool
462 $inputStream = static::readFromStream();
465 $data = static::decode($inputStream);
471 if (isset(
$data[
'transaction'][
'tracking_id']))
473 [, $trackingPaySystemId] = explode(self::TRACKING_ID_DELIMITER,
$data[
'transaction'][
'tracking_id']);
474 return (
int)$trackingPaySystemId === (int)$paySystemId;
485 public function getPaymentIdFromRequest(Request
$request)
487 $inputStream = static::readFromStream();
490 $data = static::decode($inputStream);
491 if (isset(
$data[
'transaction'][
'tracking_id']))
493 [$trackingPaymentId] = explode(self::TRACKING_ID_DELIMITER,
$data[
'transaction'][
'tracking_id']);
494 return (
int)$trackingPaymentId;
505 private function getPaymentDescription(Payment
$payment)
508 $collection =
$payment->getCollection();
509 $order = $collection->getOrder();
510 $userEmail =
$order->getPropertyCollection()->getUserEmail();
521 $payment->getField(
'ACCOUNT_NUMBER'),
522 $order->getField(
'ACCOUNT_NUMBER'),
525 ($userEmail) ? $userEmail->getValue() :
''
527 $this->getBusinessValue(
$payment,
'BEPAID_PAYMENT_DESCRIPTION')
537 private function getSuccessUrl(Payment
$payment)
546 private function getDeclineUrl(Payment
$payment)
555 private function getFailUrl(Payment
$payment)
564 private function getCancelUrl(Payment
$payment)
576 'Content-Type' =>
'application/json',
577 'Accept' =>
'application/json',
578 'Authorization' =>
'Basic '.$this->getBasicAuthString(
$payment),
579 'RequestID' => $this->getIdempotenceKey(),
582 if ($this->isWidgetMode())
584 $headers[
'X-API-Version'] = 2;
594 private function getBasicAuthString(Payment
$payment): string
596 return base64_encode(
606 private function isWidgetMode(): bool
608 return $this->service->getField(
'PS_MODE') === self::MODE_WIDGET;
614 private function isCheckoutMode(): bool
616 return $this->service->getField(
'PS_MODE') === self::MODE_CHECKOUT;
622 private function getIdempotenceKey(): string
624 return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
625 mt_rand(0, 0xffff), 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)
643 $url = str_replace(
'#payment_token#',
$payment->getField(
'PS_INVOICE_ID'),
$url);
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',
665 protected function isTestMode(Payment
$payment =
null): bool
673 private static function readFromStream()
675 return file_get_contents(
'php://input');
684 return Main\Web\Json::encode(
$data, JSON_UNESCAPED_UNICODE);
691 private static function decode(
$data)
695 return Main\Web\Json::decode(
$data);
697 catch (Main\ArgumentException $exception)
703 private static function getAdditionalData():
array
706 'platform_data' => self::getPlatformData(),
709 $integrationData = self::getIntegrationData();
710 if ($integrationData)
712 $additionalData[
'integration_data'] = $integrationData;
715 return $additionalData;
718 private static function getPlatformData(): string
720 if (Main\ModuleManager::isModuleInstalled(
'bitrix24'))
722 $platformType =
'Bitrix24';
724 elseif (Main\ModuleManager::isModuleInstalled(
'intranet'))
726 $platformType =
'Self-hosted';
730 $platformType =
'Bitrix Site Manager';
733 return $platformType;
736 private static function getIntegrationData(): ?string
738 $version = self::getSaleModuleVersion();
744 return 'bePaid system module v' . $version;
747 private static function getSaleModuleVersion(): ?string
749 $modulePath =
getLocalPath(
'modules/sale/install/version.php');
750 if ($modulePath ===
false)
755 $arModuleVersion =
array();
756 include
$_SERVER[
'DOCUMENT_ROOT'].$modulePath;
757 return (isset($arModuleVersion[
'VERSION']) ? (
string)$arModuleVersion[
'VERSION'] :
null);
762 return array_keys(self::getHandlerModeList());
if(!Loader::includeModule('catalog')) if(!AccessController::getCurrent() ->check(ActionDictionary::ACTION_PRICE_EDIT)) if(!check_bitrix_sessid()) $request
static getHandlerModeList()
showTemplate(Payment $payment=null, $template='')
setExtraParams(array $values)
getBusinessValue(Payment $payment=null, $code)
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
if(Loader::includeModule( 'bitrix24')) elseif(Loader::includeModule('intranet') &&CIntranetUtils::getPortalZone() !=='ru') $description
$_SERVER["DOCUMENT_ROOT"]
if(!is_null($config))($config as $configItem)(! $configItem->isVisible()) $code
getLocalPath($path, $baseFolder="/bitrix")
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
if($inWords) echo htmlspecialcharsbx(Number2Word_Rus(roundEx($totalVatSum $params['CURRENCY']