Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
cashboxrobokassa.php
1<?php
2namespace Bitrix\Sale\Cashbox;
3
7
8Main\Localization\Loc::loadMessages(__FILE__);
9
15{
16 public const CACHE_ID = 'BITRIX_CASHBOX_ROBOKASSA_ID';
17
18 private const URL = 'https://ws.roboxchange.com/RoboFiscal/Receipt/';
19
20 private const CODE_VAT_0 = 'vat0';
21 private const CODE_VAT_10 = 'vat10';
22 private const CODE_VAT_20 = 'vat20';
23
24 private const CHECK_PAYMENT_TYPE = 2;
25
26 private const MAX_NAME_LENGTH = 128;
27
28 public static function getName(): string
29 {
30 return Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_TITLE');
31 }
32
33 protected function getPrintUrl(): string
34 {
35 return self::URL . 'Attach';
36 }
37
38 protected function getCheckUrl(): string
39 {
40 return self::URL . 'Status';
41 }
42
47 public function buildCheckQuery(Check $check): array
48 {
49 $checkParamsResult = $this->checkParams($check);
50 if (!$checkParamsResult->isSuccess())
51 {
52 return [];
53 }
54
55 $payment = CheckManager::getPaymentByCheck($check);
56 if (!$payment)
57 {
58 return [];
59 }
60
61 $checkData = $check->getDataForCheck();
62
63 $request = Main\Application::getInstance()->getContext()->getRequest();
64 $protocol = $request->isHttps() ? 'https://' : 'http://';
65
66 $fields = [
67 'merchantId' => $this->getPaySystemSetting($payment, 'ROBOXCHANGE_SHOPLOGIN'),
68 'id' => $checkData['unique_id'],
69 'originId' => $payment->getId(),
70 'operation' => SellCheck::getType(),
71 'sno' => $this->getValueFromSettings('TAX', 'SNO'),
72 'url' => \urlencode($protocol . $request->getHttpHost()),
73 'total' => (string)Sale\PriceMaths::roundPrecision($checkData['total_sum']),
74 'client' => [
75 'email' => $checkData['client_email'],
76 'phone' => $checkData['client_phone'],
77 ],
78 'payments' => [],
79 'items' => [],
80 'vats' => [],
81 ];
82
83 foreach ($checkData['payments'] as $paymentItem)
84 {
85 $fields['payments'][] = [
86 'type' => self::CHECK_PAYMENT_TYPE,
87 'sum' => (string)Sale\PriceMaths::roundPrecision($paymentItem['sum']),
88 ];
89 }
90
91 $checkTypeMap = $this->getCheckTypeMap();
92 $paymentMethod = $checkTypeMap[$check::getType()];
93 $paymentObjectMap = $this->getPaymentObjectMap();
94 foreach ($checkData['items'] as $item)
95 {
96 $vat = $this->getValueFromSettings('VAT', $item['vat']);
97 $tax = $vat ?? $this->getValueFromSettings('VAT', 'NOT_VAT');
98
99 $receiptItem = [
100 'name' => mb_substr($item['name'], 0, self::MAX_NAME_LENGTH),
101 'quantity' => (string)$item['quantity'],
102 'sum' => (string)Sale\PriceMaths::roundPrecision($item['sum']),
103 'tax' => $tax,
104 'payment_method' => $paymentMethod,
105 'payment_object' => $paymentObjectMap[$item['payment_object']],
106 ];
107
108 if (!empty($item['marking_code']))
109 {
110 $receiptItem['nomenclature_code'] = $item['marking_code'];
111 }
112
113 $fields['items'][] = $receiptItem;
114
115 $fields['vats'][] = [
116 'type' => $tax,
117 'sum' => (string)Sale\PriceMaths::roundPrecision($item['vat_sum']),
118 ];
119 }
120
121 return $fields;
122 }
123
124 protected static function extractCheckData(array $data): array
125 {
126 $result = [];
127
128 $id = $data['CheckId'] ?? null;
129 if (!$id)
130 {
131 return $result;
132 }
133
134 $result['ID'] = $id;
135
136 $check = CheckManager::getObjectById($id);
137 if ($check)
138 {
139 $result['LINK_PARAMS'] = [
140 Check::PARAM_FISCAL_DOC_ATTR => $data['FiscalDocumentAttribute'],
141 Check::PARAM_FISCAL_DOC_NUMBER => $data['FiscalDocumentNumber'],
142 Check::PARAM_FN_NUMBER => $data['FnNumber'],
143 Check::PARAM_DOC_SUM => (float)$check->getField('SUM'),
144 Check::PARAM_CALCULATION_ATTR => $check::getCalculatedSign()
145 ];
146
147 if (!empty($data['FiscalDate']))
148 {
149 try
150 {
151 $dateTime = new Main\Type\DateTime($data['FiscalDate']);
152 $result['LINK_PARAMS'][Check::PARAM_DOC_TIME] = $dateTime->getTimestamp();
153 }
154 catch (Main\ObjectException $ex)
155 {}
156 }
157 }
158
159 return $result;
160 }
161
162 protected function processPrintResult(Sale\Result $result): Sale\Result
163 {
164 $processPrintResult = new Sale\Result();
165
166 $data = $result->getData();
167 $resultCode = (int)$data['ResultCode'];
168 switch ($resultCode)
169 {
170 case 0:
171 $processPrintResult->setData(['UUID' => ($data['OpKey'] ?? '')]);
172 break;
173
174 case 1:
175 $processPrintResult->addError(new Main\Error(Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_PRINT_ERROR_FORMAT')));
176 break;
177
178 case 2:
179 $processPrintResult->addError(new Main\Error(Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_PRINT_ERROR_NOT_ENOUGH_MONEY')));
180 break;
181
182 default:
183 $processPrintResult->addError(new Main\Error(Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_PRINT_ERROR_INTERNAL')));
184 break;
185 }
186
187 return $processPrintResult;
188 }
189
190 protected function getDataForCheck(Sale\Payment $payment): array
191 {
192 return [
193 'merchantId' => $this->getPaySystemSetting($payment, 'ROBOXCHANGE_SHOPLOGIN'),
194 'id' => $payment->getId(),
195 ];
196 }
197
198 protected function processCheckResult(Sale\Result $result): Sale\Result
199 {
200 $processCheckResult = new Sale\Result();
201
202 $data = $result->getData();
203 $resultCode = (int)$data['Code'];
204 switch ($resultCode)
205 {
206 case 1:
207 $processCheckResult->addError(new Main\Error(Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_PRINT_ERROR_WAIT')));
208 break;
209
210 case 0:
211 case 2:
212 if (!empty($data['Statuses']))
213 {
214 $statuses = [];
215 foreach ($data['Statuses'] as $status)
216 {
217 $statuses[] = $status;
218 }
219
220 $processCheckResult->setData($statuses);
221 }
222 break;
223
224 case 3:
225 $processCheckResult->addError(new Main\Error(Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_STATUS_ERROR_REGISTER')));
226 break;
227
228 default:
229 $processCheckResult->addError(new Main\Error(Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_STATUS_ERROR_PROCESSING')));
230 break;
231 }
232
233 return $processCheckResult;
234 }
235
236 protected function onAfterProcessCheck(Sale\Result $result, Sale\Payment $payment): Sale\Result
237 {
238 $onAfterProcessCheckResult = new Sale\Result();
239
240 $checkList = CheckManager::getList([
241 'select' => ['ID'],
242 'filter' => [
243 'ORDER_ID' => $payment->getOrder()->getId(),
244 ],
245 ])->fetchAll();
246
247 if ($checkList)
248 {
249 $statuses = $result->getData();
250 foreach ($statuses as $key => $status)
251 {
252 $resultCode = (int)$status['Code'];
253 if ($resultCode === 0)
254 {
255 continue;
256 }
257
258 switch ($resultCode)
259 {
260 case 1:
261 $onAfterProcessCheckResult->addError(new Main\Error(Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_PRINT_ERROR_WAIT')));
262 break;
263
264 case 2:
265 $status['CheckId'] = $checkList[$key]['ID'];
266 $applyCheckResult = static::applyCheckResult($status);
267 if (!$applyCheckResult->isSuccess())
268 {
269 $onAfterProcessCheckResult->addErrors($applyCheckResult->getErrors());
270 }
271 break;
272
273 case 3:
274 $onAfterProcessCheckResult->addError(new Main\Error(Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_STATUS_ERROR_REGISTER')));
275 break;
276
277 default:
278 $onAfterProcessCheckResult->addError(new Main\Error(Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_STATUS_ERROR_PROCESSING')));
279 break;
280 }
281 }
282 }
283 else
284 {
285 $onAfterProcessCheckResult->addError(new Main\Error(Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_CHECK_NOT_FOUND')));
286 }
287
288 return $onAfterProcessCheckResult;
289 }
290
303 protected function send(string $url, Sale\Payment $payment, array $fields, string $method = self::SEND_METHOD_HTTP_POST): Sale\Result
304 {
305 $result = new Sale\Result();
306
307 $startupHash = self::cutSign(
308 \base64_encode(
309 self::formatSign(
310 self::encode($fields)
311 )
312 )
313 );
314
315 $sign = self::cutSign(
316 \base64_encode(
317 \md5($startupHash . $this->getPaySystemSetting($payment, 'ROBOXCHANGE_SHOPPASSWORD'))
318 )
319 );
320
321 Logger::addDebugInfo(__CLASS__ . ': request data fields: ' . self::encode($fields));
322 Logger::addDebugInfo(__CLASS__ . ': request data: ' . $startupHash . '.' . $sign);
323
324 $httpClient = new Main\Web\HttpClient();
325 $response = $httpClient->post($url, $startupHash . '.' . $sign);
326 if ($response === false)
327 {
328 $result->addError(new Main\Error(Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_ERROR_EMPTY_RESPONSE')));
329
330 $errors = $httpClient->getError();
331 foreach ($errors as $code => $message)
332 {
333 $result->addError(new Main\Error($message, $code));
334 }
335
336 return $result;
337 }
338
339 Logger::addDebugInfo(__CLASS__ . ': response data: ' . $response);
340
341 $response = static::decode($response);
342 if (!$response)
343 {
344 $result->addError(new Main\Error(Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_ERROR_DECODE_RESPONSE')));
345 return $result;
346 }
347
348 $result->setData($response);
349
350 return $result;
351 }
352
357 protected static function formatSign(string $sign): string
358 {
359 return \strtr(
360 $sign,
361 [
362 '+' => '-',
363 '/' => '_',
364 ]
365 );
366 }
367
372 private static function cutSign(string $sign): string
373 {
374 return \preg_replace('/^(.*?)(=*)$/', '$1', $sign);
375 }
376
382 private static function encode(array $data)
383 {
384 return Main\Web\Json::encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
385 }
386
391 private static function decode($data)
392 {
393 try
394 {
395 return Main\Web\Json::decode($data);
396 }
397 catch (Main\ArgumentException $exception)
398 {
399 return false;
400 }
401 }
402
406 protected function getCheckTypeMap(): array
407 {
408 return [
409 SellCheck::getType() => 'full_payment',
410 AdvancePaymentCheck::getType() => 'advance',
411 PrepaymentCheck::getType() => 'prepayment',
412 FullPrepaymentCheck::getType() => 'full_prepayment',
413 ];
414 }
415
424 public static function getSettings($modelId = 0): array
425 {
426 $settings['VAT'] = [
427 'LABEL' => Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_SETTINGS_VAT'),
428 'REQUIRED' => 'Y',
429 'ITEMS' => [
430 'NOT_VAT' => [
431 'TYPE' => 'STRING',
432 'LABEL' => Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_SETTINGS_VAT_LABEL_NOT_VAT'),
433 'VALUE' => 'none'
434 ]
435 ]
436 ];
437
438 if (Main\Loader::includeModule('catalog'))
439 {
440 $dbRes = Catalog\VatTable::getList(['filter' => ['ACTIVE' => 'Y']]);
441 $vatList = $dbRes->fetchAll();
442 if ($vatList)
443 {
444 $defaultVatList = [
445 0 => self::CODE_VAT_0,
446 10 => self::CODE_VAT_10,
447 20 => self::CODE_VAT_20
448 ];
449
450 foreach ($vatList as $vat)
451 {
452 $value = '';
453 if (isset($defaultVatList[(int)$vat['RATE']]))
454 {
455 $value = $defaultVatList[(int)$vat['RATE']];
456 }
457
458 $settings['VAT']['ITEMS'][(int)$vat['ID']] = [
459 'TYPE' => 'STRING',
460 'LABEL' => $vat['NAME'].' ['.(int)$vat['RATE'].'%]',
461 'VALUE' => $value
462 ];
463 }
464 }
465 }
466
467 $settings['TAX'] = [
468 'LABEL' => Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_SETTINGS_SNO'),
469 'REQUIRED' => 'Y',
470 'ITEMS' => [
471 'SNO' => [
472 'TYPE' => 'ENUM',
473 'LABEL' => Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_SETTINGS_SNO_LABEL'),
474 'VALUE' => 'osn',
475 'OPTIONS' => [
476 'osn' => Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_SNO_OSN'),
477 'usn_income' => Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_SNO_UI'),
478 'usn_income_outcome' => Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_SNO_UIO'),
479 'envd' => Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_SNO_ENVD'),
480 'esn' => Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_SNO_ESN'),
481 'patent' => Main\Localization\Loc::getMessage('SALE_CASHBOX_ROBOKASSA_SNO_PATENT')
482 ]
483 ]
484 ]
485 ];
486
487 return $settings;
488 }
489
490 public static function getPaySystemCodeForKkm(): string
491 {
492 return 'ROBOXCHANGE_SHOPLOGIN';
493 }
494
498 private function getPaymentObjectMap(): array
499 {
500 return [
501 Check::PAYMENT_OBJECT_COMMODITY => 'commodity',
506 Check::PAYMENT_OBJECT_GAMBLING_BET => 'gambling_bet',
507 Check::PAYMENT_OBJECT_GAMBLING_PRIZE => 'gambling_prize',
509 Check::PAYMENT_OBJECT_LOTTERY_PRIZE => 'lottery_prize',
510 Check::PAYMENT_OBJECT_INTELLECTUAL_ACTIVITY => 'intellectual_activity',
511 Check::PAYMENT_OBJECT_AGENT_COMMISSION => 'agent_commission',
512 Check::PAYMENT_OBJECT_COMPOSITE => 'composite',
514 Check::PAYMENT_OBJECT_PROPERTY_RIGHT => 'property_right',
515 Check::PAYMENT_OBJECT_NON_OPERATING_GAIN => 'non-operating_gain',
516 Check::PAYMENT_OBJECT_SALES_TAX => 'sales_tax',
517 Check::PAYMENT_OBJECT_RESORT_FEE => 'resort_fee',
520 Check::PAYMENT_OBJECT_PENSION_INSURANCE_IP => 'pension_insurance_ip',
521 Check::PAYMENT_OBJECT_PENSION_INSURANCE => 'pension_insurance',
522 Check::PAYMENT_OBJECT_MEDICAL_INSURANCE_IP => 'medical_insurance_ip',
523 Check::PAYMENT_OBJECT_MEDICAL_INSURANCE => 'medical_insurance',
524 Check::PAYMENT_OBJECT_SOCIAL_INSURANCE => 'social_insurance',
525 Check::PAYMENT_OBJECT_CASINO_PAYMENT => 'casino_payment',
530 ];
531 }
532}
static getMessage($code, $replace=null, $language=null)
Definition loc.php:29
getValueFromSettings($name, $code)
Definition cashbox.php:201
getPaySystemSetting(Sale\Payment $payment, string $code)
send(string $url, Sale\Payment $payment, array $fields, string $method=self::SEND_METHOD_HTTP_POST)
onAfterProcessCheck(Sale\Result $result, Sale\Payment $payment)
const PAYMENT_OBJECT_SOCIAL_INSURANCE
Definition check.php:47
const PAYMENT_OBJECT_LOTTERY_PRIZE
Definition check.php:32
const PAYMENT_OBJECT_MEDICAL_INSURANCE_IP
Definition check.php:45
const PAYMENT_OBJECT_COMMODITY_MARKING_EXCISE
Definition check.php:50
const PAYMENT_OBJECT_NON_OPERATING_GAIN
Definition check.php:38
const PAYMENT_OBJECT_COMMODITY_MARKING_NO_MARKING_EXCISE
Definition check.php:49
const PAYMENT_OBJECT_RESORT_FEE
Definition check.php:40
const PAYMENT_OBJECT_PENSION_INSURANCE_IP
Definition check.php:43
const PAYMENT_OBJECT_PROPERTY_RIGHT
Definition check.php:37
const PAYMENT_OBJECT_GAMBLING_PRIZE
Definition check.php:30
const PAYMENT_OBJECT_COMMODITY_MARKING
Definition check.php:52
const PAYMENT_OBJECT_AGENT_COMMISSION
Definition check.php:34
const PAYMENT_OBJECT_MEDICAL_INSURANCE
Definition check.php:46
const PAYMENT_OBJECT_INTELLECTUAL_ACTIVITY
Definition check.php:33
const PAYMENT_OBJECT_GAMBLING_BET
Definition check.php:29
const PAYMENT_OBJECT_CASINO_PAYMENT
Definition check.php:48
const PAYMENT_OBJECT_PENSION_INSURANCE
Definition check.php:44
const PAYMENT_OBJECT_COMMODITY_MARKING_NO_MARKING
Definition check.php:51
static getPaymentByCheck(Check $check)
static getList(array $parameters=array())
static addDebugInfo(?string $message, $cashboxId=null)
Definition logger.php:43
static roundPrecision($value)