config = $config; } /** * 销售发货单创建 * @param array $order * @return array|\DOMDocument */ public function deliveryOrderCreate($order) { $method = 'taobao.qimen.deliveryorder.create'; $postData = [ 'deliveryOrder' => [ 'deliveryOrderCode' => $order['order_no'], //出库单号 'orderType' => 'JYCK', //出库单类型, string (50) , 必填, JYCK=一般交易出库单, HHCK=换货出库单,BFCK=补发出库单,QTCK=其他出库单 'warehouseCode' => $this->config['warehouse_code'], //仓库编码 'sourcePlatformCode' => 'OTHER', //订单来源平台编码 'createTime' => date('Y-m-d H:i:s'), //发货单创建时间 'placeOrderTime' => $order['created_at'], //前台订单 (店铺订单) 创建时间 (下单时间) 'operateTime' => date('Y-m-d H:i:s'), //操作 (审核) 时间 'shopNick' => (isset($order['order_sender_info']['shop_name']) && $order['order_sender_info']['shop_name']) ? $order['order_sender_info']['shop_name'] : static::$default_shop_name, 'sellerNick' => (isset($order['order_sender_info']['shop_name']) && $order['order_sender_info']['shop_name']) ? $order['order_sender_info']['shop_name'] : static::$default_seller_nick, 'remark' => $order['remark'], 'logisticsCode' => $order['order_sender_info']['logistics_code'], //发货人信息 'senderInfo' => [ 'name' => $order['order_sender_info']['name'] ?? static::$default_sender_name, 'mobile' => $order['order_sender_info']['tel'] ?? static::$default_sender_mobile, 'province' => static::$default_sender_province, 'city' => static::$default_sender_city, 'area' => static::$default_sender_area, 'detailAddress' => static::$default_sender_address, ], //收件人信息 'receiverInfo' => [ 'name' => $this->filterEmoji($order['order_address']['receive_name']), 'zipCode' => $order['order_address']['receive_zip_code'] ?? static::$default_zip_code, 'mobile' => trim($order['order_address']['receive_mobile']), 'province' => $order['order_address']['receive_province'], 'city' => $order['order_address']['receive_city'], 'area' => $order['order_address']['receive_district'], 'detailAddress' => $order['order_address']['receive_address'], ], ], 'orderLines' => [], ]; $orderLine = []; $ownerCode = $this->config['shipper_code']; //货主编码 foreach ($order['order_goods'] as $item) { $itemName = $item['goods_name'] . '(' . $item['sku_name'] . ')'; //商品名称 $actualPrice = $item['sku_pay_fee']; //实际成交价 if ($item['sku_type'] == 0) { array_push($orderLine, [ 'ownerCode' => $ownerCode, 'itemCode' => $item['wms_code'], //商品编码 'itemId' => $item['wms_code'], //仓储系统商品编码 'itemName' => $itemName, 'planQty' => $item['sku_quantity'], //应发商品数量 'actualPrice' => $actualPrice, ]); } elseif ($item['sku_type'] == 1) { foreach ($item['sku_nexus_snapshot'] as $gv) { array_push($orderLine, [ 'ownerCode' => $ownerCode, 'itemCode' => $gv['wms_code'], 'itemId' => $gv['wms_code'], 'itemName' => $itemName, 'planQty' => $gv['sku_quantity'] * $item['sku_quantity'], 'actualPrice' => $actualPrice, ]); } } } $postData = array_merge($postData, ['orderLines' => ['orderLine' => $orderLine]]); $result = $this->xmlPost($method, $postData); return $result; } /** * 商品库存查询(单个) * @param $wmsCode * @param string $inventoryType * @return array|\DOMDocument */ public function inventoryQuerySingle($wmsCode, $inventoryType) { return $this->inventoryQuery([['wms_code' => $wmsCode]], $inventoryType); } /** * 商品库存查询(批量) * @param array $params * @param string $inventoryType * @return array|\DOMDocument */ public function inventoryQuery($params, $inventoryType) { $method = 'taobao.qimen.inventory.query'; $criteria = []; foreach ($params as $item) { array_push($criteria, [ 'warehouseCode' => $this->config['warehouse_code'], 'ownerCode' => $this->config['shipper_code'], 'itemCode' => $item['wms_code'], 'itemId' => $item['wms_code'], 'inventoryType' => $inventoryType, ]); } $postData = [ 'criteriaList' => [ 'criteria' => $criteria, ], ]; $result = $this->xmlPost($method, $postData); return $result['success'] ? array_merge($result, ['response' => $result['response']['items']['item']]) : $result; } /** * 商品同步(商品创建或修改) * @param array $params * @return array|\DOMDocument */ public function goodsSkuSync($params) { $method = 'taobao.qimen.singleitem.synchronize'; $postData = [ 'actionType' => $params['action_type'], 'warehouseCode' => $this->config['warehouse_code'], 'ownerCode' => $this->config['shipper_code'], 'item' => [ [ 'itemCode' => $params['wms_code'], 'itemId' => $params['wms_code'], 'itemName' => $params['name'], 'barCode' => $params['upc_code'] ?? $params['wms_code'], //商品类型 (ZC=正常商品, FX=分销商品, ZH=组合商品, ZP=赠品, BC=包材, HC=耗材, FL=辅料, XN=虚拟品, //FS=附属品, CC=残次品, OTHER=其它) , string (10) , 必填, (只传英文编码) 'itemType' => 'ZC', 'retailPrice' => $params['unit_price'], 'isValid' => 'Y', ] ], ]; $result = $this->xmlPost($method, $postData); return $result; } /** * 单据取消 * @param array $params * @return array|\DOMDocument */ public function orderCancel($params) { $method = 'taobao.qimen.order.cancel'; $postData = [ 'warehouseCode' => $this->config['warehouse_code'], 'orderCode' => $params['order_code'], 'orderId' => $params['order_code'], 'orderType' => $params['order_type'], 'cancelReason' => $params['cancel_reason'], ]; $result = $this->xmlPost($method, $postData); return $result; } /** * 退货入库单创建(新增销售退货计划) * @param array $params * @return array|\DOMDocument */ public function returnOrderCreate($params) { $method = 'taobao.qimen.returnorder.create'; $ownerCode = $this->config['shipper_code']; $postData = [ 'returnOrder' => [ 'returnOrderCode' => $params['refund_no'], 'ownerCode' => $ownerCode, 'warehouseCode' => $this->config['warehouse_code'], 'orderType' => 'THRK', 'preDeliveryOrderCode' => $params['order_info']['order_no'], 'preDeliveryOrderId' => $params['order_info']['order_no'], 'logisticsCode' => strtoupper($params['express_code']), 'logisticsName' => $params['express_company'], 'expressCode' => $params['express_no'], 'returnReason' => $params['desc'], 'senderInfo' => [ 'name' => $params['order_address_info']['receive_name'], 'mobile' => $params['order_address_info']['receive_mobile'], 'province' => $params['order_address_info']['receive_province'], 'city' => $params['order_address_info']['receive_city'], 'area' => $params['order_address_info']['receive_district'], 'detailAddress' => $params['order_address_info']['receive_address'], ], ], 'orderLines' => [], ]; $orderLine = []; foreach ($params['refund_goods'] as $item) { if ($item['sku_type'] == 0) { array_push($orderLine, [ 'ownerCode' => $ownerCode, 'itemCode' => $item['wms_code'], 'itemId' => $item['wms_code'], 'planQty' => $item['sku_quantity'], 'inventoryType' => 'ZP', ]); } elseif ($item['sku_type'] == 1) { foreach ($item['combined_sku'] as $v) { array_push($orderLine, [ 'ownerCode' => $ownerCode, 'itemCode' => $v['wms_code'], 'itemId' => $v['wms_code'], 'planQty' => $v['sku_quantity'], 'inventoryType' => 'ZP', ]); } } } $postData = array_merge($postData, ['orderLines' => ['orderLine' => $orderLine]]); $result = $this->xmlPost($method, $postData); return $result; } /** * 新增入库计划 * @param array $params * @return array|\DOMDocument */ public function stockInCreate($params) { $method = 'taobao.qimen.entryorder.create'; $ownerCode = $this->config['shipper_code']; $postData = [ 'entryOrder' => [ 'entryOrderCode' => $params['number'], 'ownerCode' => $ownerCode, 'purchaseOrderCode' => $this->goodsWarehouseCgrkNumber(), 'warehouseCode' => $this->config['warehouse_code'], 'orderType' => self::RK_TYPE[$params['type']] ?? 'DBRK', ], 'orderLines' => [], ]; $orderLine = []; foreach ($params['sku_info'] as $skuV) { $stockZP = [ 'ownerCode' => $ownerCode, 'itemCode' => $skuV['wms_code'], 'itemId' => $skuV['wms_code'], 'planQty' => $skuV['apply_stock'] * 1, 'purchasePrice' => $this->formatNumber($skuV['price']), 'inventoryType' => 'ZP', ]; array_push($orderLine, $stockZP); if (!empty($skuV["apply_bad_stock"])) { $stockCC = array_merge($stockZP, [ 'planQty' => $skuV['apply_bad_stock'] * 1, 'inventoryType' => 'CC', ]); array_push($orderLine, $stockCC); } } $postData = array_merge($postData, ['orderLines' => ['orderLine' => $orderLine]]); $result = $this->xmlPost($method, $postData); return $result; } public function stockOutCreate($params) { $method = 'taobao.qimen.stockout.create'; $postData = [ 'deliveryOrder' => [ 'deliveryOrderCode' => $params['out_warehouse_number'], //出库单号 'orderType' => 'DBCK', //出库单类型 'warehouseCode' => $this->config['warehouse_code'], //仓库编码 'logisticsCode' => $params['order_sender_info']['logistics_code'] ?? 'SF', 'logisticsName' => $params['order_sender_info']['logistics_name'] ?? '顺丰', 'createTime' => date('Y-m-d H:i:s'), //出库单创建时间 'receiverInfo' => [ 'name' => $this->filterEmoji($params['address_name']), 'zipCode' => $params['receive_zip_code'] ?? self::$default_zip_code, 'mobile' => $params['address_telephone'], 'province' => $params['address_province'], 'city' => $params['address_city'], 'area' => $params['address_area'], 'detailAddress' => $params['address_detail'], ], ], 'orderLines' => [], ]; $ownerCode = $this->config['shipper_code']; $orderLine = []; foreach ($params['order_goods'] as $item) { $itemName = $item['product_name'] . "(" . $item['sku_name'] . ")"; //商品名称 $actualPrice = $item['sku_pay_fee'] ?? 0; if (!isset($item['sku_type']) || $item['sku_type'] == 0) { $goodsZP = [ 'ownerCode' => $ownerCode, //货主编码 'itemCode' => $item['wms_code'], //商品编码 'itemId' => $item['wms_code'], //仓储系统商品编码 'itemName' => $itemName, 'planQty' => $item['number'], //应发商品数量 'actualPrice' => $actualPrice, 'inventoryType' => 'ZP', //库存类型,ZP=正品, CC=残次,JS=机损, XS= 箱损 ]; if ($item['number'] > 0) { array_push($orderLine, $goodsZP); } if (isset($item['bad_num']) && $item['bad_num'] > 0) { $goodsCC = array_merge($goodsZP, [ 'planQty' => $item['number'], 'inventoryType' => 'CC', ]); array_push($orderLine, $goodsCC); } } elseif ($item['sku_type'] == 1) { foreach ($item['sku_nexus_snapshot'] as $gv) { array_push($orderLine, [ 'ownerCode' => $ownerCode, 'itemCode' => $gv['wms_code'], 'itemId' => $gv['wms_code'], 'itemName' => $itemName, 'planQty' => $gv['sku_quantity'] * $item['sku_quantity'], 'actualPrice' => $actualPrice, 'inventoryType' => 'ZP', ]); } } } $postData = array_merge($postData, ['orderLines' => ['orderLine' => $orderLine]]); $result = $this->xmlPost($method, $postData); return $result; } public function createSign($params, $body) { ksort($params); $info = ''; foreach ($params as $key => $val) { $info .= $key . $val; } $appSecret = $this->config['app_secret']; $info = $appSecret . '' . $info . $body . '' . $appSecret; $sign = strtoupper(md5($info)); return $sign; } public function xmlPost($method, $params) { $query = [ 'method' => $method, 'timestamp' => date('Y-m-d H:i:s'), 'format' => $this->config['format'], 'app_key' => $this->config['app_key'], 'v' => $this->config['version'], 'sign_method' => $this->config['sign_method'], 'customerId' => $this->config['customerId'], ]; $xml = Array2XML::createXML('request', $params); $body = $xml->saveXML(); app('log')->info('php-wms-client-body', ['request_body' => $body]); $sign = $this->createSign($query, $body); $query['sign'] = $sign; $response = $this->post($body, $query); $content = $response->getBody()->getContents(); return $this->getResponse($content); } final protected function post($body, $params = [], $headers = ['content-type' => 'text/xml; charset = utf-8'], $ret = []) { $url = $this->config['base_uri']; $url = $url . http_build_query($params); $data = [ 'body' => $body, 'headers' => $headers, ]; $client = new Client(); $result = $client->request('POST', $url, $data); return $result; } function getResponse($content) { $result = XML2Array::createArray($content); $isSuccess = (empty($result) || !is_array($result) || $result['response']['flag'] != 'success') ? false : true; $result = [ 'success' => $isSuccess, 'response' => $isSuccess ? $result['response'] : $result, ]; return $result; } /** * 确认回传方法 * @var array */ private static $callbackMethods = [ 'deliveryorder.confirm' => 'deliveryOrderConfirm', 'entryorder.confirm' => 'stockInConfirm', 'itemlack.report' => 'itemLackReport', 'orderprocess.report' => 'orderProcessReport', 'returnorder.confirm' => 'returnOrderConfirm', 'stockchange.report' => 'stockChangeReport', 'stockout.confirm' => 'stockOutConfirm', ]; public function callback($params, $onlyCheckSign = false) { if (!isset($params['method']) || empty($params['method'])) { return $this->response('缺少参数method', false); } if (!isset($params['timestamp']) || empty($params['timestamp'])) { return $this->response('缺少参数timestamp', false); } if (!isset($params['format']) || empty($params['format'])) { return $this->response('缺少参数format', false); } if (!isset($params['app_key']) || empty($params['app_key'])) { return $this->response('缺少参数key', false); } if (!isset($params['v']) || empty($params['v'])) { return $this->response('缺少版本参数', false); } if (!isset($params['sign']) || empty($params['sign'])) { return $this->response('缺少参数sign', false); } if (!isset($params['sign_method']) || empty($params['sign_method'])) { return $this->response('缺少加密方式', false); } if (empty(($params['body']))) { return $this->response('缺少消息body', false); } $method = $params['method']; if (!array_key_exists($method, self::$callbackMethods)) { return $this->response('调用的方法不存在', false); } if (!$this->checkSign($params)) { return $this->response('签名错误', false); } // 用于阿里云自测验签 if ($onlyCheckSign) { return $this->response(); } $body = XML2Array::createArray($params['body']); if (!isset($body['request']) || empty($body['request'])) { return $this->response('消息body错误', false); } $result = call_user_func([$this, self::$callbackMethods[$method]], $body['request']); return $result; } /** * 奇门自测,仅验签 */ public function onlyCheckSign($params) { return $this->callback($params, true); } /** * 发货单确认 * @param $params * @return array */ private function deliveryOrderConfirm($params) { $service = new OrderService(); $result = $service->deliverOrderConfirm($params); if (!$result) { return $this->response('失败', false); } return $this->response(); } /** * 入库单确认 * @param $params * @return array */ private function stockInConfirm($params) { $service = new GoodsWarehouseService(); $result = $service->entryorderConfirm($params); if (!$result) { return $this->response('失败', false); } return $this->response(); } /** * 发货单缺货通知 * @param $params * @return array */ private function itemLackReport($params) { $service = new OrderItemLackReportService(); $result = $service->createReceipts($params); if (!$result) { return $this->response('失败', false); } return $this->response(); } /** * 订单流水通知 * @param $params * @return array */ private function orderProcessReport($params) { $service = new OrderProcessReportService(); $result = $service->orderProcessReport($params); if (!$result) { return $this->response('失败', false); } return $this->response(); } /** * 退货入库单确认 * @param $params * @return array */ private function returnOrderConfirm($params) { $service = new RefundService(); $result = $service->refundOrderConfirm($params); if (!$result) { return $this->response('失败', false); } return $this->response(); } /** * 库存异动通知 * @param $params * @return array */ private function stockChangeReport($params) { $service = new GoodsWarehouseService(); $result = $service->StockChangeReport($params); if (!$result) { return $this->response('失败', false); } return $this->response(); } /** * 出库单确认 * @param $params * @return array */ private function stockOutConfirm($params) { $service = new OrderService(); $result = $service->stockOutOrderConfirm($params); if (!$result) { return $this->response('失败', false); } return $this->response(); } /** * @检查签名 * @param $params * @return bool */ public function checkSign($params) { if (empty($params)) { return false; } $query = $params; unset($query['body'], $query['sign']); $sign = $this->createSign($query, $params['body']); if (!hash_equals((string) $sign, (string) $params['sign'])) { return false; } return true; } public function response($message = '成功', $flag = true) { $response = [ 'flag' => $flag ? 'success' : 'failure', 'code' => $flag ? '001' : '002', 'message' => $message, ]; $xml = Array2XML::createXML('response', $response); $body = $xml->saveXML(); return [ 'content' => $body, 'status' => 200, 'headers' => ['Content-Type' => 'utf-8'], ]; } }