22 private const HANDLER_MODE_TEST =
'TEST';
23 private const HANDLER_MODE_ACTIVE =
'ACTIVE';
25 private const API_VERSION =
'v1';
26 private const HANDLER_TEST_URL =
'https://dev-api.checkbox.in.ua/api';
27 private const HANDLER_ACTIVE_URL =
'https://api.checkbox.in.ua/api';
29 private const OPERATION_SIGN_IN =
'cashier/signin';
30 private const OPERATION_CREATE_SHIFT =
'shifts';
31 private const OPERATION_CHECK_SHIFTS =
'cashier/shift';
32 private const OPERATION_CLOSE_SHIFT =
'shifts/close';
33 private const OPERATION_CREATE_CHECK =
'receipts/sell';
34 private const OPERATION_GET_CHECK =
'receipts';
36 private const MAX_CODE_LENGTH = 256;
37 private const MAX_NAME_LENGTH = 256;
39 private const HTTP_METHOD_GET =
'get';
40 private const HTTP_METHOD_POST =
'post';
41 private const HTTP_NO_REDIRECT =
false;
42 private const HTTP_RESPONSE_CODE_201 = 201;
43 private const HTTP_RESPONSE_CODE_202 = 202;
44 private const HTTP_RESPONSE_CODE_400 = 400;
45 private const HTTP_RESPONSE_CODE_401 = 401;
46 private const HTTP_RESPONSE_CODE_403 = 403;
47 private const HTTP_RESPONSE_CODE_422 = 422;
49 private const SHIFT_STATUS_OPENED =
'OPENED';
51 private const CHECK_STATUS_DONE =
'DONE';
52 private const CHECK_STATUS_ERROR =
'ERROR';
54 private const HEADER_TOKEN_TYPE =
'Bearer';
55 private const TOKEN_OPTION_NAME =
'cashbox_checkbox_token';
57 private const QUANTITY_MULTIPLIER = 1000;
58 private const PRICE_MULTIPLIER = 100;
60 private const DPS_URL =
'https://cabinet.tax.gov.ua/cashregs/check?';
62 private const CODE_NO_VAT =
'0';
63 private const CODE_VAT_0 =
'4';
64 private const CODE_VAT_7 =
'2';
65 private const CODE_VAT_20 =
'1';
67 private const OPEN_SHIFT_WAIT_SECONDS = 5;
68 private const OPEN_SHIFT_WAIT_ATTEMPTS = 2;
70 private const BITRIX_CLIENT_NAME =
'api_1c-bitrix';
77 private static function isCheckReturn(
Check $check)
86 $isReturn = self::isCheckReturn($check);
89 foreach ($checkData[
'items'] as $item)
93 $itemId = $item[
'entity']->getField(
'PRODUCT_ID');
94 $code = $item[
'properties'][
'ARTNUMBER'];
101 $code =
'delivery' . $item[
'entity']->getField(
'ID');
105 $goodEntry[
'good'] = [
106 'code' => mb_substr($code, 0, static::MAX_CODE_LENGTH),
107 'name' => mb_substr($item[
'name'], 0, static::MAX_NAME_LENGTH),
111 if ($vat && $vat !== static::CODE_NO_VAT)
113 $goodEntry[
'good'][
'tax'] = [$vat];
116 if ($item[
'barcode'])
118 $goodEntry[
'good'][
'barcode'] = $item[
'barcode'];
121 $goodEntry[
'quantity'] = $item[
'quantity'] * static::QUANTITY_MULTIPLIER;
122 $goodEntry[
'is_return'] = $isReturn;
123 $goods[] = $goodEntry;
128 if ($checkData[
'client_email'])
130 $delivery[
'email'] = $checkData[
'client_email'];
134 foreach ($checkData[
'payments'] as $payment)
138 'type' => $paymentType,
141 $payments[] = $paymentEntry;
146 'delivery' => $delivery,
147 'payments' => $payments,
166 $url = $this->getRequestUrl(static::OPERATION_GET_CHECK, [
'CHECK_ID' => $check->
getField(
'EXTERNAL_UUID')]);
167 $token = $this->getAccessToken();
170 'ACCESS_TOKEN' => $token,
175 $checkResult = $this->sendRequestWithAuthorization(self::HTTP_METHOD_GET, $url, $requestHeaders, $requestBody);
176 if (!$checkResult->isSuccess())
181 $response = $checkResult->getData();
182 $responseStatus = $response[
'status'];
184 switch ($responseStatus)
186 case static::CHECK_STATUS_DONE:
187 return static::applyCheckResult($response);
188 case static::CHECK_STATUS_ERROR:
189 $checkResult->addError(
new Main\
Error(
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_CHECK_PRINT_ERROR')));
200 if (!isset($data[
'id']))
206 if (empty($checkInfo))
211 $result[
'ID'] = $checkInfo[
'ID'];
212 $result[
'CHECK_TYPE'] = $checkInfo[
'TYPE'];
216 $result[
'LINK_PARAMS'] = [
234 $url = $this->getRequestUrl(static::OPERATION_CREATE_CHECK);
235 $token = $this->getAccessToken();
238 'ACCESS_TOKEN' => $token,
243 $printResult = $this->sendRequestWithAuthorization(self::HTTP_METHOD_POST, $url, $requestHeaders, $requestBody, self::HTTP_NO_REDIRECT);
244 if (!$printResult->isSuccess())
249 $response = $printResult->getData();
251 if ($response[
'http_code'] === self::HTTP_RESPONSE_CODE_400)
253 $openShiftResult = $this->openShift();
254 if (!$openShiftResult->isSuccess())
256 return $openShiftResult;
258 $this->addCloseShiftAgent();
260 $printResult = $this->sendRequestWithAuthorization(self::HTTP_METHOD_POST, $url, $requestHeaders, $requestBody, self::HTTP_NO_REDIRECT);
261 if (!$printResult->isSuccess())
265 $response = $printResult->getData();
268 $responseCode = $response[
'http_code'];
269 switch ($responseCode)
271 case self::HTTP_RESPONSE_CODE_201:
274 $printResult->setData([
'UUID' => $response[
'id']]);
278 $printResult->addError(
new Main\
Error(
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_CHECK_PRINT_ERROR')));
281 case self::HTTP_RESPONSE_CODE_422:
282 if ($response[
'detail'])
284 foreach ($response[
'detail'] as $errorDetail)
286 $printResult->addError(
new Main\
Error($errorDetail[
'msg']));
291 $printResult->addError(
new Main\
Error(
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_CHECK_PRINT_ERROR')));
295 if ($response[
'message'])
297 $printResult->addError(
new Main\
Error($response[
'message']));
301 $printResult->addError(
new Main\
Error(
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_CHECK_PRINT_ERROR')));
308 private function addCloseShiftAgent()
310 $agentName =
'Bitrix\Sale\Cashbox\CashboxCheckbox::closeShiftAgent(' . $this->
getField(
'ID') .
');';
311 $agentTime = Main\Type\DateTime::createFromPhp(date_create(
'today 23:50'));
312 \CAgent::AddAgent($agentName,
'sale',
'Y', 0,
'',
'Y', $agentTime);
324 if ($cashbox && $cashbox instanceof
self)
326 $closeShiftResult = $cashbox->closeShift();
327 if (!$closeShiftResult->isSuccess())
329 $closeShiftErrors = $closeShiftResult->getErrorCollection();
330 foreach ($closeShiftErrors as $error)
332 if ($error instanceof Errors\
Warning)
345 private function sendRequest(
string $method,
string $url, array $headersData = [], array $bodyData = [],
bool $allowRedirect =
true):
Result
349 $requestHeaders = static::getHeaders($headersData);
350 $requestBody = static::encode($bodyData);
353 $httpClient->setRedirect($allowRedirect);
354 $httpClient->setHeaders($requestHeaders);
356 if ($method === self::HTTP_METHOD_POST)
358 $response = $httpClient->post($url, $requestBody);
362 $response = $httpClient->get($url);
366 $responseData = static::decode($response);
367 $responseData[
'http_code'] = $httpClient->getStatus();
368 $result->addData($responseData);
372 $error = $httpClient->getError();
373 foreach ($error as $code =>$message)
375 $result->addError(
new Main\Error($message, $code));
382 private function sendRequestWithAuthorization(
string $method,
string $url, array $headersData = [], array $bodyData = [],
bool $allowRedirect =
true): Result
384 $firstRequestResult = $this->sendRequest($method, $url, $headersData, $bodyData, $allowRedirect);
385 if (!$firstRequestResult->isSuccess())
387 return $firstRequestResult;
390 $firstRequestResponse = $firstRequestResult->getData();
391 $badResponseCodes = [self::HTTP_RESPONSE_CODE_401, self::HTTP_RESPONSE_CODE_403];
392 if (!in_array($firstRequestResponse[
'http_code'], $badResponseCodes))
394 return $firstRequestResult;
397 $headersDataWithNewToken = $headersData;
398 $requestTokenResult = $this->requestAccessToken();
399 if (!$requestTokenResult->isSuccess())
401 return $requestTokenResult;
403 $newToken = $requestTokenResult->get(
'token');
404 $headersDataWithNewToken[
'ACCESS_TOKEN'] = $newToken;
406 $secondRequestResult = $this->sendRequest($method, $url, $headersDataWithNewToken, $bodyData, $allowRedirect);
407 return $secondRequestResult;
410 private static function getAuthorizationHeaderValue(
string $token): ?string
414 return static::HEADER_TOKEN_TYPE .
' ' . $token;
420 private static function getHeaders(array $headersData): array
422 $accessToken = $headersData[
'ACCESS_TOKEN'] ??
'';
424 'Authorization' => static::getAuthorizationHeaderValue($accessToken),
425 'X-License-Key' => $headersData[
'LICENSE_KEY'],
426 'X-Client-Name' => static::BITRIX_CLIENT_NAME,
427 'X-Client-Version' =>
'1.0',
431 private static function encode(array $data)
433 return Main\Web\Json::encode($data, JSON_UNESCAPED_UNICODE);
436 private static function decode(
string $data)
438 return Main\Web\Json::decode($data);
441 private function getRequestUrl(
string $action, array $requestParams = []): string
443 $url = static::HANDLER_ACTIVE_URL;
447 $url = static::HANDLER_TEST_URL;
450 $url .=
'/' . static::API_VERSION;
454 case static::OPERATION_CREATE_SHIFT:
455 $url .=
'/' . static::OPERATION_CREATE_SHIFT;
457 case static::OPERATION_CHECK_SHIFTS:
458 $url .=
'/' . static::OPERATION_CHECK_SHIFTS;
460 case static::OPERATION_CLOSE_SHIFT:
461 $url .=
'/' . static::OPERATION_CLOSE_SHIFT;
463 case static::OPERATION_CREATE_CHECK:
464 $url .=
'/' . static::OPERATION_CREATE_CHECK;
466 case static::OPERATION_GET_CHECK:
467 $url .=
'/' . static::OPERATION_GET_CHECK .
'/' . $requestParams[
'CHECK_ID'];
469 case static::OPERATION_SIGN_IN:
470 $url .=
'/' . static::OPERATION_SIGN_IN;
473 throw new Main\SystemException();
479 private function getTokenOptionName(): string
481 return static::TOKEN_OPTION_NAME .
'_' . $this->
getField(
'ID');
484 private function getAccessToken(): string
486 $optionName = $this->getTokenOptionName();
487 return Main\Config\Option::get(
'sale', $optionName,
'');
490 private function setAccessToken(
string $token): void
492 $optionName = $this->getTokenOptionName();
493 Main\Config\Option::set(
'sale', $optionName, $token);
496 private function requestAccessToken(): Result
498 $result =
new Result();
500 $url = $this->getRequestUrl(static::OPERATION_SIGN_IN);
509 $requestResult = $this->sendRequest(self::HTTP_METHOD_POST, $url, $headersData, $requestData);
511 if (!$requestResult->isSuccess())
513 return $requestResult;
516 $response = $requestResult->getData();
518 if ($response[
'http_code'] === self::HTTP_RESPONSE_CODE_403)
520 $result->addError(
new Main\
Error(
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_AUTHORIZATION_ERROR')));
524 if ($response[
'access_token'])
526 $token = $response[
'access_token'];
527 $this->setAccessToken($token);
528 $result->set(
'token', $token);
536 private function getCurrentShift(): Result
538 $url = $this->getRequestUrl(static::OPERATION_CHECK_SHIFTS);
539 $token = $this->getAccessToken();
542 'ACCESS_TOKEN' => $token,
547 $result = $this->sendRequestWithAuthorization(self::HTTP_METHOD_GET, $url, $requestHeaders, $requestBody);
551 private function openShift(): Result
553 $url = $this->getRequestUrl(static::OPERATION_CREATE_SHIFT);
554 $token = $this->getAccessToken();
557 'ACCESS_TOKEN' => $token,
563 $openShiftResult = $this->sendRequestWithAuthorization(self::HTTP_METHOD_POST, $url, $requestHeaders, $requestBody);
564 if (!$openShiftResult->isSuccess())
566 return $openShiftResult;
569 $response = $openShiftResult->getData();
571 switch ($response[
'http_code'])
573 case self::HTTP_RESPONSE_CODE_202:
575 $openShiftSuccess =
false;
576 while ($waitAttempts < static::OPEN_SHIFT_WAIT_ATTEMPTS)
578 sleep(static::OPEN_SHIFT_WAIT_SECONDS);
579 $currentShiftResult = $this->getCurrentShift();
580 if (!$currentShiftResult->isSuccess())
582 return $currentShiftResult;
585 $currentShiftStatus = $currentShiftResult->getData()[
'status'];
586 if ($currentShiftStatus === static::SHIFT_STATUS_OPENED)
588 $openShiftSuccess =
true;
594 if (!$openShiftSuccess)
596 $openShiftResult->addError(
new Main\
Error(
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_SHIFT_OPEN_ERROR')));
597 return $openShiftResult;
600 return $openShiftResult;
601 case self::HTTP_RESPONSE_CODE_400:
602 $currentShiftResult = $this->getCurrentShift();
603 if (!$currentShiftResult->isSuccess())
605 return $currentShiftResult;
608 $currentShift = $currentShiftResult->getData();
609 if ($currentShift[
'status'] && $currentShift[
'status'] === static::SHIFT_STATUS_OPENED)
611 $openShiftResult->addWarning(
new ResultWarning(
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_SHIFT_ALREADY_OPENED')));
612 return $openShiftResult;
615 $openShiftResult->addError(
new Main\
Error(
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_SHIFT_OPEN_ERROR')));
616 return $openShiftResult;
618 $openShiftResult->addError(
new Main\
Error(
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_SHIFT_OPEN_ERROR')));
619 return $openShiftResult;
623 private function closeShift(): Result
625 $url = $this->getRequestUrl(static::OPERATION_CLOSE_SHIFT);
626 $token = $this->getAccessToken();
629 'ACCESS_TOKEN' => $token,
634 $closeShiftResult = $this->sendRequestWithAuthorization(self::HTTP_METHOD_POST, $url, $requestHeaders, $requestBody);
635 if (!$closeShiftResult->isSuccess())
637 return $closeShiftResult;
640 $response = $closeShiftResult->getData();
642 if ($response[
'http_code'] !== self::HTTP_RESPONSE_CODE_202)
644 $closeShiftResult->addError(
new Main\
Error(
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_SHIFT_CLOSE_ERROR')));
647 return $closeShiftResult;
662 'LABEL' =>
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_SETTINGS_AUTH_LOGIN_LABEL'),
666 'LABEL' =>
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_SETTINGS_AUTH_PASSWORD_LABEL'),
670 'LABEL' =>
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_SETTINGS_AUTH_LICENSE_KEY_LABEL'),
675 'LABEL' =>
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_SETTINGS_INTERACTION'),
679 'LABEL' =>
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_SETTINGS_HANDLER_MODE_LABEL'),
681 self::HANDLER_MODE_ACTIVE =>
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_MODE_ACTIVE'),
682 self::HANDLER_MODE_TEST =>
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_MODE_TEST'),
695 'LABEL' =>
Loc::getMessage(
'SALE_CASHBOX_CHECKBOX_SETTINGS_VAT_LABEL_NOT_VAT'),
696 'VALUE' => static::CODE_NO_VAT,
701 if (Main\Loader::includeModule(
'catalog'))
703 $dbRes = Catalog\VatTable::getList([
'filter' => [
'ACTIVE' =>
'Y']]);
704 $vatList = $dbRes->fetchAll();
708 0 => static::CODE_VAT_0,
709 7 => static::CODE_VAT_7,
710 20 => static::CODE_VAT_20,
713 foreach ($vatList as $vat)
715 $value = $defaultVatList[(int)$vat[
'RATE']] ??
'';
717 $settings[
'VAT'][
'ITEMS'][(int)$vat[
'ID']] = [
719 'LABEL' => $vat[
'NAME'].
' ['.(
int)$vat[
'RATE'].
'%]',
740 return static::DPS_URL.implode(
'&', $queryParams);