轻流 x 旺店通(企业版)
应用场景
产品介绍:
旺店通是一款专门为电商量身定做的电商ERP系统软件,致力于提供专业的电子商务ERP解决方案,能全面覆盖线上电商、网店以及线下实体店进销存管理及wms仓储管理系统等
API文档:https://open.wangdian.cn/qyb/open/apidoc
实现目标:
通过轻流应用表单实现与旺店通(企业版)进行对接
Ps:
1、目前批量获取数据暂时只能存放在表格中并且只能获取部分数据,体验上会有点不好。
不过后续产品QS主动支持代码块处理即可实现定时轮训获取。如果不想要放在表格中,可以采用定制开发来实现
2、部分接口暂时无法通过轻流实现,原因是由于数组中需要嵌套数组,但可以使用代码块进行数据处理,望知晓,例如:创建原始订单(https://open.wangdian.cn/qyb/open/apidoc/doc?path=trade_push.php)
落地方案

图文/视频演示
帮助文档
一、旺店通(企业版)接入

- 获取必要参数,这里是测试环境测试用

- 正式环境必要参数获取位置

注意点:
接口规范:https://open.wangdian.cn/qyb/open/guide?path=guide_jkgf
- REST风格的API,使用HTTPS协议发送POST请求调用接口。
- HTTPS请求头类型为 Content-Type: application/x-www-form-urlencoded

二、签名算法
1、签名sign介绍

2、代码块实现
以文档所举为示例,其中timestamp计算规则:北京时间1970-01-01 08:00:00起至现在的总秒数,10位int值,旺店通企业版API服务端允许请求最大时间误差为5min,date.timezone = Asia/Shanghai。
const { differenceInSeconds, parse } = require('date-fns');
const crypto = require('crypto');
// 通过当前时间和起始时间计算总秒数
function calculateTotalSeconds() {
const startTime = parse('1970-01-01 08:00:00', 'yyyy-MM-dd HH:mm:ss', new Date(), { timeZone: 'Asia/Shanghai' });
const currentTime = new Date();
const seconds = differenceInSeconds(currentTime, startTime);
return seconds;
}
// 输出总秒数时间戳
const totalSeconds = calculateTotalSeconds();
// 请求参数
const params = {
appkey: 'test2-xx',
page_no: 0,
end_time: '2016-08-01 13:00:00',
start_time: '2016-08-01 12:00:00',
page_size: 40,
sid: 'test2',
timestamp: totalSeconds,
};
// 第一步:参数排序
const sortedParams = Object.keys(params).sort().reduce((acc, key) => Object.assign(acc, { [key]: params[key] }), {});
// 第二步:处理每个参数
const processedParams = Object.keys(sortedParams).map((key) => {
let value = sortedParams[key].toString();
let length = Buffer.byteLength(value, 'utf8').toString().padStart(4, '0');
let processedValue = `${length}-${value}`;
return `${Buffer.byteLength(key, 'utf8').toString().padStart(2, '0')}-${key}:${processedValue};`;
});
// 第三步:计算 sign
const appsecret = '12345';
const signString = processedParams.join('');
const index = signString.indexOf('09-timestamp'); // 获取 "09-timestamp" 的索引位置
const semicolonIndex = signString.indexOf(';', index); // 获取在 "09-timestamp" 后面的分号的索引位置
const result = signString.slice(0, semicolonIndex) + signString.slice(semicolonIndex + 1); // 切割字符串,并删除找到的分号
const sign = crypto.createHash('md5').update(`${result}${appsecret}`).digest('hex');
// 添加 sign 参数
params.sign = sign;
qf_output={params}
3、代码测试

三、查询订单管理为例
对应的接口文档:https://open.wangdian.cn/qyb/open/apidoc/doc?path=trade_query.php

1、代码块实现
- 具体查询参数内容根据接口文档去填写,填写至params中

- 这里end_time、start_time为插入表单中的字段

const { differenceInSeconds, parse } = require('date-fns');
const crypto = require('crypto');
const axios = require('axios');
const qs = require('qs');
// 通过当前时间和起始时间计算总秒数
function calculateTotalSeconds() {
const startTime = parse('1970-01-01 08:00:00', 'yyyy-MM-dd HH:mm:ss', new Date(), { timeZone: 'Asia/Shanghai' });
const currentTime = new Date();
const seconds = differenceInSeconds(currentTime, startTime);
return seconds;
}
// 输出总秒数时间戳
const totalSeconds = calculateTotalSeconds();
// 请求参数
const params = {
appkey: 'test2-123456',
page_no: '0',
end_time: qf_field.{结束时间$$1856C9B1B$$},
start_time: qf_field.{开始时间$$1856C9B1A$$},
page_size: '50',
sid: 'test2',
timestamp: totalSeconds,
};
if(params.start_time==null || params.end_time==null){
qf_output={'data':null}
}else{
// 第一步:参数排序
const sortedParams = Object.keys(params).sort().reduce((acc, key) => Object.assign(acc, { [key]: params[key] }), {});
// 第二步:处理每个参数
const processedParams = Object.keys(sortedParams).map((key) => {
let value = sortedParams[key].toString();
let length = Buffer.byteLength(value, 'utf8').toString().padStart(4, '0');
let processedValue = `${length}-${value}`;
return `${Buffer.byteLength(key, 'utf8').toString().padStart(2, '0')}-${key}:${processedValue};`;
});
// 第三步:计算 sign
const appsecret = '12345';
const signString = processedParams.join('');
const index = signString.indexOf('09-timestamp'); // 获取 "09-timestamp" 的索引位置
const semicolonIndex = signString.indexOf(';', index); // 获取在 "09-timestamp" 后面的分号的索引位置
const result = signString.slice(0, semicolonIndex) + signString.slice(semicolonIndex + 1); // 切割字符串,并删除找到的分号
const sign = crypto.createHash('md5').update(`${result}${appsecret}`).digest('hex');
// 添加 sign 参数
params.sign = sign;
let data = qs.stringify(params);
let config = {
method: 'post',
maxBodyLength: Infinity,
url: 'https://api.wangdian.cn/openapi2/trade_query.php',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data : data
};
axios(config)
.then(function (res) {
const resdata=res.data;
qf_output={'data':res.data}
});
}
2、字段接收配置
- 这里只配置了部分字段,可以根据自己的需求去具体配置


当然后续QS主动支持代码块,也可以在代码块中去配置上述配置。也可以通过定制开发去封装实现QS主动调用
四、创建采购申请单为例
对应的接口文档:https://open.wangdian.cn/qyb/open/apidoc/doc?path=purchase_apply_push.php

1、代码块实现
- 表单配置参考

-
这里的请求参数根据具体接口文档去修改
-
其中qf_field.{采购明细节点$$1857CBECC$$}为表格字段对应的是接口文档的采购明细节点details_list数组内容
-
goodsDetail 这块代码中的field_173486855和field_173486856分别为表格中的商家编码以及采购申请量的表格字段ID,获取方式:field_id获取方式https://exiao.yuque.com/rlf3k1/oanb79/ml0u7lgb3wa0vhiz?singleDoc# 《如何在代码块中分辨表格的不同列》

- appkey、appsecret以及sid需替换为咱们旺店通所提供的具体参数内容
const { differenceInSeconds, parse } = require('date-fns');
const crypto = require('crypto');
const axios = require('axios');
const qs = require('qs');
// 通过当前时间和起始时间计算总秒数
function calculateTotalSeconds() {
const startTime = parse('1970-01-01 08:00:00', 'yyyy-MM-dd HH:mm:ss', new Date(), { timeZone: 'Asia/Shanghai' });
const currentTime = new Date();
const seconds = differenceInSeconds(currentTime, startTime);
return seconds;
}
// 输出总秒数时间戳
const totalSeconds = calculateTotalSeconds();
const table = qf_field.{采购明细节点$$1857CBECC$$};
if(table==null){
qf_output={'data':null}
}else{
const goodsDetail = table.map(item => {
const newItem = {
"spec_no": item.field_173486855,
"num": item.field_173486856
};
return newItem;
});
// 请求参数
const params = {
appkey: 'test2-123456',
warehouse_no: qf_field.{仓库编号$$18596807C$$},
details_list:`${JSON.stringify(goodsDetail)}`,
sid: 'test2',
timestamp: totalSeconds,
};
if(params.warehouse_no==null ){
qf_output={'data':null}
}else{
// 第一步:参数排序
const sortedParams = Object.keys(params).sort().reduce((acc, key) => Object.assign(acc, { [key]: params[key] }), {});
// 第二步:处理每个参数
const processedParams = Object.keys(sortedParams).map((key) => {
let value = sortedParams[key].toString();
let length = Buffer.byteLength(value, 'utf8').toString().padStart(4, '0');
let processedValue = `${length}-${value}`;
return `${Buffer.byteLength(key, 'utf8').toString().padStart(2, '0')}-${key}:${processedValue};`;
});
// 第三步:计算 sign
const appsecret = '12345';
const signString = processedParams.join('');
const index = signString.indexOf('09-timestamp'); // 获取 "09-timestamp" 的索引位置
const semicolonIndex = signString.indexOf(';', index); // 获取在 "09-timestamp" 后面的分号的索引位置
const result = signString.slice(0, semicolonIndex) + signString.slice(semicolonIndex + 1); // 切割字符串,并删除找到的分号
const sign = crypto.createHash('md5').update(`${result}${appsecret}`).digest('hex');
// 添加 sign 参数
params.sign = sign;
let data = qs.stringify(params);
let config = {
method: 'post',
maxBodyLength: Infinity,
url: 'https://api.wangdian.cn/openapi2/purchase_apply_push.php',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data : data
};
axios(config)
.then(function (res) {
const resdata=res.data;
qf_output={resdata}
});
}
}
字段接收配置
- 上述配置完成后,点击代码块,会生成对应的采购单号自动填入对应字段中


2、webhook实现
-
通过代码块计算签名之后,提交数据进入流程触发webhook去完成调用接口
-
注意点:生成签名后,需要在5分钟之内提交数据触发webhook,因为旺店通企业版API服务端允许请求最大时间误差为5min

实现路径
- 代码块签名
const { differenceInSeconds, parse } = require('date-fns');
const crypto = require('crypto');
const axios = require('axios');
const qs = require('qs');
// 通过当前时间和起始时间计算总秒数
function calculateTotalSeconds() {
const startTime = parse('1970-01-01 08:00:00', 'yyyy-MM-dd HH:mm:ss', new Date(), { timeZone: 'Asia/Shanghai' });
const currentTime = new Date();
const seconds = differenceInSeconds(currentTime, startTime);
return seconds;
}
// 输出总秒数时间戳
const totalSeconds = calculateTotalSeconds();
const table =qf_field.{采购明细节点$$18598DCCD$$};
const goodsDetail = table.map(item => {
const newItem = {
"spec_no": item.field_173648656,
"num": item.field_173648657
};
return newItem;
});
// 请求参数
const params = {
appkey: 'test2-123456',
warehouse_no: qf_field.{仓库编号$$18598DCC2$$},
details_list:`${JSON.stringify(goodsDetail)}`,
sid: 'test2',
timestamp: totalSeconds,
};
if(params.warehouse_no==null ){
qf_output={'data':null}
}else{
// 第一步:参数排序
const sortedParams = Object.keys(params).sort().reduce((acc, key) => Object.assign(acc, { [key]: params[key] }), {});
// 第二步:处理每个参数
const processedParams = Object.keys(sortedParams).map((key) => {
let value = sortedParams[key].toString();
let length = Buffer.byteLength(value, 'utf8').toString().padStart(4, '0');
let processedValue = `${length}-${value}`;
return `${Buffer.byteLength(key, 'utf8').toString().padStart(2, '0')}-${key}:${processedValue};`;
});
// 第三步:计算 sign
const appsecret = '12345';
const signString = processedParams.join('');
const index = signString.indexOf('09-timestamp'); // 获取 "09-timestamp" 的索引位置
const semicolonIndex = signString.indexOf(';', index); // 获取在 "09-timestamp" 后面的分号的索引位置
const result = signString.slice(0, semicolonIndex) + signString.slice(semicolonIndex + 1); // 切割字符串,并删除找到的分号
const sign = crypto.createHash('md5').update(`${result}${appsecret}`).digest('hex');
// 添加 sign 参数
params.sign = sign;
qf_output={params}
}
字段接收配置



- webhook配置

