跳到主要内容

轻流 x 旺店通(企业版)

应用场景

产品介绍:

旺店通是一款专门为电商量身定做的电商ERP系统软件,致力于提供专业的电子商务ERP解决方案,能全面覆盖线上电商、网店以及线下实体店进销存管理及wms仓储管理系统等

官网:https://www.wangdian.cn/

API文档:https://open.wangdian.cn/qyb/open/apidoc

实现目标:

通过轻流应用表单实现与旺店通(企业版)进行对接

Ps:

1、目前批量获取数据暂时只能存放在表格中并且只能获取部分数据,体验上会有点不好。

不过后续产品QS主动支持代码块处理即可实现定时轮训获取。如果不想要放在表格中,可以采用定制开发来实现

2、部分接口暂时无法通过轻流实现,原因是由于数组中需要嵌套数组,但可以使用代码块进行数据处理,望知晓,例如:创建原始订单(https://open.wangdian.cn/qyb/open/apidoc/doc?path=trade_push.php

落地方案

原文画板

图文/视频演示

帮助文档

一、旺店通(企业版)接入

image.png

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

image.png

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

image.png

注意点:

接口规范:https://open.wangdian.cn/qyb/open/guide?path=guide_jkgf

  • REST风格的API,使用HTTPS协议发送POST请求调用接口。
  • HTTPS请求头类型为 Content-Type: application/x-www-form-urlencoded

image.png

二、签名算法

1、签名sign介绍

image.png

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、代码测试

image.png

三、查询订单管理为例

对应的接口文档:https://open.wangdian.cn/qyb/open/apidoc/doc?path=trade_query.php

image.png

1、代码块实现

  • 具体查询参数内容根据接口文档去填写,填写至params中

image.png

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

image.png

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、字段接收配置

  • 这里只配置了部分字段,可以根据自己的需求去具体配置

image.png

image.png

当然后续QS主动支持代码块,也可以在代码块中去配置上述配置。也可以通过定制开发去封装实现QS主动调用

四、创建采购申请单为例

对应的接口文档:https://open.wangdian.cn/qyb/open/apidoc/doc?path=purchase_apply_push.php

image.png

1、代码块实现

  • 表单配置参考

image.png

  • 这里的请求参数根据具体接口文档去修改

  • 其中qf_field.{采购明细节点$$1857CBECC$$}为表格字段对应的是接口文档的采购明细节点details_list数组内容

  • goodsDetail 这块代码中的field_173486855和field_173486856分别为表格中的商家编码以及采购申请量的表格字段ID,获取方式:field_id获取方式https://exiao.yuque.com/rlf3k1/oanb79/ml0u7lgb3wa0vhiz?singleDoc# 《如何在代码块中分辨表格的不同列》

image.png

  • 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}
});
}
}
字段接收配置
  • 上述配置完成后,点击代码块,会生成对应的采购单号自动填入对应字段中

image.pngimage.png

2、webhook实现

  • 通过代码块计算签名之后,提交数据进入流程触发webhook去完成调用接口

  • 注意点:生成签名后,需要在5分钟之内提交数据触发webhook,因为旺店通企业版API服务端允许请求最大时间误差为5min

image.png

实现路径
  • 代码块签名
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}

}
字段接收配置

image.png

image.png

image.png

  • webhook配置

image.png

image.png