21 private const MAX_NAME_LENGTH = 128;
23 private const URL =
'https://api.yookassa.ru/v3/receipts/';
25 private const CODE_NO_VAT = 1;
26 private const CODE_VAT_0 = 2;
27 private const CODE_VAT_10 = 3;
28 private const CODE_VAT_20 = 4;
30 private const SETTLEMENT_TYPE_PREPAYMENT =
'prepayment';
31 private const CHECK_TYPE_PAYMENT =
'payment';
33 private const MARK_CODE_TYPE_GS1M =
'gs_1m';
43 if (!$checkParamsResult->isSuccess())
61 if (isset($checkData[
'client_email']))
63 $fields[
'customer'][
'email'] = $checkData[
'client_email'];
66 if (isset($checkData[
'client_phone']))
68 $phoneParser = PhoneNumber\Parser::getInstance();
71 $phoneNumber = $phoneParser->parse($checkData[
'client_phone']);
72 if ($phoneNumber->isValid())
80 $paymentMode = $paymentModeMap[$check::getType()];
81 $paymentObjectMap = $this->getPaymentObjectMap();
83 foreach ($checkData[
'items'] as $item)
92 'description' => mb_substr($item[
'name'], 0, self::MAX_NAME_LENGTH),
95 'currency' => (string)$item[
'currency'],
97 'vat_code' => (
int)$vat,
98 'quantity' => (string)$item[
'quantity'],
99 'measure' => (
string)$measure,
100 'payment_subject' => $paymentObjectMap[$item[
'payment_object']],
101 'payment_mode' => $paymentMode,
104 if (!empty($item[
'marking_code']))
106 $receiptItem[
'mark_code_info'] = $this->buildPositionMarkCode($item);
109 $fields[
'items'][] = $receiptItem;
112 if ($this->needDataForSecondCheck($payment))
114 $fields[
'send'] =
true;
115 $fields[
'type'] = self::CHECK_TYPE_PAYMENT;
116 $fields[
'payment_id'] = $payment->getField(
'PS_INVOICE_ID');
117 $fields[
'settlements'] = [];
119 foreach ($checkData[
'payments'] as $paymentItem)
121 $fields[
'settlements'][] = [
122 'type' => self::SETTLEMENT_TYPE_PREPAYMENT,
125 'currency' => (string)$paymentItem[
'currency'],
134 private function buildPositionMarkCode(array $item): array
137 self::MARK_CODE_TYPE_GS1M => $item[
'marking_code'],
154 'payment_id' => $payment->getField(
'PS_INVOICE_ID'),
163 $headers = $this->getHeaders($payment);
164 foreach ($headers as $name => $value)
166 $httpClient->setHeader($name, $value);
169 if ($method === self::SEND_METHOD_HTTP_POST)
171 $data = self::encode($fields);
173 $response = $httpClient->post($url, $data);
177 $uri =
new Uri($url);
178 $uri->addParams($fields);
179 $response = $httpClient->get($uri->getUri());
182 if ($response ===
false || $response ===
'')
186 $errors = $httpClient->getError();
187 foreach ($errors as $code => $message)
189 $result->addError(
new Error($message, $code));
197 $response = static::decode($response);
204 $result->setData($response);
217 $data = $result->getData();
222 if (isset($data[
'type']) && $data[
'type'] ===
'error')
224 $errorCode = $data[
'code'] ??
'';
227 case 'internal_server_error':
228 case 'too_many_requests':
229 $processCheckResult->addError(
new Error(
Loc::getMessage(
'SALE_CASHBOX_YOOKASSA_ERROR_CHECK_WAIT')));
233 $processCheckResult->addError(
new Error(
Loc::getMessage(
'SALE_CASHBOX_YOOKASSA_ERROR_CHECK_PROCESSING')));
237 return $processCheckResult;
240 $processCheckResult->setData($data);
242 return $processCheckResult;
251 'ORDER_ID' => $payment->getOrderId(),
253 'order' => [
'ID' =>
'DESC'],
256 $data = $result->getData();
258 if (isset($data[
'type']) && $data[
'type'] ===
'list')
260 $checkData = $data[
'items'] ?? [];
268 'checkId' => $checkList[0][
'ID'],
270 'MESSAGE' =>
Loc::getMessage(
'SALE_CASHBOX_YOOKASSA_ERROR_CHECK_NOT_FOUND'),
271 'TYPE' => Errors\Error::TYPE,
274 $applyCheckResult = static::applyCheckResult($externalCheck);
275 $onAfterProcessCheckResult->addErrors($applyCheckResult->getErrors());
278 foreach ($checkData as $key => $externalCheck)
280 $checkStatus = $externalCheck[
'status'] ??
'';
281 switch ($checkStatus)
284 $externalCheck[
'error'] = [
285 'MESSAGE' =>
Loc::getMessage(
'SALE_CASHBOX_YOOKASSA_STATUS_CHECK_PENDING'),
286 'TYPE' => Errors\Warning::TYPE,
291 $externalCheck[
'error'] = [
292 'MESSAGE' =>
Loc::getMessage(
'SALE_CASHBOX_YOOKASSA_STATUS_CHECK_CANCELLED'),
293 'TYPE' => Errors\Error::TYPE,
298 $externalCheck[
'checkId'] = $checkList[$key][
'ID'];
299 $applyCheckResult = static::applyCheckResult($externalCheck);
300 if (!$applyCheckResult->isSuccess())
302 $onAfterProcessCheckResult->addErrors($applyCheckResult->getErrors());
308 $onAfterProcessCheckResult->addError(
new Error(
Loc::getMessage(
'SALE_CASHBOX_YOOKASSA_ERROR_CHECK_NOT_FOUND')));
311 return $onAfterProcessCheckResult;
318 $id = $data[
'checkId'] ??
null;
327 $result[
'ERROR'] = $data[
'error'];
332 $result[
'EXTERNAL_UUID'] = $data[
'id'];
338 $result[
'LINK_PARAMS'] = [
347 if (!empty($data[
'registered_at']))
365 return 'YANDEX_CHECKOUT_SHOP_ID';
372 return [md5(static::getYandexToken())];
375 return parent::getKkmValue($service);
388 'LABEL' =>
Loc::getMessage(
'SALE_CASHBOX_YOOKASSA_SETTINGS_SNO_LABEL'),
409 'LABEL' =>
Loc::getMessage(
'SALE_CASHBOX_YOOKASSA_SETTINGS_VAT_LABEL_NOT_VAT'),
410 'VALUE' => self::CODE_NO_VAT,
415 if (Loader::includeModule(
'catalog'))
417 $dbRes = \Bitrix\Catalog\VatTable::getList([
'filter' => [
419 'EXCLUDE_VAT' =>
'N',
421 $vatList = $dbRes->fetchAll();
425 0 => self::CODE_VAT_0,
426 10 => self::CODE_VAT_10,
427 20 => self::CODE_VAT_20,
430 foreach ($vatList as $vat)
432 $value = $defaultVatList[(int)$vat[
'RATE']] ??
'';
434 $settings[
'VAT'][
'ITEMS'][(int)$vat[
'ID']] = [
436 'LABEL' => $vat[
'NAME'] .
' (' . (
int)$vat[
'RATE'] .
'%)',
446 'LABEL' =>
Loc::getMessage(
'SALE_CASHBOX_MEASURE_SUPPORT_SETTINGS_DEFAULT_VALUE'),
450 if (Loader::includeModule(
'catalog'))
452 $measuresList = \CCatalogMeasure::getList();
453 while ($measure = $measuresList->fetch())
455 $measureItems[$measure[
'CODE']] = [
457 'LABEL' => $measure[
'MEASURE_TITLE'],
463 $settings[
'MEASURE'] = [
467 'ITEMS' => $measureItems,
499 private function getPaymentObjectMap(): array
546 private function needDataForSecondCheck(
Sale\
Payment $payment): bool
548 return (
bool)$payment->getField(
'PS_INVOICE_ID');
551 private function getHeaders(
Sale\
Payment $payment): array
554 'Content-Type' =>
'application/json',
555 'Idempotence-Key' => $this->getIdempotenceKey(),
560 $headers[
'Authorization'] = $this->getAuthorizationHeader($payment);
562 catch (\Exception $ex)
564 $headers[
'Authorization'] =
'Basic '.$this->getBasicAuthString($payment);
570 private function getIdempotenceKey(): string
572 return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
573 mt_rand(0, 0xffff), mt_rand(0, 0xffff),
575 mt_rand(0, 0x0fff) | 0x4000,
576 mt_rand(0, 0x3fff) | 0x8000,
577 mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
581 private function getAuthorizationHeader(
Sale\Payment $payment)
585 $token = static::getYandexToken();
586 return 'Bearer '.$token;
589 return 'Basic '.$this->getBasicAuthString($payment);
592 private static function isOAuth(): bool
594 return Main\Config\Option::get(
'sale',
'YANDEX_CHECKOUT_OAUTH',
false) ==
true;
597 private static function getYandexToken()
599 if (!Main\Loader::includeModule(
'seo'))
604 $authAdapter = Seo\Checkout\Service::getAuthAdapter(Seo\Checkout\Service::TYPE_YOOKASSA);
605 $token = $authAdapter->getToken();
608 $authAdapter = Seo\Checkout\Service::getAuthAdapter(Seo\Checkout\Service::TYPE_YANDEX);
609 $token = $authAdapter->getToken();
615 private function getBasicAuthString(
Sale\Payment $payment)
617 return base64_encode(
628 private static function decode(
string $data)
632 return Main\Web\Json::decode($data);
634 catch (Main\ArgumentException $exception)
640 private static function encode(array $data)
642 return Main\Web\Json::encode($data, JSON_UNESCAPED_UNICODE);
static loadMessages($file)
static getMessage($code, $replace=null, $language=null)
const PARAM_CALCULATION_ATTR
const PARAM_FISCAL_DOC_ATTR
const PARAM_FISCAL_DOC_NUMBER
const PARAM_FISCAL_RECEIPT_NUMBER
getValueFromSettings($name, $code)
getPaySystemSetting(Sale\Payment $payment, string $code)
checkParams(Check $check)
const SEND_METHOD_HTTP_GET
send(string $url, Sale\Payment $payment, array $fields, string $method=self::SEND_METHOD_HTTP_POST)
static getPaySystemCodeForKkm()
onAfterProcessCheck(Sale\Result $result, Sale\Payment $payment)
static getSettings($modelId=0)
getDataForCheck(Sale\Payment $payment)
buildCheckQuery(Check $check)
static getKkmValue(Sale\PaySystem\Service $service)
static extractCheckData(array $data)
processCheckResult(Sale\Result $result)
processPrintResult(Sale\Result $result)
const PAYMENT_OBJECT_EXCISE
const PAYMENT_OBJECT_LOTTERY
const PAYMENT_OBJECT_SOCIAL_INSURANCE
const PAYMENT_OBJECT_COMPOSITE
const PAYMENT_OBJECT_LOTTERY_PRIZE
const PAYMENT_OBJECT_MEDICAL_INSURANCE_IP
const PAYMENT_OBJECT_COMMODITY_MARKING_EXCISE
const PAYMENT_OBJECT_NON_OPERATING_GAIN
const PAYMENT_OBJECT_COMMODITY_MARKING_NO_MARKING_EXCISE
const PAYMENT_OBJECT_RESORT_FEE
const PAYMENT_OBJECT_PENSION_INSURANCE_IP
const PAYMENT_OBJECT_FINE
const PAYMENT_OBJECT_PROPERTY_RIGHT
const PAYMENT_OBJECT_GAMBLING_PRIZE
const PAYMENT_OBJECT_COMMODITY_MARKING
const PAYMENT_OBJECT_COMMODITY
const PAYMENT_OBJECT_AGENT_COMMISSION
const PAYMENT_OBJECT_ANOTHER
const PAYMENT_OBJECT_DEPOSIT
const PAYMENT_OBJECT_MEDICAL_INSURANCE
const PAYMENT_OBJECT_SERVICE
const PAYMENT_OBJECT_INTELLECTUAL_ACTIVITY
const PAYMENT_OBJECT_INSURANCE_PREMIUM
const PAYMENT_OBJECT_AGENT_WITHDRAWALS
const PAYMENT_OBJECT_PAYMENT
const PAYMENT_OBJECT_EXPENSE
const PAYMENT_OBJECT_GAMBLING_BET
const PAYMENT_OBJECT_CASINO_PAYMENT
const PAYMENT_OBJECT_PENSION_INSURANCE
const PAYMENT_OBJECT_COMMODITY_MARKING_NO_MARKING
const PAYMENT_OBJECT_SALES_TAX
static getPaymentByCheck(Check $check)
static getList(array $parameters=array())
static getObjectById($id)
static addDebugInfo(?string $message, $cashboxId=null)
static getTag2108Value(?string $measureCode)
static roundPrecision($value)