Experience in integrating Atol cash desk with own CRM trading

  • Tutorial
Around the online box office recently, wild excitement, on July 1, 2019, the last postponement ends, so I had to deal with this issue. Those who have 1C or another system especially can not strain, but if you have your own self-written system, then integration with online cash registers also falls on your shoulders.

My experience is useful for integration with Atol cash desks in the network data exchange mode, your program can send data to the Atol web server both on the localhost and on the local network, you can send it even from the AJAX browser, even from the server via CURL, therefore, no matter what language your corporate software is written in, everything is cross-platform.

I’ve come across the Atol 30f checkout box, it’s such a simple typewriter with a black box (FN), it’s just right when all the logic for ordering is on external software, and not on the software built into the cashier. In addition, devices of this type are relatively inexpensive, compared to android counterparts.

I would also like to note that the “specialists” of some companies involved in support do not know at all that Atol with version 10 has a built-in web server in the driver that accepts JSON tasks, moreover, this driver can also be installed on linux, judging by the number of ready-made solutions on the raspberries, I can assume that it can also be installed there, in the distribution of the 10th version of the driver, the installer for arm is present.

The planned scheme is something like this - there is CRM that runs on the server on the local network, it is opened from browsers, from the server side checks will be sent to PHP through curl and printed at the cash register. And the cash desk itself is connected to any computer on Windows on the same network.

They say that if you do not activate the cashier, then it can work in printer mode and print that the check is invalid, but I could not verify it, I had to do penny sales and returns.

The driver of the tenth version is downloaded here .

Before installing, you need to install Java of the same bit depth as the driver, otherwise the checkbox web-server will not be available, if you install the 64-bit KKT driver, then Java x64.

It seems that, logically, you need to install a 64-bit driver on a 64-bit system, but some 32-bit software will not be able to work with it (like this applies to 1C if it is 32-bit).



At the end of the installation there is a checkmark - to configure the web server, if it was not installed, then you need to go to the browser at 127.0.0.1 : 16732 / settings, check the box "activate server" and save.





After that, you need to restart the server through START-> ATOL-> restart ...

I also want to warn you right away that if you start the web server, then local applications will not be able to access the CCP, I toil for a long time, install the driver, run the Cct driver test, and he tells me that the port is busy and that’s it, he called technical support of the local seller, there they said we don’t know what to do, then I overloaded the computer ten times, reinstalled the driver, nothing helps.

In general, after you have activated and restarted the server, and before that you turned off the server and checked the printing of plain text through the supplied utility or just checked the connection - you can proceed.

This web service does not have any password protection, so you need to immediately configure the Windows firewall or other software so that only the necessary computers can access port 16732, in my situation this is the server on which CRM is running.

Communication with the web service is generally a separate topic, very interesting ...

  1. Generate a unique uuid for the job
  2. We send the task using the POST method
  3. We are crashing at the web service, waiting for the result of the task with our UUID, it may be that for a few seconds our task will have a wait status, or an error may occur if something is wrong in the request ...

And then I’ll give a working version, it is suitable for situations where payment is only one method, and not part of the cash and part non-cash, it also uses the default taxation system, VAT is not calculated yet, I wanted to add the code, then upload it, but I think there are still people who need this information before July 1 than after. I must say right away that the class needs refinement, a lot of raw, no error handling, everything was done in a couple of hours without taking into account reading the documentation, this code is more as an example and I advise you to study the documentation in great detail and adapt it to your specific processes.

php code for an example of working with api (used for educational purposes only)
addr=$addr; 
		if ($port!==false) $this->port=$port; 
	}
	public function CallAPI($method, $data,$_url="/requests") 
	{
		$url = "http://".$this->addr.":".$this->port.$_url;
		$curl = curl_init($url);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($curl,CURLOPT_TIMEOUT, $this->timeout);
		$headers = ['Content-Type: application/json'];
		curl_setopt($curl,CURLOPT_HTTPHEADER, $headers);
		curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
		curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
		$resp = curl_exec($curl);
		$data = json_decode($resp,1);
		$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
		curl_close($curl);
		$res= [$data,$code,$resp];
		print_r($res);
		return $res;
	}
	//получает результат задания
	public function get_res($uuid)
	{
		$ready = false;
		$cnt=0;
		$res_url = '/requests/'.$uuid;
		while (!$ready && ++$cnt<60) 
		{
			usleep(500000); //подождем чуть, прежде чем просить ответ
			list($res,$code,$resp) = $this->CallAPI('GET',[],$res_url);
			$ready = ($res['results'][0]['status'] == 'ready');
			if ($ready) return $res;
		}
		return false; //не удалось получить результата
	}
	//создает задание 
	public function add_req($uuid,$req)
	{
		return $this->CallAPI('POST', ['uuid'=>$uuid,'request'=>$req]);
	}
	//генерирует уникальный id для задания
	public function gen_uuid()
	{
		return exec('uuidgen -r');
	}
	//выполняет задание и возвращает его результат
	public function atol_task($type,$req=[])
	{
		$req['type'] = $type;
		$uuid =  $this->gen_uuid();
		$req = $this->add_req($uuid,$req);
		if ($req[1]!='201') return false; //ошибка добавления
		$res = $this->get_res($uuid);
		//ошибка результата
		if ($res===false || !isset($res['results'][0])) return false; 
		return $res['results'][0];
	}
	/*дальше уже идут конкретные задачи*/
	//статус смены
	public function get_shift_status()
	{
		$res = $this->atol_task('getShiftStatus');
		if ($res===false) return false;
		//closed / opened / expired 
		return $res['result']['shiftStatus']['state'];
	}
	//открытие смены
	public function open_shift()
	{
		$status = $this->get_shift_status();
		//eсли истекла, то надо закрыть
		if ($status=="expired") $this->close_shift(); 
		if ($status=="opened") return "Не могу открыть открытую смену";
		$res = $this->atol_task('openShift',['operator'=>$this->operator]);
	}
	//закрытие смены
	public function close_shift()
	{
		$status = $this->get_shift_status();
		if ($status=="closed") return "Не могу закрыть закрытую смену";
		$res = $this->atol_task('closeShift',['operator'=>$this->operator]);
	}
	public function items_prepare($items)
	{
		$res_items = [];
		$summ = 0;
		while ($item = array_shift($items))
		{
			$res_item = $item;
			if (!isset($item['type'])) 
				$res_item['type']="position";
			if (isset($item['price']) && isset($item['quantity']))
			{
				$res_item['amount'] = $item['price']*$item['quantity'];
				$res_item['tax'] = ['type'=>'none'];
				$summ+=$res_item['amount'];
			}
			$res_items[] = $res_item;
		}
		return [$res_items,$summ];
	}
	//продажа sell, возврат sellReturn
	public function fiskal($type_op="sell",$items,$pay_type="cash")
	{
		$data = [];
		$data['operator'] = $this->operator;
		$data['payments'] = [];
		list($data['items'],$summ) = $this->items_prepare($items);
		//+++тут может быть несколько типов оплаты одновременно
		$data['payments'][] = ['type'=>$pay_type,'sum'=>$summ];
		$res = $this->atol_task($type_op,$data);
	}
}
//тут передается ip где крутится web-сервис драйвера Атол, можно еще и порт передать
$atol = new AtolWebDriver('192.168.100.10');
//тут надо фио кассира
$atol->operator = ['name'=>'сист.администратор'];
//составляем массив товаров с количеством и ценой
$items = [];
$items[] = ['name'=>'Пакет полиэтиленовый','price'=>0.7,'quantity'=>1];
$items[] = ['name'=>'Пакет бумажный','price'=>0.4,'quantity'=>1];
//открываем смену
$atol->open_shift();
//продаем товары
$atol->fiskal("sell",$items); 
sleep(10); //немного подождем, чтобы успеть оторвать чек
//а теперь сделаем возврат всего этого, т.к. это просто проверка 
$atol->fiskal("sellReturn",$items); 
//еще подождем перед огромным чеком
sleep(20);
//закрываем смену, печатаем отчет
$atol->close_shift();


There are some flaws here that I’ll fix

  1. Rounding fractions when calculating the amounts, you need to round to cents, otherwise you can get 1.000000001 or 0.999999999
  2. With the correct spelling of the rest of the program logic, this usually does not occur, but during the tests I caught myself on the fact that the task returned an error result, and I was waiting for ready

Well, in the process of implementation, I’m afraid to catch a lot of mistakes, for example, if the task hangs for a long time in the wait status, then it’s better to remove it from the queue, otherwise the subsequent tasks will hang for several minutes, I caught such a glitch once, I did not hope that it would print and here I sit, and it hop and printed immediately two checks in a row sent earlier ...

In general, it is possible to collect acquiring from the site in the future if there are no online checks in them, until you have decided which acquiring to screw. But the solution is, more likely as an idea for a solution, time will tell how this box office will take root.

Warning , for those who inattentively read the article and are not very competent in the issue of security - this web service does not have encryption (https), does not have authorization, even if it is used only on the local network, configure the protection to access the port.

Also popular now: