2namespace Sale\Handlers\PaySystem;
6 Bitrix\Main\Localization\Loc,
11 Bitrix\Sale\PriceMaths;
13Loc::loadMessages(__FILE__);
23 private const APPLE_PAY_INITIATIVE_CONTEXT =
'/bitrix/tools/sale/applepay_gateway.php';
26 private const PAYMENT_API_VERSION =
"v51";
28 private const CHECKOUT_API_VERSION =
"v51";
31 private const EVENT_CODE_AUTHORISATION =
"AUTHORISATION";
33 private const HTTP_RESPONSE_CODE_OK = 200;
35 private const RESULT_CODE_AUTHORISED =
"Authorised";
36 private const RESULT_CODE_ERROR =
"Error";
37 private const RESULT_CODE_REFUSED =
"Refused";
39 private const RESPONSE_REFUND_RECEIVED =
"[refund-received]";
40 private const NOTIFICATION_RESPONSE_ACCEPTED =
"[accepted]";
42 private const MAX_REQUEST_ATTEMPT = 3;
44 public const PAYMENT_METHOD_APPLE_PAY =
"apple_pay";
56 public function initiatePay(Payment
$payment, Request
$request =
null): PaySystem\ServiceResult
58 $result =
new PaySystem\ServiceResult();
62 $request = Main\Context::getCurrent()->getRequest();
67 if ($this->isActionExists(
$action))
73 $result->addError(
new Main\
Error(Loc::getMessage(
"SALE_HPS_ADYEN_ERROR_ACTION", [
95 private function initiatePayInternal(Payment
$payment): PaySystem\ServiceResult
97 $result =
new PaySystem\ServiceResult();
101 "PAYSYSTEM_ID" => $this->service->getField(
"ID"),
102 "MERCHANT_ID" => $this->getBusinessValue(
$payment,
"APPLE_PAY_MERCHANT_ID"),
103 "ORDER_ID" =>
$payment->getOrder()->getId(),
104 "TOTAL_SUM" => PriceMaths::roundPrecision(
$payment->getSum()),
105 "CURRENCY" =>
$payment->getField(
"CURRENCY"),
106 "MAKE_PAYMENT_ACTION" =>
"makePaymentAction",
109 $modeParams = $this->getModeParams(
$payment);
110 if (isset($modeParams[
"ERRORS"]))
112 $result->addErrors($modeParams[
"ERRORS"]);
119 $template =
"template_".$this->service->getField(
"PS_MODE");
121 if ($showTemplateResult->isSuccess())
123 $result->setTemplate($showTemplateResult->getTemplate());
127 $result->addErrors($showTemplateResult->getErrors());
139 private function isActionExists(
$action): bool
141 return is_callable([$this,
$action]);
150 self::PAYMENT_METHOD_APPLE_PAY =>
"Apple Pay",
159 public static function isMyResponse(Request
$request, $paySystemId): bool
161 $notificationItem = static::getNotificationItem();
162 return isset($notificationItem[
"additionalData"][
"metadata.paySystemId"])
163 && ((int)$notificationItem[
"additionalData"][
"metadata.paySystemId"] === (int)$paySystemId);
169 private static function getNotificationItem()
171 $inputStream = static::readFromStream();
172 $data = static::decode($inputStream);
173 if (isset(
$data[
"notificationItems"][0][
"NotificationRequestItem"])
174 && is_array(
$data[
"notificationItems"][0][
"NotificationRequestItem"])
177 return $data[
"notificationItems"][0][
"NotificationRequestItem"];
188 return [
"USD",
"EUR"];
200 public function processRequest(Payment
$payment, Request
$request): PaySystem\ServiceResult
202 $result =
new PaySystem\ServiceResult();
204 $notificationItem = static::getNotificationItem();
205 if ($notificationItem)
210 || empty($notificationItem[
"additionalData"][
"hmacSignature"])
211 || !$this->isSignatureCorrect($notificationItem, $secretKey)
214 $result->addError(
new Main\
Error(Loc::getMessage(
"SALE_HPS_ADYEN_ERROR_CHECK_SUM")));
218 if (isset($notificationItem[
"eventCode"])
219 && $notificationItem[
"eventCode"] === self::EVENT_CODE_AUTHORISATION
220 && filter_var($notificationItem[
"success"], FILTER_VALIDATE_BOOLEAN)
223 $description = Loc::getMessage(
"SALE_HPS_ADYEN_TRANSACTION").$notificationItem[
"pspReference"];
225 "PS_INVOICE_ID" => $notificationItem[
"pspReference"],
226 "PS_STATUS_CODE" => $notificationItem[
"eventCode"],
228 "PS_SUM" => $notificationItem[
"amount"][
"value"] / 100,
230 "PS_CURRENCY" => $notificationItem[
"amount"][
"currency"],
231 "PS_RESPONSE_DATE" =>
new Main\Type\DateTime()
234 if ($this->isSumCorrect(
$payment, $notificationItem[
"amount"][
"value"] / 100))
237 PaySystem\Logger::addDebugInfo(
243 $result->setOperationType(PaySystem\ServiceResult::MONEY_COMING);
248 $error = Loc::getMessage(
"SALE_HPS_ADYEN_ERROR_SUM");
249 $fields[
"PS_STATUS_DESCRIPTION"] .=
" ".$error;
258 $result->addError(
new Main\
Error(Loc::getMessage(
"SALE_HPS_ADYEN_ERROR_QUERY")));
269 private function isSignatureCorrect($notificationItem, $secretKey): bool
271 $data = $this->getDataToSign($notificationItem);
273 $calculatedHmacSignature = hash_hmac(
"sha256",
$data, pack(
"H*", $secretKey),
true);
274 if ($calculatedHmacSignature ===
false)
279 $calculatedHmacSignature = base64_encode($calculatedHmacSignature);
281 $requestHmacSignature = $notificationItem[
"additionalData"][
"hmacSignature"] ??
'';
282 return $calculatedHmacSignature === $requestHmacSignature;
289 private function getDataToSign($notificationItem): string
292 $notificationItem[
"pspReference"] ??
"",
293 $notificationItem[
"originalReference"] ??
"",
294 $notificationItem[
"merchantAccountCode"] ??
"",
295 $notificationItem[
"merchantReference"] ??
"",
296 $notificationItem[
"amount"][
"value"] ??
"",
297 $notificationItem[
"amount"][
"currency"] ??
"",
298 $notificationItem[
"eventCode"] ??
"",
299 $notificationItem[
"success"] ??
"",
303 $data = array_map(
static function($value) {
304 return str_replace(
":",
"\\:", str_replace(
"\\",
"\\\\", $value));
307 return implode(
":",
$data);
319 private function isSumCorrect(Payment
$payment,
$sum): bool
321 PaySystem\Logger::addDebugInfo(
322 "Adyen: adyenSum=".PriceMaths::roundPrecision(
$sum).
"; paymentSum=".PriceMaths::roundPrecision(
$payment->getSum())
325 return PriceMaths::roundPrecision(
$sum) === PriceMaths::roundPrecision(
$payment->getSum());
332 public function getPaymentIdFromRequest(Request
$request)
334 $notificationItem = static::getNotificationItem();
335 return $notificationItem[
"merchantReference"] ??
false;
348 private function getPaymentMethods(Payment
$payment,
string $channel): PaySystem\ServiceResult
350 $result =
new PaySystem\ServiceResult();
352 $headers = $this->getHeaders(
$payment);
353 $requestParameters = [
355 "amount" => $this->getAmount(
$payment),
356 "channel" => $channel,
362 $result->addError(
new Main\
Error(Loc::getMessage(
"SALE_HPS_ADYEN_ERROR_URL", [
363 "ACTION" =>
"paymentMethod",
368 $sendResult = $this->send(
$url, $requestParameters, $headers);
369 if (!$sendResult->isSuccess())
371 $result->addErrors($sendResult->getErrors());
375 $paymentMethods = [];
381 if (isset($group[
"types"]))
383 $paymentMethods[] = $group[
"types"];
388 $paymentMethods = array_merge(...$paymentMethods);
389 $result->setData($paymentMethods);
399 private function getApplePayWebSessionAction(Payment
$payment, Request
$request): PaySystem\ServiceResult
401 $applePay =
new PaySystem\ApplePay(
408 return $applePay->getWebSession(
$request->get(
"url"));
422 public function makePaymentAction(Payment
$payment, Request
$request): PaySystem\ServiceResult
424 $result =
new PaySystem\ServiceResult();
426 $headers = $this->getHeaders(
$payment);
427 $requestParameters = [
428 "amount" => $this->getAmount(
$payment),
431 "paymentMethod" => $this->getRequestPaymentMethod(
$request),
433 "paySystemId" => $this->service->getField(
"ID")
440 $result->addError(
new Main\
Error(Loc::getMessage(
"SALE_HPS_ADYEN_ERROR_URL", [
441 "ACTION" =>
"payment",
446 $sendResult = $this->send(
$url, $requestParameters, $headers);
447 if (!$sendResult->isSuccess())
449 $result->addErrors($sendResult->getErrors());
471 private function getIMessagePaymentAction(Payment
$payment, Request
$request): PaySystem\ServiceResult
473 $paymentMethodResult = $this->getPaymentMethods(
$payment,
"iOS");
474 if (!$paymentMethodResult->isSuccess())
476 $result =
new PaySystem\ServiceResult();
477 $result->addErrors($paymentMethodResult->getErrors());
481 $applePay =
new PaySystem\ApplePay(
489 $gateWayUrl =
new Main\Web\Uri(
$host.$request->getHttpHost().static::APPLE_PAY_INITIATIVE_CONTEXT);
490 $gateWayUrl->addParams([
492 'PAYSYSTEM_ID' => $this->service->getField(
'ID'),
493 'action' =>
'makePaymentAction'
496 $applePay->setInitiativeContext($gateWayUrl->getLocator());
500 "supportedNetworks" => $paymentMethodResult->getData(),
504 "paymentGatewayUrl" => $gateWayUrl->getLocator(),
515 private function getRequestPaymentMethod(Request
$request):
array
519 $psMode = $this->service->getField(
"PS_MODE");
520 if ($psMode === self::PAYMENT_METHOD_APPLE_PAY)
523 "type" =>
"applepay",
524 "applepay.token" => static::decode(
$request->get(
"paymentData")),
542 if ($liveEndpointUrlPrefix)
544 $url = str_replace(
"#LIVE_URL_PREFIX#", $liveEndpointUrlPrefix,
$url);
560 $testCheckoutUrl =
"https://checkout-test.adyen.com/".self::CHECKOUT_API_VERSION;
561 $activeCheckoutUrl =
"https://#LIVE_URL_PREFIX#-checkout-live.adyenpayments.com/checkout/".self::CHECKOUT_API_VERSION;
563 $testPaymentUrl =
"https://pal-test.adyen.com/pal/servlet/Payment/".self::PAYMENT_API_VERSION;
564 $activePaymentUrl =
"https://#LIVE_URL_PREFIX#-pal-live.adyenpayments.com/pal/servlet/Payment/".self::PAYMENT_API_VERSION;
568 self::TEST_URL => $testCheckoutUrl.
"/paymentMethods",
569 self::ACTIVE_URL => $activeCheckoutUrl.
"/paymentMethods"
572 self::TEST_URL => $testCheckoutUrl.
"/payments",
573 self::ACTIVE_URL => $activeCheckoutUrl.
"/payments"
576 self::TEST_URL => $testPaymentUrl.
"/refund",
577 self::ACTIVE_URL => $activePaymentUrl.
"/refund"
586 protected function isTestMode(Payment
$payment =
null): bool
598 "Content-Type" =>
"application/json",
600 "Idempotency-Key" => $this->getIdempotenceKey(),
607 private function getIdempotenceKey(): string
609 return sprintf(
"%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
610 mt_rand(0, 0xffff), mt_rand(0, 0xffff),
612 mt_rand(0, 0x0fff) | 0x4000,
613 mt_rand(0, 0x3fff) | 0x8000,
614 mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
628 public function refund(Payment
$payment, $refundableSum): PaySystem\ServiceResult
630 $result =
new PaySystem\ServiceResult();
632 $headers = $this->getHeaders(
$payment);
633 $requestParameters = [
634 "originalReference" =>
$payment->getField(
"PS_INVOICE_ID"),
635 "modificationAmount" => [
636 "value" => PriceMaths::roundPrecision($refundableSum * 100),
637 "currency" =>
$payment->getField(
"CURRENCY"),
646 $sendResult = $this->send(
$url, $requestParameters, $headers);
647 if ($sendResult->isSuccess())
650 if (
$response[
"response"] === self::RESPONSE_REFUND_RECEIVED)
652 $result->setOperationType(PaySystem\ServiceResult::MONEY_LEAVING);
657 $result->addErrors($sendResult->getErrors());
662 $result->addError(
new Main\
Error(Loc::getMessage(
"SALE_HPS_ADYEN_ERROR_URL", [
663 "ACTION" =>
"refund",
669 PaySystem\Logger::addError(
"Adyen: refund: ".implode(
"\n",
$result->getErrorMessages()));
688 $result =
new PaySystem\ServiceResult();
690 $httpClient =
new Web\HttpClient();
691 foreach ($headers as
$name => $value)
693 $httpClient->setHeader(
$name, $value);
702 PaySystem\Logger::addDebugInfo(
"Adyen: request data: ".
$postData);
707 $errors = $httpClient->getError();
716 PaySystem\Logger::addDebugInfo(
"Adyen: response data: ".
$response);
719 $responseHeader = $httpClient->getHeaders();
720 if ($responseHeader->get(
"Transient-Error") && $attempt < self::MAX_REQUEST_ATTEMPT)
722 $second = 2 ** $attempt;
731 $httpStatus = $httpClient->getStatus();
732 if ($httpStatus === self::HTTP_RESPONSE_CODE_OK)
736 if (isset(
$response[
"resultCode"]) &&
$response[
"resultCode"] !== self::RESULT_CODE_AUTHORISED)
738 if (
$response[
"resultCode"] === self::RESULT_CODE_ERROR ||
$response[
"resultCode"] === self::RESULT_CODE_REFUSED)
740 $result->addError(
new Main\
Error(Loc::getMessage(
"SALE_HPS_ADYEN_SEND_REFUSAL_REASON_ERROR", [
741 "#RESULT_CODE#" =>
$response[
"resultCode"],
742 "#REFUSAL_REASON#" =>
$response[
"refusalReason"],
747 $result->addError(
new Main\
Error(Loc::getMessage(
"SALE_HPS_ADYEN_SEND_RESULT_CODE_ERROR", [
748 "#RESULT_CODE#" =>
$response[
"resultCode"],
755 $result->addError(
new Main\
Error(Loc::getMessage(
"SALE_HPS_ADYEN_HTTP_STATUS", [
756 "#HTTP_STATUS#" => $httpStatus
789 $psMode = $this->service->getField(
"PS_MODE");
790 if ($psMode === self::PAYMENT_METHOD_APPLE_PAY)
809 $paymentMethodResult = $this->getPaymentMethods(
$payment,
"Web");
810 if (!$paymentMethodResult->isSuccess())
813 "ERRORS" => $paymentMethodResult->getErrors(),
817 $paymentMethodData = $paymentMethodResult->getData();
819 "SUPPORTED_METHOD" =>
"https://apple.com/apple-pay",
823 "GET_SESSION_ACTION" =>
"getApplePayWebSessionAction",
824 "MERCHANT_CAPABILITIES" => [
"supports3DS"],
825 "SUPPORTED_NETWORKS" => $paymentMethodData,
837 "value" => PriceMaths::roundPrecision(
$payment->getSum() * 100),
838 "currency" =>
$payment->getField(
"CURRENCY"),
845 private static function readFromStream()
847 return file_get_contents(
"php://input");
857 return Main\Web\Json::encode(
$data, JSON_UNESCAPED_UNICODE);
864 private static function decode(
$data)
868 return Main\Web\Json::decode(
$data);
870 catch (Main\ArgumentException $exception)
886 public function sendResponse(PaySystem\ServiceResult
$result, Request
$request)
895 "notificationResponse" => self::NOTIFICATION_RESPONSE_ACCEPTED
897 PaySystem\Logger::addDebugInfo(
"Adyen: response: ".
$result);
909 self::PAYMENT_METHOD_APPLE_PAY
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
if(!is_null($config))($config as $configItem)(! $configItem->isVisible()) $code
if($inWords) echo htmlspecialcharsbx(Number2Word_Rus(roundEx($totalVatSum $params['CURRENCY']