完整代码请点击:
https://download.csdn.net/download/adophper/83339172
直接上代码:
<?php namespace appapicontroller; use appcommoncontrollerApi; use appcommonserviceClientService; use appcommonserviceWechatService; use thinkCache; use thinkDb; /** * 保税海关179接口 * Class Customs * @package appapicontroller */ class Custom extends Api { // 无需登录的接口,*表示全部 protected $noNeedLogin = ['*']; // 无需鉴权的接口,*表示全部 protected $noNeedRight = ['*']; //redis缓存key private $key = 'openReq'; /** * 查询订单信息 * @param $orderno * @return array * @throws thinkException * @throws thinkdbexceptionDataNotFoundException * @throws thinkdbexceptionModelNotFoundException * @throws thinkexceptionDbException */ private function searchOrderInfo($orderno) { if (empty($orderno)) return []; $order = Db::name('order_info')->where(['order_sn' => $orderno])->find(); if (empty($order)) return []; $goods = Db::name('order_goods')->where('order_id='.$order['order_id'])->select(); foreach($goods as $v){ $goods_list[] = array( 'gname' => Sbc2Dbc($v['goods_name']), 'itemLink' => '没有产品连接', ); } //查询支付信息 $pay_info = Db::name('pay_log')->where('order_id', $order['order_id'])->find(); //微信订单附加信息提交接口 $customdeclareorder = $pay_info['customdeclareorder']; if (empty($customdeclareorder)) { $wechatService = new WechatService(); $customdeclareorder = $wechatService->customDeclareOrder($order['order_sn'], $pay_info['transid']); } $verify_department = $customdeclareorder['verify_department']; $verDept = 3;//其他-OTHERS(如余额支付,零钱通支付等) if ($verify_department == 'UNIONPAY') { $verDept = 1;//银联- } else if ($verify_department == 'NETSUNION') { $verDept = 2;//网联 } else { //TODO } ////////实现以上方法////////// $resp = array( 'initalRequest' => $pay_info['request'], //发送给支付企业的原始请求,微信xml,支付宝url 'initalResponse' => $pay_info['response'], //支付成功后回调的原始请求 //交易流水号(支付报关时有返回,微信:verify_department_trade_id,支付宝:pay_transaction_id) 'payTransactionId' => $pay_info['transid'], //参数:verDept,填:1,2,3;支付报关返回,支付宝参数直接用:ver_dept, //微信参数名:verify_department,需要转换:'UNIONPAY'=>1,'NETSUNION'=> 2,'OTHERS'=>3 'verDept' => $verDept, 'totalAmount' => $order['money_paid'], 'tradingTime' => date('YmdHis', $order['pay_time']), //时间格式,date('YmdHis'),例子:20181212041803 'goods' => $goods_list, 'ebpCode' => 'xxxxxxxxxx', //电商平台十位海关编码 'payCode' => '4403169D3W', //支付企业十位海关编码,微信:4403169D3W,支付宝:31222699S7,其他的按实际填 'currency' => 142, //币种,只能填142 'recpAccount' => 'xxxxxxxxxxxxxxxxx', //公司对公银行卡号(最终收款的卡号) 'recpName' => 'xxxxxxxxxxxxxxxxxxx', //电商平台海关备案名称 'note' => '没有产品连接', ); return $resp; } /** * 保税查询订单详情 * @throws thinkdbexceptionDataNotFoundException * @throws thinkdbexceptionModelNotFoundException * @throws thinkexceptionDbException */ public function queryOrder(){ $orderno = $this->request->request("orderNo", ''); $resp = $this->searchOrderInfo($orderno); if (empty($resp)) exit('[]'); echo json_encode($resp); exit; } /** * 海关请求plateDataOpen,接受openReq并保存 */ public function plateDataOpen(){ $openReq = $this->request->post("openReq", ''); $data = @json_decode($openReq,true); if ($data) { $redis = Cache::store('redis'); $redis->handler()->sadd($this->key, $openReq); $insert = []; $insert['orderno'] = $data['orderNo']; $insert['sessionid'] = $data['sessionID']; $insert['status'] = 0; $insert['add_time'] = date('Y-m-d H:i:s'); $insert['update_time'] = date('Y-m-d H:i:s'); Db::table('custom_open_req')->insert($insert); } exit('{"code":"10000","message":"","serviceTime":'.time().'}') ; } /** * 查询未验签上报的数据 * @throws thinkException * @throws thinkdbexceptionDataNotFoundException * @throws thinkdbexceptionModelNotFoundException * @throws thinkexceptionDbException */ public function getOpenReq() { $redis = Cache::store('redis'); if (!$redis->has($this->key)) { $data = Db::table("custom_open_req")->where('status', 0)->field('orderno,sessionid')->select(); if (!empty($data)) { foreach ($data as &$val) { $val['orderNo'] = $val['orderno']; $val['sessionID'] = $val['sessionid']; } } } else { $data = $redis->handler()->smembers($this->key); if (!empty($data)) { foreach ($data as &$val) { $val = @json_decode($val, true); } } } $result = null; //组装数据 if ($data) { foreach ($data as $item) { $guid = guid(); $time = time(); $order = $this->searchOrderInfo($item['orderNo']); $info = []; $info['orderNo'] = $item['orderNo']; $info['goodsInfo'] = $order['goods']; $info['recpAccount'] = $order['recpAccount']; $info['recpCode'] = $order['recpCode']; $info['recpName'] = $order['recpName']; $array = []; $array['sessionID'] = $item['sessionID']; $order['guid'] = $guid; $array['payExchangeInfoHead'] = json_encode($order, JSON_UNESCAPED_UNICODE); $array['payExchangeInfoLists'] = json_encode($info,JSON_UNESCAPED_UNICODE); $array['serviceTime'] = $time; $url = []; foreach ($array as $sk => $sv) { $url[] = '"'.$sk.'":"'.$sv.'"'; } $result[$item['orderNo']]['url'] = implode('||', $url); $result[$item['orderNo']]['json'] = json_encode($array, JSON_UNESCAPED_UNICODE); } exit(json_encode($result)); } exit('[]'); } /** * 更新数据状态 * @param string $data * @throws thinkException * @throws thinkexceptionPDOException */ public function callback($data = '') { if (empty($data)) { $data = $this->request->request("data"); } if ($data) { $redis = Cache::store('redis'); $openReq = $redis->handler()->smembers($this->key); $data = @json_decode(htmlspecialchars_decode($data), true); //因为是定时任务,所以可能有多个单,这时就会出现多个反馈 $customTable = Db::table('custom_open_req'); foreach ($data as $key => $val) { foreach ($openReq as $sv) { $info = @json_decode($sv, true); if ($info['orderNo'] == $key) { $redis->handler()->srem($this->key, $sv); break; } //更新数据状态 $customTable->where('orderno', $key)->update(['msg' => $val['msg'], 'status' => $val['status'], 'update_time' => date('Y-m-d H:i:s')]); } } } exit(); } /** * 定时任务,上报数据 * @throws thinkException * @throws thinkexceptionPDOException */ public function crontab() { $uri = 'ws://127.0.0.1:61232'; $client = new ClientService($uri); $data = $this->lunxun(); if ($data) { $client->send('hello'); $result = $client->receive(); $result = @json_decode($result, true); $send_data = [ '_id' => $result['_id'], '_method' => 'cus-sec_SpcSignDataAsPEM', ]; $data = @json_decode($data,true); $result = null; foreach ($data as $key =>$val){ $send_data['args'] = array( 'inData' => $val['url'],//得到要加签的字符串 'passwd' => '88888888',//默认密码 ); $client->send(json_encode($send_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); $data = @json_decode($client->receive(), true); $args = $data['_args']; // 加签的值. $sign_data['signValue'] = $args['Data'][0]; $sign_data['certNo'] = $args['Data'][1]; $reqData = @json_decode($val['json'], true);//得到上传给海关的值 $reqData['certNo'] = $sign_data['certNo']; $reqData['signValue'] = $sign_data['signValue']; $reqData = @json_encode($reqData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $res = $client->req('https://swapptest.singlewindow.cn/ceb2grab/grab/realTimeDataUpload','payExInfoStr='.urlencode($reqData)); $res = @json_decode($res,true); $result[$key] = array('msg'=>$res['message'],'status'=>$res['code'] == 10000?1:2);//开始调用海关接口 } if($result){ $result = json_encode($result); //$client->req('把验证接口推给服务器','data='.$result); $this->callback($result); } } } }
ClientService.php
0, 'text' => 1, 'binary' => 2, 'close' => 8, 'ping' => 9, 'pong' => 10, ); /** * @param string $uri A ws/wss-URI * @param array $options * Associative array containing: * - context: Set the stream context. Default: empty context * - timeout: Set the socket timeout in seconds. Default: 5 * - headers: Associative array of headers to set/override. */ public function __construct($uri, $options = array()) { $this->options = $options; if (!array_key_exists('timeout', $this->options)) { $this->options['timeout'] = 5; } // the fragment size if (!array_key_exists('fragment_size', $this->options)) { $this->options['fragment_size'] = 4096; } $this->socket_uri = $uri; } public function getLastOpcode() { return $this->last_opcode; } public function getCloseStatus() { return $this->close_status; } public function isConnected() { return $this->is_connected; } public function setTimeout($timeout) { $this->options['timeout'] = $timeout; if ($this->socket && get_resource_type($this->socket) === 'stream') { stream_set_timeout($this->socket, $timeout); } } public function setFragmentSize($fragment_size) { $this->options['fragment_size'] = $fragment_size; return $this; } public function getFragmentSize() { return $this->options['fragment_size']; } public function send($payload, $opcode = 'text', $masked = true) { if (!$this->is_connected) { $this->connect(); } /// @todo This is a client function, fixme! if (!in_array($opcode, array_keys(self::$opcodes))) { throw new Exception("Bad opcode '$opcode'. Try 'text' or 'binary'."); } // record the length of the payload $payload_length = strlen($payload); $fragment_cursor = 0; // while we have data to send while ($payload_length > $fragment_cursor) { // get a fragment of the payload $sub_payload = substr($payload, $fragment_cursor, $this->options['fragment_size']); // advance the cursor $fragment_cursor += $this->options['fragment_size']; // is this the final fragment to send? $final = $payload_length <= $fragment_cursor; // send the fragment $this->send_fragment($final, $sub_payload, $opcode, $masked); // all fragments after the first will be marked a continuation $opcode = 'continuation'; } } protected function send_fragment($final, $payload, $opcode, $masked) { // Binary string for header. $frame_head_binstr = ''; // Write FIN, final fragment bit. $frame_head_binstr .= (bool) $final ? '1' : '0'; // RSV 1, 2, & 3 false and unused. $frame_head_binstr .= '000'; // Opcode rest of the byte. $frame_head_binstr .= sprintf('%04b', self::$opcodes[$opcode]); // Use masking? $frame_head_binstr .= $masked ? '1' : '0'; // 7 bits of payload length... $payload_length = strlen($payload); if ($payload_length > 65535) { $frame_head_binstr .= decbin(127); $frame_head_binstr .= sprintf('%064b', $payload_length); } elseif ($payload_length > 125) { $frame_head_binstr .= decbin(126); $frame_head_binstr .= sprintf('%016b', $payload_length); } else { $frame_head_binstr .= sprintf('%07b', $payload_length); } $frame = ''; // Write frame head to frame. foreach (str_split($frame_head_binstr, 8) as $binstr) { $frame .= chr(bindec($binstr)); } // Handle masking if ($masked) { // generate a random mask: $mask = ''; for ($i = 0; $i < 4; ++$i) { $mask .= chr(rand(0, 255)); } $frame .= $mask; } // Append payload to frame: for ($i = 0; $i < $payload_length; ++$i) { $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i]; } $this->write($frame); } public function receive() { if (!$this->is_connected) { $this->connect(); } /// @todo This is a client function, fixme! $this->huge_payload = ''; $response = null; while (is_null($response)) { $response = $this->receive_fragment(); } return $response; } protected function receive_fragment() { // Just read the main fragment information first. $data = $this->read(2); // Is this the final fragment? // Bit 0 in byte 0 /// @todo Handle huge payloads with multiple fragments. $final = (bool) (ord($data[0]) & 1 << 7); // Should be unused, and must be false… // Bits 1, 2, & 3 $rsv1 = (bool) (ord($data[0]) & 1 << 6); $rsv2 = (bool) (ord($data[0]) & 1 << 5); $rsv3 = (bool) (ord($data[0]) & 1 << 4); // Parse opcode $opcode_int = ord($data[0]) & 31; // Bits 4-7 $opcode_ints = array_flip(self::$opcodes); if (!array_key_exists($opcode_int, $opcode_ints)) { throw new Exception("Bad opcode in websocket frame: $opcode_int"); } $opcode = $opcode_ints[$opcode_int]; // record the opcode if we are not receiving a continutation fragment if ($opcode !== 'continuation') { $this->last_opcode = $opcode; } // Masking? $mask = (bool) (ord($data[1]) >> 7); // Bit 0 in byte 1 $payload = ''; // Payload length $payload_length = (int) ord($data[1]) & 127; // Bits 1-7 in byte 1 if ($payload_length > 125) { if ($payload_length === 126) { $data = $this->read(2); } // 126: Payload is a 16-bit unsigned int else { $data = $this->read(8); } // 127: Payload is a 64-bit unsigned int $payload_length = bindec(self::sprintB($data)); } // Get masking key. if ($mask) { $masking_key = $this->read(4); } // Get the actual payload, if any (might not be for e.g. close frames. if ($payload_length > 0) { $data = $this->read($payload_length); if ($mask) { // Unmask payload. for ($i = 0; $i < $payload_length; ++$i) { $payload .= ($data[$i] ^ $masking_key[$i % 4]); } } else { $payload = $data; } } if ($opcode === 'close') { // Get the close status. if ($payload_length >= 2) { $status_bin = $payload[0].$payload[1]; $status = bindec(sprintf('%08b%08b', ord($payload[0]), ord($payload[1]))); $this->close_status = $status; $payload = substr($payload, 2); if (!$this->is_closing) { $this->send($status_bin.'Close acknowledged: '.$status, 'close', true); } // Respond. } if ($this->is_closing) { $this->is_closing = false; } // A close response, all done. // And close the socket. fclose($this->socket); $this->is_connected = false; } // if this is not the last fragment, then we need to save the payload if (!$final) { $this->huge_payload .= $payload; return null; } // this is the last fragment, and we are processing a huge_payload elseif ($this->huge_payload) { // sp we need to retreive the whole payload $payload = $this->huge_payload .= $payload; $this->huge_payload = null; } return $payload; } /** * Tell the socket to close. * * @param int $status http://tools.ietf.org/html/rfc6455#section-7.4 * @param string $message a closing message, max 125 bytes */ public function close($status = 1000, $message = 'ttfn') { $status_binstr = sprintf('%016b', $status); $status_str = ''; foreach (str_split($status_binstr, 8) as $binstr) { $status_str .= chr(bindec($binstr)); } $this->send($status_str.$message, 'close', true); $this->is_closing = true; $response = $this->receive(); // Receiving a close frame will close the socket now. return $response; } protected function write($data) { $written = fwrite($this->socket, $data); if ($written < strlen($data)) { throw new Exception( "Could only write $written out of ".strlen($data).' bytes.' ); } } protected function read($length) { $data = ''; while (strlen($data) < $length) { $buffer = fread($this->socket, $length - strlen($data)); if ($buffer === false) { $metadata = stream_get_meta_data($this->socket); throw new Exception( 'Broken frame, read '.strlen($data).' of stated ' .$length.' bytes. Stream state: ' .json_encode($metadata) ); } $data .= $buffer; } return $data; } protected static function sprintB($string) { $return = ''; for ($i = 0; $i < strlen($string); ++$i) { $return .= sprintf('%08b', ord($string[$i])); } return $return; } public function __destruct() { if ($this->socket) { if (get_resource_type($this->socket) === 'stream') { fclose($this->socket); } $this->socket = null; } } protected function connect() { $url_parts = parse_url($this->socket_uri); $scheme = $url_parts['scheme']; $host = $url_parts['host']; $user = isset($url_parts['user']) ? $url_parts['user'] : ''; $pass = isset($url_parts['pass']) ? $url_parts['pass'] : ''; $port = isset($url_parts['port']) ? $url_parts['port'] : ($scheme === 'wss' ? 443 : 80); $path = isset($url_parts['path']) ? $url_parts['path'] : '/'; $query = isset($url_parts['query']) ? $url_parts['query'] : ''; $fragment = isset($url_parts['fragment']) ? $url_parts['fragment'] : ''; $path_with_query = $path; if (!empty($query)) { $path_with_query .= '?'.$query; } if (!empty($fragment)) { $path_with_query .= '#'.$fragment; } if (!in_array($scheme, array('ws', 'wss'))) { throw new Exception( "Url should have scheme ws or wss, not '$scheme' from URI '$this->socket_uri' ." ); } $host_uri = ($scheme === 'wss' ? 'ssl' : 'tcp').'://'.$host; // Set the stream context options if they're already set in the config if (isset($this->options['context'])) { // Suppress the error since we'll catch it below if (@get_resource_type($this->options['context']) === 'stream-context') { $context = $this->options['context']; } else { throw new InvalidArgumentException( "Stream context in $options['context'] isn't a valid context" ); } } else { $context = stream_context_create(); } // Open the socket. @ is there to supress warning that we will catch in check below instead. $this->socket = @stream_socket_client( $host_uri.':'.$port, $errno, $errstr, $this->options['timeout'], STREAM_CLIENT_CONNECT, $context ); if ($this->socket === false) { throw new Exception( "Could not open socket to "$host:$port": $errstr ($errno)." ); } // Set timeout on the stream as well. stream_set_timeout($this->socket, $this->options['timeout']); // Generate the WebSocket key. $key = self::generateKey(); // Default headers (using lowercase for simpler array_merge below). $headers = array( 'host' => $host.':'.$port, 'user-agent' => 'websocket-client-php', 'connection' => 'Upgrade', 'upgrade' => 'websocket', 'sec-websocket-key' => $key, 'sec-websocket-version' => '13', ); // Handle basic authentication. if ($user || $pass) { $headers['authorization'] = 'Basic '.base64_encode($user.':'.$pass)."rn"; } // Deprecated way of adding origin (use headers instead). if (isset($this->options['origin'])) { $headers['origin'] = $this->options['origin']; } // Add and override with headers from options. if (isset($this->options['headers'])) { $headers = array_merge($headers, array_change_key_case($this->options['headers'])); } $header = 'GET '.$path_with_query." HTTP/1.1rn" &