1. 接口说明 #
提供两类接口:
- OPENAPI
- 基于 WebSocket 提供实时事件通知的事件中心
2. 对接方式 #
2.1. OPENAPI #
2.1.1. URL地址(POST方式,JSON编码) #
https://api.xiaoduoai.com/gate/open
2.1.2. 公共参数 #
调用任何一个API都必须传入的参数,目前支持的公共参数有:
参数名称 |
参数类型 |
是否必须 |
参数描述 |
示例 |
method |
string |
是 |
具体API接口名称 |
xd.gate.demo |
appkey |
string |
是 |
配给企业的AppKey |
key123456 |
ts |
int |
是 |
10位的秒级时间戳 |
1668654293 |
sig |
string |
是 |
参数签名结果,签名算法参照下面的介绍 |
|
body |
map[string]interface{} |
是 |
业务参数 |
示例:
curl -H "content-type: application/json" https://api.xiaoduoai.com/gate/open -d '
{
"method": "'${method}'",
"body": {
"key": "value"
},
"appkey": "key123456",
"ts": '${ts}',
"sig": "'${sig}'"
}'
2.1.3. 签名 #
为了防止API调用过程中被篡改,调用任何一个API都需要携带签名,服务端会根据请求参数,对签名进行验证,签名不合法的请求将会被拒绝。
签名方式:将method
、ts
、app_secret
拼接得到的字符串MD5取16进制小写32位字符串
示例:
参数method
=xd.gate.demo
,ts
=1668654293
,app_secret
=secret456
拼接后字符串 xd.gate.demo1668654293secret456
最终得到的签名为:159235348d54ea0c0d42bc8c849f75ed
2.1.4. demo.sh #
#!/bin/bash
method="xd.gate.demo"
ts=`date +%s`
sig=`echo -n "xd.gate.demo1668654293secret456"|md5sum|cut -d ' ' -f1`
curl -H "content-type: application/json" https://api.xiaoduoai.com/gate/open -d '
{
"method": "xd.gate.demo",
"body": {
"body_key": "body_val"
},
"appkey": "key123",
"ts": 1668664279,
"sig": "b09914f8721c16617994e0ac9c9c45fc"
}'
2.1.5. 接口调用频率限制 #
调用API接口每分钟允许请求的次数,限制说明详情如下
API接口 |
接口名称 |
并发量 & 频率/分钟 |
xd.ticket.create |
创建工单 |
1 & 60次/分钟 |
xd.ticket.update |
更新工单 |
1 & 60次/分钟 |
xd.ticket.list |
查询工单 |
1 & 60次/分钟 |
xd.ticket.batch_pay_list |
查询批量打款工单 |
1 & 60次/分钟 |
2.2. 使用WebSocket接入 #
2.2.1. 连接到Gateway #
ws://api.xiaoduoai.com/event/open
2.2.2. 鉴权连接 #
建立 websocket 连接之后,需要进行鉴权
参数名称 |
参数类型 |
是否必须 |
参数描述 |
示例 |
sub_event_ids |
[]string |
是 |
订阅消息的接口名称 |
xd.gate.demo |
app_key |
string |
是 |
配给企业的AppKey |
key123456 |
ts |
int |
是 |
10位的秒级时间戳 |
1668654293 |
sig |
string |
是 |
参数签名结果,签名算法参照下面的介绍 |
示例:
ws.onopen = function(evt)
{
const ts = parseInt(Date.now() / 1000)
const auth = {
"sub_event_ids" : ["xd.event.demo"],
"appkey": "key123",
"ts": ts
};
auth.sig = md5("xd.event.demo"+ts+"secret456")
const data = JSON.stringify(auth);
ws.send(data)
};
2.2.3. 签名 #
签名方式:将sub_event_ids
(用,
连接)、ts
、app_secret
拼接得到的字符串MD5取16进制小写32位字符串
示例:
参数 sub_event_ids
=["xd.gate.demo","xd.gate.demo2"]
,ts
=1668654293
,app_secret
=secret456
拼接后字符串xd.gate.demo,xd.gate.demo21668654293secret456
最终得到的md5签名为:b0ce12faf07c250aa799a243ad4a9de5
2.2.4. ws_client.html #
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>打开F12开发者工具查看浏览器控制台标准输出</title>
</head>
<body>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.10.0/js/md5.min.js"></script>
<script type="text/javascript">
window.onload = function(){
// 后端针对每个appkey仅支持一个并发接入,
// 后接入的会踢掉已建立连接的
const ws = new WebSocket("ws://api.xiaoduoai.com/event/open");
// 第一个包,连接鉴权
ws.onopen = function(evt)
{
const ts = parseInt(Date.now() / 1000)
const auth = {
"sub_event_ids" : ["xd.event.demo"],
"appkey": "key123",
"ts": ts
};
auth.sig = md5("xd.event.demo"+ts+"secret456")
const data = JSON.stringify(auth);
ws.send(data)
window.setInterval(function () {
sendPing(ws);
}, 20000);
};
// 发送心跳,后端会立即返回event_id = ping的数据
const sendPing = function(ws)
{
const ping = {
"event_id" : "ping"
}
const data = JSON.stringify(ping);
ws.send(data)
}
ws.onmessage = function(evt)
{
console.log("message " + evt.data)
const ret = JSON.parse(evt.data)
console.log("message " + ret["event_id"])
if (ret["event_id"] == "ping") {
} else {
console.log("message " + ret["data"].message)
}
};
ws.onclose = function(evt)
{
// 关闭 websocket
console.log("closed " + evt)
};
ws.onerror = function(evt)
{
// 异常退出,需要处理断线重连
console.log("error " + evt)
};
}
</script>
</html>
注意事项
- 连接
1min
内没有发消息会自动断开 - 同一个
appkey
只能保持一个连接,多个连接只会保留最后一次连接
2.3. 回调地址 #
- 回调地址是用来接收工单变更事件,工单字段变更后,会在回调地址中接收到加密之后的变更消息。
POST
方式请求,将相应的消息存放在请求体中,使用企业的app_secret
直接对请求体进行AES
解密即可获取对应的信息。- 若回调失败,即第三方响应
code
不为0
,那么会每隔5s
进行重试,最多重试3
次。
- 若
3
次都没有成功响应,则会在对应工单中记录一条推送失败的操作记录。 - 若多次推送都失败,第三方可通过
api
接口xd.ticket.list
拉取工单数据。
2.3.1. 回调地址要求 #
- 推送的消息是
POST
方式,编码格式为utf-8
- 不支持传自定义参数
2.3.2. 消息解析 #
通过解析消息体,可以获取每条消息的具体信息
(1)推送给第三方的加密消息
curl --location '{{host}}'
--header 'Content-Type: application/json'
--data '{
"version": "v0.0.1",
"data": "jF5gtaRtxPX1XqOhuN+uFs+FgkYfTLBUh29OrghmTEAfVMqXGYAO4k1mbKuzhZnkQuAE8sAfhTIrqrgGMycDeJ00Bbx8H2rQxJdpCQGlK5wHhIo20takjXy0VIqQ+wy/sYMjwcPDVVcyB4tdb5S4d7CroP00o0mREloX4kmq4N3bum2dl4+X5iKV0aRTlYWcVFsre7PcF7OF/oBTQ14eF2yYILTTopTWHsPBUId0jARGhFIPnBLiv6a6gqMmouIfq4I4ckHCugmEduZP0sF5ITFIkqgL/ttDK/dcdY8UN1MgrUQrteT/1juz/CV35o/8Y9GeZRWCJNqXJaX5rBgXUM4Dx0a+IcRLcxydlegmi3uzxKacEUw9X76q/S7ePdbzJe/6ma5HTLl8zi+6uuunmZMUUg8Oo70yKuomVJylgCXiX3RgBmbDLXT6q/osfcyueSiHVqcOpw23+qSZ1ibkjC4ln1ouJjBQVBpRKLcTKkszBUTXLwRsHXTzYQIn8ZUMruJTiiVPw9rebCAKctUZZmPIRurikao6h4gHLLlYMDErq1KhCOf4WIpiB1jTXTfusJG1bdUBSTmQoFguwdVc90BENFGKI6iVADBlgVUyRhdscd2d6fp2+MDSdMaeZR/bt1o+gvqw7hrCST6Cj+ndKREDRSbV5yNPau7YAjcK8ikQyMVBd1OjXDCwuy/0dImcNpdzy3ZkI9BFc5lA4OC/w7POsBpwQaqRrCIqBJM4Phir7KcatSua51lHhLTrPuwXC7i/YLzLAkUhDqpRMkjUiej7Jf6MXdAGhnmqgpxOjS/JcvKqbObR5qNlEXSKrZzbkIOGXxZcADBn60AXqZ+Gn+izsH+mGce3tSbWgGTeIDRdWcD57kHIh/JNSalU5MckoWoOdXZYvUuHzpjgX765+BrPZ2SIewWl5MZobUHEVyGkqpHSBXmRlyYQROrl5a0G"
}'
(2)app_secret
nkwpdVyagzTcFXJt
(3)解密之后的消息
{
"event_id": "xd.ticket.result",
"event_time": 1670240307806,
"version": "v0.0.1",
"data": {
"version": "v0.0.1",
"category": "仓库",
"cf_values": [{
"name": "优先级",
"value": "普通",
"type": "优先级",
"is_changed": true
}, {
"name": "处理人",
"value": "待分配",
"type": "处理人"
}, {
"name": "补发原因",
"value": "破损",
"type": "下拉"
}],
"create_time": 1670240307000,
"creator_name": "杜可风按",
"enterprise_id": "q-610a49e0db1d8875d8980e41",
"source": "第三方",
"ticket_entry_id": 1,
"ticket_seq": 1,
"ticket_status": "已完成",
"ticket_type": "换货",
"ticket_type_id": 1,
"update_time": 1670240307000,
"message_type": "ticket_update",
"comments": [{
"content": "备注内容",
"create_time": 1670240307000,
"account_name": "张三"
}]
}
}
(4)第三方返回的响应(无需加密):
若失败,需返回message
错误原因
{
"code": 0,
"message": ""
}
2.3.3. 解密算法 #
Go解密代码:
func main() {
// 回调地址接受到的加密内容
msgSecret := "jF5gtaRtxPX1XqOhuN+uFs+FgkYfTLBUh29OrghmTEAfVMqXGYAO4k1mbKuzhZnksf5BPW2/8jmi1zIYwUG9jtZTWbLgwZB57EowtXP5H8qfsu4gBClufVT9cebZHYyFtYZ64btsIF8EUtsU9+p5bvNNB/k7urXfR+e7hk8XOMhfZI70ZRa+BEoNY8Br6kkcfZdV4S6mimViJJkhLBkbbCN22ZKDx/swzp8wGmI9y9RLLtjGPzUv6DJ+xyKu8fYTQjeYXHu9T/fGOhT6nbXbod0KBv8zODT9glbAXQg5ZSEKb8H2wSTBMy2SzswAksUPfRPnhMtSMbL164gybAd7TAXA7F+HLYxfvN2gPd4yDTlH1s2/CYGVLXAMoZVxR+hBY8QHn+f+sxLiAU1TstuRscT1AxIh5UY8KGKIrZi86qEa657JEPGFS2m1i4s5/eZwHxTj2D79l5uV0ek6vmqAumz/VLC0Uf7tpK96pAwdkA8ttifxkn3aknfzPgZgsp56czqk4sci88GHCQwv4IQn5zqA/GrmmmHNLxjsQ4tojMavrGMjrOI28k+s8H1YUWwvoBH6J5cyMi9Yb9JdL7/BKz9mtv4zpr7rQBuVe+rVyMHNa+bZ/eL2HCbqoj7OT8FGogPZQntmTfoXjfPn4AscUQrAIwpT5Z6edo19Ae4qj3kjGJ/Q0JwoTjTslIeAfRc9LRCjECNpHLTzHICi7NDDLrVE2L+OT6FeRrbYwpNGFQvcKNtL5ad3dXXj17jIob9SaijRKsDJZBHfIgxOGjHNhA20NPF/mRfxdU1erqX1N3gZsOEtPIui0yQt1X+Q02hD" //加密之后的消息
// 企业的 app_secret
appSecret := "nkwpdVyagzTcFXJt"
// 获取解密后的内容
sourceMsg := AesDecrypt(msgSecret, appSecret)
fmt.Printf("解密后:%sn", sourceMsg)
}
func AesDecrypt(msgSecret, appSecret string) string {
bytesPass, err := base64.StdEncoding.DecodeString(msgSecret)
if err != nil {
fmt.Println(err)
return "解密失败!!!"
}
sourceMsg, err := DoAesDecrypt(bytesPass, []byte(appSecret))
if err != nil {
fmt.Println(err)
return "解密失败!!!"
}
return string(sourceMsg)
}
func DoAesDecrypt(encryptedMsg, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
//AES分组长度为128位,所以blockSize=16,单位字节
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) //初始向量的长度必须等于块block的长度16字节
origData := make([]byte, len(encryptedMsg))
blockMode.CryptBlocks(origData, encryptedMsg)
origData = PKCS5UnPadding(origData)
return origData, nil
}
//去除填充数据
func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unfilledNum := int(origData[length-1])
return origData[:(length - unfilledNum)]
}
Python解密代码:
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
def aes_decrypt(msg_secret, app_secret):
key = app_secret.encode('utf-8')
encrypted_msg = base64.standard_b64decode(msg_secret)
cipher = AES.new(key, AES.MODE_CBC, key[:16])
decrypted_data = cipher.decrypt(encrypted_msg)
unpadded_data = unpad(decrypted_data, 16)
return unpadded_data.decode('utf-8')
msg_secret = "jF5gtaRtxPX1XqOhuN+uFs+FgkYfTLBUh29OrghmTEAfVMqXGYAO4k1mbKuzhZnksf5BPW2/8jmi1zIYwUG9jtZTWbLgwZB57EowtXP5H8qfsu4gBClufVT9cebZHYyFtYZ64btsIF8EUtsU9+p5bvNNB/k7urXfR+e7hk8XOMhfZI70ZRa+BEoNY8Br6kkcfZdV4S6mimViJJkhLBkbbCN22ZKDx/swzp8wGmI9y9RLLtjGPzUv6DJ+xyKu8fYTQjeYXHu9T/fGOhT6nbXbod0KBv8zODT9glbAXQg5ZSEKb8H2wSTBMy2SzswAksUPfRPnhMtSMbL164gybAd7TAXA7F+HLYxfvN2gPd4yDTlH1s2/CYGVLXAMoZVxR+hBY8QHn+f+sxLiAU1TstuRscT1AxIh5UY8KGKIrZi86qEa657JEPGFS2m1i4s5/eZwHxTj2D79l5uV0ek6vmqAumz/VLC0Uf7tpK96pAwdkA8ttifxkn3aknfzPgZgsp56czqk4sci88GHCQwv4IQn5zqA/GrmmmHNLxjsQ4tojMavrGMjrOI28k+s8H1YUWwvoBH6J5cyMi9Yb9JdL7/BKz9mtv4zpr7rQBuVe+rVyMHNa+bZ/eL2HCbqoj7OT8FGogPZQntmTfoXjfPn4AscUQrAIwpT5Z6edo19Ae4qj3kjGJ/Q0JwoTjTslIeAfRc9LRCjECNpHLTzHICi7NDDLrVE2L+OT6FeRrbYwpNGFQvcKNtL5ad3dXXj17jIob9SaijRKsDJZBHfIgxOGjHNhA20NPF/mRfxdU1erqX1N3gZsOEtPIui0yQt1X+Q02hD"
app_secret = "nkwpdVyagzTcFXJt"
decrypted_msg = aes_decrypt(msg_secret, app_secret)
print(f"解密后: {decrypted_msg}")
#请注意,Python 中使用了 pycryptodome 库来提供 AES 加密和解密的功能。如果您尚未安装该库,可以通过运行 pip install pycryptodome 命令进行安装。
Java解密代码
public class Main {
public static void main(String[] args) {
try {
String msgSecret = "jF5gtaRtxPX1XqOhuN+uFs+FgkYfTLBUh29OrghmTEAfVMqXGYAO4k1mbKuzhZnksf5BPW2/8jmi1zIYwUG9jtZTWbLgwZB57EowtXP5H8qfsu4gBClufVT9cebZHYyFtYZ64btsIF8EUtsU9+p5bvNNB/k7urXfR+e7hk8XOMhfZI70ZRa+BEoNY8Br6kkcfZdV4S6mimViJJkhLBkbbCN22ZKDx/swzp8wGmI9y9RLLtjGPzUv6DJ+xyKu8fYTQjeYXHu9T/fGOhT6nbXbod0KBv8zODT9glbAXQg5ZSEKb8H2wSTBMy2SzswAksUPfRPnhMtSMbL164gybAd7TAXA7F+HLYxfvN2gPd4yDTlH1s2/CYGVLXAMoZVxR+hBY8QHn+f+sxLiAU1TstuRscT1AxIh5UY8KGKIrZi86qEa657JEPGFS2m1i4s5/eZwHxTj2D79l5uV0ek6vmqAumz/VLC0Uf7tpK96pAwdkA8ttifxkn3aknfzPgZgsp56czqk4sci88GHCQwv4IQn5zqA/GrmmmHNLxjsQ4tojMavrGMjrOI28k+s8H1YUWwvoBH6J5cyMi9Yb9JdL7/BKz9mtv4zpr7rQBuVe+rVyMHNa+bZ/eL2HCbqoj7OT8FGogPZQntmTfoXjfPn4AscUQrAIwpT5Z6edo19Ae4qj3kjGJ/Q0JwoTjTslIeAfRc9LRCjECNpHLTzHICi7NDDLrVE2L+OT6FeRrbYwpNGFQvcKNtL5ad3dXXj17jIob9SaijRKsDJZBHfIgxOGjHNhA20NPF/mRfxdU1erqX1N3gZsOEtPIui0yQt1X+Q02hD";
String appSecret = "nkwpdVyagzTcFXJt";
String decrypt = decrypt(msgSecret, appSecret);
System.out.println("解密后: " + decrypt);
} catch (Exception e) {
System.out.println("解密失败!!!");
e.printStackTrace();
}
}
public static String decrypt(String sSrc, String sKey) throws Exception {
try {
byte[] raw = sKey.getBytes(StandardCharsets.US_ASCII);
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
int blockSize = cipher.getBlockSize();
IvParameterSpec iv = new IvParameterSpec(sKey.substring(0, blockSize).getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] encrypted1 = Base64.getDecoder().decode(sSrc); // 先用Base64解密
byte[] original = cipher.doFinal(encrypted1);
return new String(original, StandardCharsets.UTF_8);
} catch (Exception ex) {
throw ex;
}
}
}
2.3.4. 结构体展示 #
参数 |
类型 |
描述 |
示例 |
event_id |
string |
推送事件类型 |
xd.ticket.result |
event_time |
int64 |
消息推送时间 |
1670240307806 |
version |
string |
推送接口的版本信息 |
v0.0.1 |
data |
object |
对应的消息内容 |
|
data.version |
string |
推送事件的版本信息 |
v0.0.1 |
data.message_type |
string |
工单变更类型 |
ticket_create/ticket_update/ticket_comment (创建工单/更新工单/评论(备注)工单) |
data.ticket_entry_id |
int |
工单的id |
1 |
data.ticket_seq |
int |
工单编号 |
1 |
data.ticket_type_id |
int |
工单类型id |
1 |
data.category |
string |
一级分类名称 |
仓库 |
data.ticket_type |
string |
二级工单类型名称 |
换货 |
data.ticket_status |
string |
工单状态 |
待分配 |
data.cf_values |
[]object |
对应工单类型管理中当前二级工单类型中的组件 |
{ "name": "店铺", "value": "小宏v/淘宝", "is_changed": true, "type":"店铺" } |
data.cf_values.name |
string |
组件名称 |
店铺 |
data.cf_values.value |
string |
填入的值 |
小宏v/淘宝 |
data.cf_values.is_changed |
boolean |
本次变更后,该字段值是否改变 |
false |
data.cf_values.type |
string |
组件类型 |
店铺 |
data.creator_name |
string |
创建人 |
张三 |
data.create_time |
int |
创建时间 |
1670240307000 |
data.update_time |
int |
更新时间 |
1670240307000 |
data.completed_time |
int |
完成时间 |
1670240307000 |
data.source |
string |
创建来源 |
后台/小程序/导入/第三方 |
data.comments |
[]object |
|
|
data.comments.content |
string |
备注内容 |
xxx |
data.comments.create_time |
string |
备注时间 |
1670240307000 |
data.comments.account_name |
string |
备注人员账号名称 |
张三 |
data.relations |
[]object |
||
data.relations.is_parent |
boolean |
目标是否是本工单父单,false则为子单 |
|
data.relations.ticket_entry_id |
int |
父子关联单的工单id |
|
data.relations.ticket_entry_seq |
int |
父子关联单的工单序列 |
示例
{
"event_id": "xd.ticket.result",
"event_time": 1670240307806,
"version":"v0.0.1",
"data": {
"version":"v0.0.1",
"category": "仓库",// 一级工单类型
"cf_values": [{
"name": "优先级", // 组件名称
"value": "普通", // 组件值
"type":"优先级", // 组件类型
"is_changed": true // 本次变更后,该字段值是否改变
}, {
"name": "处理人",
"value": "待分配",
"type":"处理人"
}, {
"name": "补发原因",
"value": "破损",
"type":"下拉"
}],
"relations":[
{
"is_parent":false,
"ticket_entry_id":370198,
"ticket_entry_seq":56680,
"ticket_type_id":12118
}
],
"create_time": 1670240307000, // 创建时间
"creator_name": "杜可风按",// 创建人
"enterprise_id": "q-610a49e0db1d8875d8980e41",
"source": "第三方", // 工单创建来源
"ticket_entry_id": 1, // 工单id
"ticket_seq": 1, // 工单编号
"ticket_status": "已完成",// 工单状态
"ticket_type": "换货", // 二级工单类型
"ticket_type_id": 1,// 工单类型id
"update_time": 1670240307000,// 更新时间
"message_type": "ticket_update",// 推送事件类型
"comments":[{
"content":"备注内容",
"create_time":1670240307000,//备注时间
"account_name":"张三"//备注人
}]
}
}
3. API接口 #
3.1. 创建工单xd.ticket.create #
- method:
xd.ticket.create
- 参数:
参数 |
类型 |
必填 |
描述 |
示例值 |
enterprise_id |
string |
是 |
企业ID |
1234567890 |
operator |
string |
是 |
操作人,对应青鸟中人员账号名称 |
张三 |
category |
string |
是 |
一级分类名称 |
仓库 |
ticket_type |
string |
是 |
二级工单类型名称 |
换货 |
ticket_status |
string |
是 |
工单状态 |
待分配 |
cf_values |
[]object |
是 |
对应工单类型管理中当前二级工单类型中的组件,传入要创建或更新的组件的值 (填入值可参考 5.工单传入参数规则) |
{ "name": "店铺", "value": "小宏v/淘宝" } |
cf_values.name |
string |
是 |
组件名称 |
店铺 |
cf_values.value |
string |
是 |
填入的值 |
小宏v/淘宝 |
version |
string |
是 |
版本 |
v0.0.1 |
relations |
[]object |
否 |
父子单关系。不填则不关联父子关系 |
[{ "is_parent":true, "ticket_entry_id":370000 }] is_parent:对方是否是本工单父工单,false则为子工单。 支持关联最多三个工单。 |
- 示例
###创建工单
{
"version":"v0.0.1",
"enterprise_id": "1234567890",
"operator": "张三",
"category": "仓库",
"ticket_type": "换货",
"cf_values": [{
"name": "店铺",
"value": "小宏v/淘宝"
},{
"name": "订单号",
"value": "2245853052090782746"
}]
}
@return
{
"code": 0,
"error_message": "",
"data": {
"code": 0,
"data": {
"data": {
"fields": [
"店铺"
],
"ticket_entry_id": 1, // 标识工单唯一的id,可用于更新工单
"ticket_seq":2 // 工单编号,可用于web页面查询工单编号
},
"err_code": 10204,
"message": "存在非必填组件,字段因不匹配被忽略"
}
}
}
3.2. 更新工单xd.ticket.update #
- method:
xd.ticket.update
- 参数:
参数 |
类型 |
必填 |
描述 |
示例值 |
enterprise_id |
string |
是 |
企业ID |
1234567890 |
operator |
string |
是 |
操作人 |
张三 |
ticket_entry_id |
int |
是 |
需更新工单的id,由创建接口返回 |
1 |
ticket_status |
string |
否 |
工单状态 |
待分配 |
cf_values |
[]object |
否 |
对应工单类型管理中当前二级工单类型中的组件,传入要创建或更新的组件的值 (填入值可参考 5.工单传入参数规则) |
{ "name": "店铺", "value": "小宏v/淘宝" } |
cf_values.name |
string |
|
组件名称 |
店铺 |
cf_values.value |
string |
|
填入的值 |
小宏v/淘宝 |
comment |
string |
否 |
备注内容 |
备注内容 |
version |
string |
是 |
版本 |
v0.0.1 |
silent |
boolean |
否 |
是否推送第三方消息 |
true |
relations |
[]object |
否 |
更新父子单关系。不填或空则为删除父子单关系 |
[{ "is_parent":true, "ticket_entry_id":370000 }] is_parent:对方是否是本工单父工单,false则为子工单。 |
- 示例
###更新工单
{
"version":"v0.0.1",
"enterprise_id": "1234567890",
"operator": "张三",
"ticket_entry_id":1,
"cf_values": [{
"name": "订单号",
"value": "2245853052090782746"
}]
}
@return
{
"code": 0,
"error_message": "",
"data": {
"code": 0,
"data": {
"data": [],
"err_code": 10102,
"message": "工单状态不存在"
}
}
}
3.3. 查询工单xd.ticket.list #
- method:
xd.ticket.list
- 参数:
参数 |
类型 |
必填 |
描述 |
示例值 |
enterprise_id |
string |
是 |
企业ID |
1234567890 |
limit |
Int |
是 |
默认值10,不超过1000 |
10 |
skip |
int |
否 |
默认值0 |
0 |
category |
string |
否 |
一级分类名称 |
仓库 |
ticket_type |
string |
否 |
二级工单类型名称 |
换货 |
create_time_begin |
int |
否 |
工单创建开始时间(毫秒级时间戳) |
1670083200000 |
create_time_end |
int |
否 |
工单创建截止时间(毫秒级时间戳) |
1670169599999 |
update_time_begin |
int |
否 |
工单更新开始时间(毫秒级时间戳) |
1670083200000 |
update_time_end |
int |
否 |
工单更新截止时间(毫秒级时间戳) |
1670169599999 |
completed_time_begin |
int |
否 |
工单完成开始时间(毫秒级时间戳) |
1670083200000 |
completed_time_end |
int |
否 |
工单完成截止时间(毫秒级时间戳) |
1670169599999 |
ticket_seq |
int |
否 |
工单编号 |
1 |
ticket_status |
[]string |
否 |
工单状态 |
["已关闭","已完成"] |
version |
string |
是 |
版本 |
v0.0.1 |
cf_values |
[]object |
否 |
对应工单类型管理中当前二级工单类型中的组件,传入要创建或更新的组件的值 (填入值可参考 5.工单传入参数规则) |
{ "name": "订单号", "value": "123456", "type": "订单信息" } |
cf_values.name |
string |
|
组件名称 |
订单号 |
cf_values.value |
string |
|
填入的值 |
123456 |
cf_values.type |
string |
|
组件类型 |
订单信息 |
search_relations |
boolean |
查询父子单时必填 |
是否查询父子单关系 |
true: 查询父子单关系。可跨普通/批量打款查询工单 |
ticket_entry_ids |
[]int |
查询父子单时必填 |
父子单工单id |
说明: 需要通过父子单id进一步查询工单列表获取父子单详情。 |
- 示例:
###查询工单
{
"version":"v0.0.1",
"enterprise_id": "1234567890",
"category":"仓库",
"ticket_type": "换货",
"limit":30,
"skip":60,
"cf_values": [
{
"type": "订单信息",
"name": "订单信息",
"value": "123"
}
]
}
@return
{
"code": 0,
"error_message": "",
"data": {
"code": 0,
"data": {
"data": {
"count": 8536,
"entries": [{
"category": "群对接",// 一级工单类型
"cf_values": [{
"name": "处理人1",// 组件名称
"value": "22222" // 组件值
},{
"name": "优先级22",
"value": "普通"
},{
"name": "多选14",
"value": "[]"
},{
"name": "多级下拉18",
"value": "["11111","234543"]"
},{
"name": "订单信息10",
"value": "{"order_id":"1730441211855860970"}"
},{
"name": "订单金额",
"value": "0.01"
},{
"name": "顾客昵称23",
"value": "tb43288734"
},{
"name": "店铺",
"value": "不像实力派/淘宝"
},{
"name": "平台",
"value": "淘宝"
},{
"name": "收货人手机号3",
"value": "15775555555"
},{
"name": "发货仓库19",
"value": "成都"
},{
"name": "下单时间7",
"value": "1667875139000"
}],
"relations":[
{"is_parent":true,"ticket_entry_id":95,"ticket_entry_seq":25,"ticket_type_id":93}
],
"create_time": 1680854756000,// 创建时间
"creator_name": "杜可风按",// 创建人
"source": "后台", // 创建工单来源
"ticket_entry_id": 5761752,// 工单id
"ticket_seq": 17720,//工单编号
"ticket_status": "待受理",// 工单状态
"ticket_type": "群对接(全组件)",// 二级工单类型
"ticket_type_id": 5876,
"update_time": 1680854757000,// 更新时间
"comments":[{
"content":"备注内容",
"create_time":1670240307000,//备注时间
"account_name":"张三"//备注人
}]
}
]
}
}
}
}
3.4. 查询批量打款xd.ticket.batch_pay_list #
- method:
xd.ticket.batch_pay_list
- 参数:
参数 |
类型 |
必填 |
描述 |
示例值 |
enterprise_id |
string |
是 |
企业ID |
1234567890 |
limit |
Int |
是 |
默认值10,不超过1000 |
10 |
skip |
int |
否 |
默认值0 |
0 |
category |
string |
否 |
一级分类名称 |
仓库 |
ticket_type |
string |
否 |
二级工单类型名称 |
换货 |
create_time_begin |
int |
否 |
工单创建开始时间(毫秒级时间戳) |
1670083200000 |
create_time_end |
int |
否 |
工单创建截止时间(毫秒级时间戳) |
1670169599999 |
ticket_seq |
int |
否 |
工单编号 |
1 |
pay_time_begin |
int |
否 |
支付开始时间(毫秒级时间戳) |
1670083200000 |
pay_time_end |
int |
否 |
支付截止时间(毫秒级时间戳) |
1670169599999 |
ticket_status |
[]string |
否 |
工单状态 |
["待审核"] |
version |
string |
是 |
版本 |
v0.0.1 |
cf_values |
[]object |
否 |
对应工单类型管理中当前二级工单类型中的组件,传入要创建或更新的组件的值 (填入值可参考 5.工单传入参数规则) |
{ "name": "订单号", "value": "123456", "type": "订单信息" } |
cf_values.name |
string |
|
组件名称 |
订单号 |
cf_values.value |
string |
|
填入的值 |
123456 |
cf_values.type |
string |
|
组件类型 |
订单信息 |
- 示例:
###查询工单
{
"version":"v0.0.1",
"enterprise_id": "1234567890",
"category":"仓库",
"ticket_type": "换货",
"limit":30,
"skip":60,
"cf_values": [
{
"type": "订单信息",
"name": "订单信息",
"value": "123"
}
]
}
@return
{
"code": 0,
"error_message": "",
"data": {
"count":2100,
"entries":[{
"ticket_entry_id": 330080, // 工单id
"ticket_seq":321321, //工单编号
"category": "全组件-组件",// 一级工单类型
"ticket_type": "全部组件", // 二级工单类型
"creator_name": "杜可风按",// 创建人
"ticket_status": "已完成",// 工单状态
"create_time": 1670240307000, // 创建时间
"update_time": 1670240307000// 更新时间
"pay_account":"908392819@126.com", // 付款支付宝账号
"pay_time":1670240307000, //付款时间
"pay_operator":"小王", //付款人
"cf_values": [{
"name": "优先级",// 组件名称
"value": "普通" // 组件值
}, {
"name": "处理人",
"value": "待分配"
}],
"comments":[{
"content":"备注内容",
"create_time":1670240307000,//备注时间
"account_name":"张三"//备注人
}]
}]
}
}
4. 订阅事件 #
4.1. 工单变更消息xd.ticket.result
#
将消费者通过小程序、客服通过青鸟工单后台、导入创建或更新的工单推送消息到第三方。
- sub_event_ids:
xd.ticket.result
- 参数:
参数 |
类型 |
描述 |
示例值 |
ticket_entry_id |
int |
工单的id |
1 |
ticket_seq |
int |
工单编号 |
1 |
ticket_type_id |
int |
工单类型id |
1 |
category |
string |
一级分类名称 |
仓库 |
ticket_type |
string |
二级工单类型名称 |
换货 |
ticket_status |
string |
工单状态 |
待分配 |
cf_values |
[]object |
对应工单类型管理中当前二级工单类型中的组件 |
{ "name": "店铺", "value": "小宏v/淘宝", "is_changed": true } |
cf_values.name |
string |
组件名称 |
店铺 |
cf_values.value |
string |
填入的值 |
小宏v/淘宝 |
cf_values.is_changed |
boolean |
本次变更后,该字段值是否改变 |
false |
creator_name |
string |
创建人 |
张三 |
create_time |
int |
创建时间 |
1670240307000 |
source |
string |
创建来源 |
后台/小程序/导入/第三方 |
message_type |
string |
推送事件类型 |
ticket_create/ticket_update/ticket_comment (创建工单/更新工单/评论(备注)工单) |
- 示例:
{
"event_id": "xd.ticket.result",
"event_time": 1670240307806,
"data": {
"category": "仓库",// 一级工单类型
"cf_values": [{
"name": "优先级",// 组件名称
"value": "普通", // 组件值
"is_changed": true // 本次变更后,该字段值是否改变
}, {
"name": "处理人",
"value": "待分配"
}],
"create_time": 1670240307000, // 创建时间
"creator_name": "杜可风按",// 创建人
"enterprise_id": "q-610a49e0db1d8875d8980e41",
"source": "第三方", // 工单创建来源
"ticket_entry_id": 330080, // 工单id
"ticket_seq": 17632, // 工单编号
"ticket_status": "已完成",// 工单状态
"ticket_type": "换货", // 二级工单类型
"ticket_type_id": 1480,// 工单类型id
"update_time": 1670240307000,// 更新时间
"message_type": "ticket_update",// 推送事件类型
"comments":[{
"content":"备注内容",
"create_time":1670240307000,//备注时间
"account_name":"张三"//备注人
}]
},
"appkey": ""
}
5. 工单传入参数规则 #
在创建工单、更新工单时,传入组件值的数据规则与工单类型中组件类型对应的,对应规则如下,若表格中无对应的组件,则不支持传入。
5.1. 版本v0.0.1 #
组件类型 |
新建、更新工单 传入示例 |
查询工单 传入示例 |
说明 |
数据来源 |
|
电商组件 |
|
||||
店铺 |
{"name": "组件名称","value": "测试店铺非卖品/淘宝"} |
{"name": "组件名称","value": "["测试店铺非卖品/淘宝"]","type": "店铺"} |
店铺管理中已有的店铺,格式:店铺名称/平台 |
||
平台 |
{"name": "组件名称","value": "淘宝"} |
{"name": "组件名称","value": "["淘宝"]","type":"平台"} |
青鸟协同里平台组件已有的平台 |
||
订单信息 |
订单号 |
{"name": "组件名称","value": "{"order_id":"20220000000","sku_ids":["1000000222"],"goods_ids":["9999000000"]}"} |
{"name": "组件名称","value": "20220000000","type":"订单信息"} |
order_id为订单号 |
|
订单商品 |
|
sku_id为商品SKU的ID,goods_id为商品的ID 传入SKU的ID时,将勾选对应的SKU商品;传入商品ID时,可能出现多个不同SKU的商品,将勾选商品ID对应的该商品在订单中的所有SKU商品。 |
|
||
订单金额 |
{"name": "组件名称","value": "1.02"} |
{"name": "组件名称","value": "[1,2]","type":"订单金额"} |
传入不为空时则为传入的内容,没有则默认按照订单自动回填;任意数字(格式支持最多3位小数) |
|
|
顾客昵称 |
{"name": "组件名称","value": "tb425743344"} |
{"name": "组件名称","value": "tb425743344","type":"顾客昵称"} |
传入不为空时则为传入的内容,没有则默认通过订单号自动回填 |
|
|
商品 |
{"name": "组件名称","value": "[{"outer_sku_id":"PT004","item_name":"衣架,二手衣架","num":1}]"} |
|
ERP商品 value.outer_sku_id 为商家编码 value.item_name 为商品名称 value.num 为商品数据 |
|
|
发货仓库 |
{"name": "组件名称","value": "成都"} |
{"name": "组件名称","value": "成都","type":"发货仓库"} |
传入不为空时则为传入的内容,没有则默认按照订单自动回填;请填写工单系统内发货仓库组件中已有的选项 |
|
|
下单时间 |
{"name": "组件名称","value": "1667977603000"} |
|
传入不为空时则为传入的内容,没有则默认按照订单自动回填;填写13位的时间戳 |
|
|
收货人信息套件 |
收货人 |
{"name": "组件名称","value": "张三"} |
{"name": "组件名称","value": "张三","type":"收货人"} |
收货人姓名 |
|
收货人手机号 |
{"name": "组件名称","value": "180000000"} |
{"name": "组件名称","value": "180000000","type":"收货人手机号"} |
180000000 |
|
|
收货人地址 |
{"name": "组件名称","value": "{"province":{"name":"四川省"},"town":{"name":"成都市"},"district":{"name":"双流区"},"street":"中和街道","detail":"中和街道朝阳路229号城南春天"}"} |
{"name": "组件名称","value": "四川省成都市双流区中和街道朝阳路229号城南春天","type":"收货人地址"} |
省,市,区,街道,详细地址 (每个字段都需填写,地址信息才能生效) |
|
|
物流信息 |
{"name":"组件名称","value":"[{"logistics_company":"中通","logistics_no":"123465489"}]"} |
|
logistics_company为承运商 logistics_no为物流单号 |
|
|
基础组件 |
|
||||
单行文本 |
{"name": "组件名称","value": "单行文本"} |
{"name": "组件名称","value": "单行文本","type": "单行文本"} |
一则单行文本 |
|
|
多行文本 |
{"name": "组件名称","value": "多行文本"} |
{"name": "组件名称","value": "多行文本","type":"多行文本"} |
一则多行文本 |
|
|
日期 |
{"name": "组件名称","value": "1667977603000"} |
{"name": "组件名称","value": "[1667977603000,1667977603000]","type":"日期"} |
填写毫秒级时间戳 |
|
|
日期&时间 |
{"name": "组件名称","value": "1667977603000"} |
{"name": "组件名称","value": "[1667977603000,1667977603000]","type":"日期&时间"} |
填写毫秒级时间戳 |
|
|
下拉 |
{"name": "组件名称","value": "下拉选项"} |
{"name": "组件名称","value": "["下拉选项"]","type":"下拉"} |
请填写该组件内已有的选项 |
|
|
多级下拉 |
{"name": "组件名称","value": "["第1层选项","第2层选项"]"} |
{"name": "组件名称","value": "["第1层选项>第2层选项"]","type":"多级下拉"} |
请填写该组件内已有的选项 |
|
|
单选 |
{"name": "组件名称","value": "单选选项"} |
{"name": "组件名称","value": "["单选选项"]","type":"单选"} |
请填写该组件内已有的选项 |
|
|
多选 |
{"name": "组件名称","value": "["第1个多选选项","第2个多选选项"]"} |
{"name": "组件名称","value": "["第1个多选选项","第2个多选选项"]","type":"多选"} |
请填写该组件内已有的选项 |
|
|
NPS量表 |
{"name": "组件名称","value": "NPS选项值"} |
{"name": "组件名称","value": "["NPS选项值"]","type":"NPS量表"} |
请填写该组件内已有的选项 |
||
数值 |
{"name": "组件名称","value": "1.2"} |
{"name": "组件名称","value": "[1,2]","type":"数值"} |
任意数字(格式支持最多3位小数) |
|
|
上传图片 |
{"name": "组件名称","value":"[{"name":"f5dd25c2527841ec883df8f3ceadc983.jpg","url":"https://wangcai-client-new.oss-cn-hangzhou.aliyuncs.com/1647949042024_f5dd25c2527841ec883df8f3ceadc983.jpg"}]"} |
|
value.name 为图片名称 value.url 为图片的地址 |
|
|
附件 |
{"name": "组件名称","value":"[{"file_name":"f5dd25c2527841ec883df8f3ceadc983.jpg","url":"https://wangcai-client-new.oss-cn-hangzhou.aliyuncs.com/1647949042024_f5dd25c2527841ec883df8f3ceadc983.jpg"}]"} |
|
value.file_name为附件名称 value.url 为附件的地址 |
|
|
优先级 |
{"name": "组件名称","value": "普通"} |
{"name": "组件名称","value": "["普通"]","type":"优先级"} |
普通/紧急/非常紧急 |
|
|
处理人 |
{"name": "组件名称","value": "处理人"} |
{"name": "组件名称","value": "["处理人"]","type":"处理人"} |
请填写工单系统内成员昵称或保持空白 |
|
|
工单状态 |
作为ticket_status参数传入,”ticket_status“:"待分配" |
作为ticket_status参数传入,”ticket_status“:["待分配"] |
待分配/待受理/处理中/已完成/已关闭 审批驳回/待审批/待支付/支付中/已支付/支付失败/已关闭/已支付处理中 |
|
|
跟进时间 |
{"name": "组件名称","value": "1667977603000"} |
{"name": "组件名称","value": "[1667977603000,1667977603000]","type":"跟进时间"} |
填写13位的时间戳 |
|
|
智能组件 |
|
||||
群对接 |
{"name": "组件名称","value": "中通对接群"} |
|
请填写工单系统中已配置的物流对接群 |
||
旺店通智能组件 |
{"name": "组件名称","value": "[{"order_type":"plat","erp_order_id":"JY202209210235","warehouse_code":"001","logistics_company":"淘宝-中通","logistics_no":"","create_time":1663750499000,"got_time":0,"sign_time":0,"estimate_profit":0.0,"latest_logistics_info":"","goods":[{"goods_name":"床上四件套-床单","spec_name":"默认规格","spec_no":"CHUANG-CHUANGDAN","share_price":0.0011,"share_amount":0.0077,"num":7}]}]"} |
|
order_type 订单类型 string erp_order_id erp单号 string warehouse_code 发货仓 string logistics_company 物流公司 string logistics_no 物流编号 string create_time 创建时间 int64 13位的时间戳 got_time 揽收时间 int64 13位的时间戳 sign_time 签收时间 int64 13位的时间戳 estimate_profit 预估毛利 float64 latest_logistics_info 最新物流信息 string goods.goods_name 商品名称 string goods.spec_name 规格名称 string goods.spec_no 规格编码 string goods.share_price 分摊价格 float64 goods.share_amount 分摊总价 float64 goods.num 数量 int64 |
|
6. 错误码 #
错误码分为两部分:
- http 响应体
- http body 返回的 json 中的 code
- 其中有一些错误会在 http body 返回的 json 中的 data 中,返回业务具体信息。如:创建工单时,存在必填项未填时,data中返回具体哪些组件未填
{
"code": 0,
"error_message": "",
"data": {
"code": 0,
"data": {
"data": {
"fields": [
"店铺"
],
"ticket_entry_id": 1
},
"err_code": 10204,
"message": "存在非必填组件,字段因不匹配被忽略"
}
}
}
6.1. http 响应体 #
外层code值 |
含义 |
0 |
成功,可以使用data内的业务内容 |
非0 |
各种网关层错误,原因会填写error_message字段 |
6.2. 内层code #
值 |
含义 |
data |
10001 |
找不到对应的工单类型 |
|
10002 |
存在多个工单类型 |
|
10003 |
该工单类型不支持创建工单 |
|
10101 |
工单ID不存在 |
|
10102 |
工单状态不存在 |
|
10103 |
该企业不存在 |
|
10104 |
该操作人不存在 |
|
10105 |
该版本不存在 |
|
10201 |
缺少必填项组件 |
{"fields":["组件名称1","组件名称2"]} |
10202 |
必填组件,但未定义规则,不支持传入 |
{"fields":["组件名称1","组件名称2"]} |
10203 |
必填组件字段不匹配 |
{"fields":["组件名称1","组件名称2"]} |
10204 |
存在非必填组件字段不匹配(其他能匹配到的字段仍然能成功更新) |
{"fields":["组件名称1","组件名称2"]} |
10301 |
此工单当前状态下不允许编辑 |
|
10302 |
当前账号无权限编辑工单 |
|