1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
handler.php
См. документацию.
1<?php
2
3namespace Sale\Handlers\PaySystem;
4
5use Bitrix\Main,
6 Bitrix\Main\Localization\Loc,
7 Bitrix\Main\Request,
8 Bitrix\Sale\Payment,
9 Bitrix\Sale\PaySystem,
10 Bitrix\Main\Web\HttpClient,
11 Bitrix\Sale\PaymentCollection,
12 Bitrix\Sale\PriceMaths;
13
14Loc::loadMessages(__FILE__);
15
20class UaPayHandler
22 implements PaySystem\IRefund
23{
24 const PAYMENT_STATUS_FINISHED = "FINISHED";
25
26 const INVOICE_ID_DELIMITER = "#";
27
39 public function initiatePay(Payment $payment, Request $request = null)
40 {
41 $result = new PaySystem\ServiceResult();
42
43 $invoiceResult = $this->createInvoice($payment);
44 if ($invoiceResult->isSuccess())
45 {
46 $result->setPsData($invoiceResult->getPsData());
47
48 $invoiceData = $invoiceResult->getData();
49 $params = [
50 "CURRENCY" => $payment->getField("CURRENCY"),
51 "SUM" => PriceMaths::roundPrecision($payment->getSum()),
52 "URL" => $invoiceData["paymentPageUrl"],
53 ];
54 $this->setExtraParams($params);
55
56 $showTemplateResult = $this->showTemplate($payment, "template");
57 if ($showTemplateResult->isSuccess())
58 {
59 $result->setTemplate($showTemplateResult->getTemplate());
60 }
61 else
62 {
63 $result->addErrors($showTemplateResult->getErrors());
64 }
65
66 if ($params["URL"])
67 {
68 $result->setPaymentUrl($params["URL"]);
69 }
70 }
71 else
72 {
73 $result->addErrors($invoiceResult->getErrors());
74 }
75
76 return $result;
77 }
78
88 private function createSession(Payment $payment)
89 {
90 $result = new PaySystem\ServiceResult();
91
92 $clientId = $this->getBusinessValue($payment, "UAPAY_CLIENT_ID");
93 if (!$clientId)
94 {
95 $result->addError(new Main\Error(Loc::getMessage("SALE_HPS_UAPAY_ERROR_CLIENT_ID")));
96 return $result;
97 }
98
99 $url = $this->getUrl($payment, "sessionCreate");
101 "params" => [
102 "clientId" => $this->getBusinessValue($payment, "UAPAY_CLIENT_ID")
103 ]
104 ];
105
106 $sendResult = $this->send($payment, $url, $requestParams);
107 if (!$sendResult->isSuccess())
108 {
109 $result->addErrors($sendResult->getErrors());
110 return $result;
111 }
112
113 $sendData = $sendResult->getData();
114
115 $validationResult = $this->validationResponse($payment, $sendData);
116 if (!$validationResult->isSuccess())
117 {
118 $result->addErrors($validationResult->getErrors());
119 return $result;
120 }
121
122 $payloadData = self::getPayload($sendData["data"]["token"]);
123 if (!$payloadData)
124 {
125 $result->addError(new Main\Error(Loc::getMessage("SALE_HPS_UAPAY_ERROR_PARSE_JWT")));
126 return $result;
127 }
128
129 PaySystem\Logger::addDebugInfo(__CLASS__.": createSession payload: ".self::encode($payloadData));
130
131 $result->setData(["id" => $payloadData["id"]]);
132 return $result;
133 }
134
145 private function createInvoice(Payment $payment)
146 {
147 $result = new PaySystem\ServiceResult();
148
149 $sessionResult = $this->createSession($payment);
150 if (!$sessionResult->isSuccess())
151 {
152 $result->addErrors($sessionResult->getErrors());
153 return $result;
154 }
155
156 $sessionData = $sessionResult->getData();
157
158 $url = $this->getUrl($payment, "invoicesCreate");
159 $extraInfo = [
160 "paySystemId" => $this->service->getField("ID"),
161 ];
163 "params" => [
164 "sessionId" => $sessionData["id"],
165 "systemType" => "ECOM"
166 ],
167 "data" => [
168 "externalId" => $payment->getField("ACCOUNT_NUMBER"),
169 "reusability" => false,
170 "type" => "PAY",
171 "callbackUrl" => $this->getBusinessValue($payment, "UAPAY_CALLBACK_URL"),
172 "description" => $this->getPaymentDescription($payment),
173 "amount" => (int)PriceMaths::roundPrecision($payment->getSum() * 100),
174 "redirectUrl" => $this->getRedirectUrl($payment),
175 "extraInfo" => self::encode($extraInfo),
176 ],
177 ];
178
179 if ($userEmail = $payment->getOrder()->getPropertyCollection()->getUserEmail())
180 {
181 $requestParams["data"]["email"] = $userEmail->getValue();
182 }
183
184 $sendResult = $this->send($payment, $url, $requestParams);
185 if (!$sendResult->isSuccess())
186 {
187 $result->addErrors($sendResult->getErrors());
188 return $result;
189 }
190
191 $sendData = $sendResult->getData();
192
193 $validationResult = $this->validationResponse($payment, $sendData);
194 if (!$validationResult->isSuccess())
195 {
196 $result->addErrors($validationResult->getErrors());
197 return $result;
198 }
199
200 $payloadData = self::getPayload($sendData["data"]["token"]);
201 if (!$payloadData)
202 {
203 $result->addError(new Main\Error(Loc::getMessage("SALE_HPS_UAPAY_ERROR_PARSE_JWT")));
204 return $result;
205 }
206
207 PaySystem\Logger::addDebugInfo(__CLASS__.": createInvoice payload: ".self::encode($payloadData));
208
209 $result->setPsData(["PS_INVOICE_ID" => $payloadData["id"]]);
210 $result->setData($payloadData);
211 return $result;
212 }
213
218 private function getRedirectUrl(Payment $payment)
219 {
220 return $this->getBusinessValue($payment, 'UAPAY_REDIRECT_URL') ?: $this->service->getContext()->getUrl();
221 }
222
233 public function refund(Payment $payment, $refundableSum)
234 {
235 $result = new PaySystem\ServiceResult();
236
237 $sessionResult = $this->createSession($payment);
238 if ($sessionResult->isSuccess())
239 {
240 $sessionData = $sessionResult->getData();
241
242 $psInvoiceId = $payment->getField("PS_INVOICE_ID");
243 $psInvoiceIdList = explode(self::INVOICE_ID_DELIMITER, $psInvoiceId);
244 if (count($psInvoiceIdList) == 2)
245 {
246 $url = $this->getUrl($payment, "paymentReverse");
248 "params" => [
249 "sessionId" => $sessionData["id"],
250 "invoiceId" => $psInvoiceIdList[0],
251 "paymentId" => $psInvoiceIdList[1],
252 ],
253 ];
254
255 $sendResult = $this->send($payment, $url, $requestParams);
256 if (!$sendResult->isSuccess())
257 {
258 $result->addErrors($sendResult->getErrors());
259 }
260
261 if ($sendResult->isSuccess())
262 {
263 $sendData = $sendResult->getData();
264 $validationResult = $this->validationResponse($payment, $sendData);
265 if (!$validationResult->isSuccess())
266 {
267 $result->addErrors($validationResult->getErrors());
268 }
269
270 if ($validationResult->isSuccess())
271 {
272 $payloadData = self::getPayload($sendData["data"]["token"]);
273 if ($payloadData)
274 {
275 PaySystem\Logger::addDebugInfo(__CLASS__.": refund payload: ".self::encode($payloadData));
276 }
277 else
278 {
279 $result->addError(new Main\Error(Loc::getMessage("SALE_HPS_UAPAY_ERROR_PARSE_JWT")));
280 }
281 }
282 }
283 }
284 else
285 {
286 $result->addError(new Main\Error(Loc::getMessage("SALE_HPS_UAPAY_ERROR_PAYMENT_ID")));
287 }
288 }
289 else
290 {
291 $result->addErrors($sessionResult->getErrors());
292 }
293
294 if ($result->isSuccess())
295 {
296 $result->setOperationType(PaySystem\ServiceResult::MONEY_LEAVING);
297 }
298 else
299 {
300 PaySystem\Logger::addError(__CLASS__.": refund: ".join("\n", $result->getErrorMessages()));
301 }
302
303 return $result;
304 }
305
317 private function send(Payment $payment, $url, array $params)
318 {
319 $result = new PaySystem\ServiceResult();
320 $httpClient = new HttpClient();
321
322 $headers = $this->getHeaders();
323 foreach ($headers as $name => $value)
324 {
325 $httpClient->setHeader($name, $value);
326 }
327
328 $params["iat"] = time();
329 $params["token"] = $this->getJwt($payment, $params);
330 $postData = self::encode($params);
331
332 PaySystem\Logger::addDebugInfo(__CLASS__.": request data: ".$postData);
333
334 $response = $httpClient->post($url, $postData);
335 if ($response === false)
336 {
337 $errors = $httpClient->getError();
338 foreach ($errors as $code =>$message)
339 {
340 $result->addError(new Main\Error($message, $code));
341 }
342
343 return $result;
344 }
345
346 PaySystem\Logger::addDebugInfo(__CLASS__.": response data: ".$response);
347
348 $httpStatus = $httpClient->getStatus();
349 if ($httpStatus !== 200)
350 {
351 $result->addError(new Main\Error(Loc::getMessage("SALE_HPS_UAPAY_ERROR_HTTP_STATUS", [
352 "#STATUS_CODE#" => $httpStatus
353 ])));
354 return $result;
355 }
356
357 $response = self::decode($response);
358 if ($response)
359 {
360 $result->setData($response);
361 }
362
363 return $result;
364 }
365
371 private function validationResponse(Payment $payment, $response)
372 {
373 $result = new PaySystem\ServiceResult();
374
375 if (!is_array($response))
376 {
377 $result->addError(new Main\Error(Loc::getMessage("SALE_HPS_UAPAY_ERROR_RESPONSE")));
378 return $result;
379 }
380
381 if (isset($response["status"]) && !$response["status"])
382 {
383 $result->addError(new Main\Error(Loc::getMessage("SALE_HPS_UAPAY_ERROR_RESPONSE_STATUS")));
384 return $result;
385 }
386
387 if (isset($response["error"]))
388 {
389 $result->addError(new Main\Error($response["error"]["message"], $response["error"]["code"]));
390 return $result;
391 }
392
393 if (!$this->isTokenCorrect($response["data"]["token"], $payment))
394 {
395 $result->addError(new Main\Error(Loc::getMessage("SALE_HPS_UAPAY_ERROR_CHECK_SUM")));
396 return $result;
397 }
398
399 return $result;
400 }
401
405 private function getHeaders()
406 {
407 $headers = [
408 "Content-Type" => "application/json",
409 ];
410
411 return $headers;
412 }
413
418 private static function getPayload($token)
419 {
420 $tokenPathList = explode(".", $token);
421 if (count($tokenPathList) != 3)
422 {
423 return null;
424 }
425
426 $payload = self::decode(self::urlSafeBase64Decode($tokenPathList[1]));
427 return $payload;
428 }
429
435 private function isTokenCorrect($token, Payment $payment)
436 {
437 $signKey = (string)$this->getBusinessValue($payment, "UAPAY_SIGN_KEY");
438
439 $tokenPathList = explode(".", $token);
440 if (count($tokenPathList) != 3)
441 {
442 return false;
443 }
444
445 list($headBase64, $bodyBase64, $cryptoBase64) = $tokenPathList;
446 $signature = self::urlSafeBase64Decode($cryptoBase64);
447
448 $hash = hash_hmac("sha256", $headBase64.".".$bodyBase64, $signKey, true);
449 if ($hash)
450 {
451 return hash_equals($signature, $hash);
452 }
453
454 return false;
455 }
456
463 private function getJwt(Payment $payment, array $payload)
464 {
465 $signKey = (string)$this->getBusinessValue($payment, "UAPAY_SIGN_KEY");
466
467 $search = ["+", "/", "="];
468 $replace = ["-", "_", ""];
469
470 $header = self::encode(["alg" => "HS256", "typ" => "JWT"]);
471 $base64UrlHeader = str_replace($search, $replace, base64_encode($header));
472
473 $payload = self::encode($payload);
474 $base64UrlPayload = str_replace($search, $replace, base64_encode($payload));
475
476 $signature = hash_hmac("sha256", $base64UrlHeader.".".$base64UrlPayload, $signKey, true);
477 $base64UrlSignature = str_replace($search, $replace, base64_encode($signature));
478 $jwt = $base64UrlHeader.".".$base64UrlPayload.".".$base64UrlSignature;
479 return $jwt;
480 }
481
485 public function getCurrencyList()
486 {
487 return ["UAH"];
488 }
489
500 public function processRequest(Payment $payment, Request $request)
501 {
502 $result = new PaySystem\ServiceResult();
503
504 $inputStream = self::readFromStream();
505 $data = self::decode($inputStream);
506 if ($payloadData = self::getPayload($data["token"]))
507 {
508 PaySystem\Logger::addDebugInfo(__CLASS__.": processRequest payloadData: ".self::encode($payloadData));
509 if ($this->isTokenCorrect($data["token"], $payment))
510 {
511 $paymentId = $payloadData["paymentId"] ?? $payloadData["id"];
512 if ($payloadData["paymentStatus"] === self::PAYMENT_STATUS_FINISHED && $paymentId)
513 {
514 $description = Loc::getMessage("SALE_HPS_UAPAY_TRANSACTION", [
515 "#ID#" => $payloadData["id"],
516 "#PAYMENT_NUMBER#" => $payloadData["paymentNumber"]
517 ]);
518 $invoiceId = $payloadData["invoiceId"] ?? $payloadData["orderId"];
519 $fields = array(
520 "PS_INVOICE_ID" => $invoiceId.self::INVOICE_ID_DELIMITER.$paymentId,
521 "PS_STATUS_CODE" => $payloadData["paymentStatus"],
522 "PS_STATUS_DESCRIPTION" => $description,
523 "PS_SUM" => $payloadData["amount"] / 100,
524 "PS_STATUS" => "N",
525 "PS_RESPONSE_DATE" => new Main\Type\DateTime()
526 );
527
528 if ($this->isSumCorrect($payment, $payloadData["amount"] / 100))
529 {
530 $fields["PS_STATUS"] = "Y";
531
532 PaySystem\Logger::addDebugInfo(
533 __CLASS__.": PS_CHANGE_STATUS_PAY=".$this->getBusinessValue($payment, "PS_CHANGE_STATUS_PAY")
534 );
535
536 if ($this->getBusinessValue($payment, "PS_CHANGE_STATUS_PAY") === "Y")
537 {
538 $result->setOperationType(PaySystem\ServiceResult::MONEY_COMING);
539 }
540 }
541 else
542 {
543 $error = Loc::getMessage("SALE_HPS_UAPAY_ERROR_SUM");
544 $fields["PS_STATUS_DESCRIPTION"] .= " ".$error;
545 $result->addError(new Main\Error($error));
546 }
547
548 $result->setPsData($fields);
549 }
550 }
551 else
552 {
553 $result->addError(new Main\Error(Loc::getMessage("SALE_HPS_UAPAY_ERROR_CHECK_SUM")));
554 }
555 }
556 else
557 {
558 $result->addError(new Main\Error(Loc::getMessage("SALE_HPS_UAPAY_ERROR_PARSE_JWT")));
559 }
560
561 if (!$result->isSuccess())
562 {
563 $error = __CLASS__.": processRequest: ".join("\n", $result->getErrorMessages());
564 PaySystem\Logger::addError($error);
565 }
566
567 return $result;
568 }
569
579 private function isSumCorrect(Payment $payment, $amount)
580 {
581 PaySystem\Logger::addDebugInfo(
582 __CLASS__.": sum=".PriceMaths::roundPrecision($amount)."; paymentSum=".PriceMaths::roundPrecision($payment->getSum())
583 );
584
585 return PriceMaths::roundPrecision($amount) === PriceMaths::roundPrecision($payment->getSum());
586 }
587
593 public static function isMyResponse(Request $request, $paySystemId)
594 {
595 $inputStream = self::readFromStream();
596 if ($inputStream)
597 {
598 $data = self::decode($inputStream);
599 if ($data !== false && isset($data["token"]))
600 {
601 $payloadData = self::getPayload($data["token"]);
602 if (!$payloadData)
603 {
604 return false;
605 }
606
607 if (isset($payloadData["extraInfo"]) && !is_array($payloadData["extraInfo"]))
608 {
609 $payloadData["extraInfo"] = self::decode($payloadData["extraInfo"]);
610 }
611
612 if (isset($payloadData["extraInfo"]["paySystemId"]) && ((int)$payloadData["extraInfo"]["paySystemId"] === (int)$paySystemId))
613 {
614 return true;
615 }
616 }
617 }
618
619 return false;
620 }
621
626 public function getPaymentIdFromRequest(Request $request)
627 {
628 $inputStream = self::readFromStream();
629 $data = self::decode($inputStream);
630 $payloadData = self::getPayload($data["token"]);
631 if (!$payloadData)
632 {
633 return false;
634 }
635
636 if (isset($payloadData["externalId"]))
637 {
638 return $payloadData["externalId"];
639 }
640
641 return false;
642 }
643
647 protected function getUrlList()
648 {
649 $testUrl = "https://api.demo.uapay.ua/api/";
650 $activeUrl = "https://api.uapay.ua/api/";
651
652 return [
653 "sessionCreate" => [
654 self::TEST_URL => $testUrl."sessions/create",
655 self::ACTIVE_URL => $activeUrl."sessions/create",
656 ],
657 "invoicesCreate" => [
658 self::TEST_URL => $testUrl."invoicer/invoices/create",
659 self::ACTIVE_URL => $activeUrl."invoicer/invoices/create",
660 ],
661 "paymentReverse" => [
662 self::TEST_URL => $testUrl."invoicer/payments/reverse",
663 self::ACTIVE_URL => $activeUrl."invoicer/payments/reverse",
664 ]
665 ];
666 }
667
672 protected function isTestMode(Payment $payment = null)
673 {
674 return ($this->getBusinessValue($payment, "UAPAY_TEST_MODE") === "Y");
675 }
676
684 private function getPaymentDescription(Payment $payment)
685 {
687 $collection = $payment->getCollection();
688 $order = $collection->getOrder();
689 $userEmail = $order->getPropertyCollection()->getUserEmail();
690
691 $description = str_replace(
692 [
693 "#PAYMENT_NUMBER#",
694 "#ORDER_NUMBER#",
695 "#PAYMENT_ID#",
696 "#ORDER_ID#",
697 "#USER_EMAIL#"
698 ],
699 [
700 $payment->getField("ACCOUNT_NUMBER"),
701 $order->getField("ACCOUNT_NUMBER"),
702 $payment->getId(),
703 $order->getId(),
704 ($userEmail) ? $userEmail->getValue() : ""
705 ],
706 $this->getBusinessValue($payment, "UAPAY_INVOICE_DESCRIPTION")
707 );
708
709 return $description;
710 }
711
717 private static function encode(array $data)
718 {
719 return Main\Web\Json::encode($data, JSON_UNESCAPED_UNICODE);
720 }
721
726 private static function decode($data)
727 {
728 try
729 {
730 return Main\Web\Json::decode($data);
731 }
732 catch (Main\ArgumentException $exception)
733 {
734 return false;
735 }
736 }
737
742 private static function urlSafeBase64Decode($input)
743 {
744 $remainder = mb_strlen($input) % 4;
745 if ($remainder)
746 {
747 $padLength = 4 - $remainder;
748 $input .= str_repeat("=", $padLength);
749 }
750 return base64_decode(strtr($input, "-_", "+/"));
751 }
752
756 private static function readFromStream()
757 {
758 return file_get_contents("php://input");
759 }
760}
$hash
Определения ajax_redirector.php:8
if(!Loader::includeModule('catalog')) if(!AccessController::getCurrent() ->check(ActionDictionary::ACTION_PRICE_EDIT)) if(!check_bitrix_sessid()) $request
Определения catalog_reindex.php:36
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
</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
$requestParams
Определения urlrewrite.php:46
$name
Определения menu_edit.php:35
trait Error
Определения error.php:11
$payment
Определения payment.php:14
$order
Определения payment.php:8
if(!function_exists("bx_hmac")) $amount
Определения payment.php:30
$message
Определения payment.php:8
$invoiceId
Определения result_rec.php:11
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"
Определения waybill.php:936
if($inWords) echo htmlspecialcharsbx(Number2Word_Rus(roundEx($totalVatSum $params['CURRENCY']
Определения template.php:799
$response
Определения result.php:21
$postData
Определения index.php:29
$clientId
Определения seo_client.php:18
$error
Определения subscription_card_product.php:20
$url
Определения iframe.php:7
$fields
Определения yandex_run.php:501