❓每次打款前,你都要核对三四遍卡号金额,生怕输错账户,打错款目
❓繁杂的付款工作占用大部分的工作时间,财务产出的整体价值不高
❓依靠人工手动从银端获取回款,不仅耗时长,而且效率低
用轻流,这些烦恼都可以统统甩开!
通过轻流丰富的 Open API 接口和强大的 Q-Linker、Webhook数据传输能力,直接在轻流系统内就可以实现支付宝收款/退款、银行卡转账并查询流水和电子回单啦!
(⚠️银企直联目前仅支持中国银行)
口说无凭,一起来看看实现效果吧👇
🌟实现步骤
教程目录:
因步骤十分详尽所以较长,耐心些,按步骤操作就可以实现视频中的同款效果
(如果你还不是轻流用户,快点击👉注册👈,体验试用吧~)
支付宝扫码支付 教程
1、准备工作
- 点击登录👉支付宝开放平台👈
- 进入后台,创建网页应用
- 绑定产品——【当面付】,并进行开通
- 接口加签方式设置
2、获取调用接口所需具体公共参数
以扫码付接口为例:alipay.trade.precreate(统一收单线下交易预创建)
- appid:应用ID
3、表单设计
1. 支付宝订单预创建并支付
1.1. 生成支付链接
1.1.1. 代码注释
具体调用接口文档:alipay.trade.precreate(统一收单线下交易预创建)
⚠️注意:所生成的二维码有效时间为2小时
app_id:需要替换成具体调用的应用ID
qf_field.{商品列表信息$$17B54DD0C$$},为表单中商品列表信息表格字段
其中以 field_169208787 为例,表示商品列表信息表格中商品数量子字段 ID
field_id 获取方式 《如何在代码块中分辨表格的不同列》
qf_field.{商户订单号$$17B54DD0A$$},为表单中商户订单号字段
qf_field.{订单总金额(单位为元)$$17B54DD0B$$},为表单中订单总金额(单位为元)字段
qf_field.{订单标题$$17B44C092$$},为表单中订单标题字段
应用私钥:只需要替换成具体调用的应用私钥
生成支付链接:
const axios = require(‘axios’);
const crypto = require(‘crypto’);
const { format } = require(‘date-fns’);const currentDate = new Date();
const formattedDate = format(currentDate, ‘yyyy-MM-dd HH:mm:ss’);
// 原始数据
const data = {
“timestamp”:formattedDate,
“method”: “alipay.trade.precreate”,
“app_id”: “2021003155650***”,
“sign_type”: “RSA2”,
“version”: “1.0”,
“charset”: “UTF-8”
};
const table = qf_field.{商品列表信息$$17B54DD0C$$};const goodsDetail = table.map(item => {
const newItem = {
“quantity”: item.field_169208787,
“goods_name”: item.field_169208786,
“goods_id”: item.field_169208785,
“price”: item.field_169208788
};
return newItem;
});//将新的 goods_detail 赋值给 data 中的 biz_content
data.biz_content =`{“out_trade_no”: qf_field.{商户订单号$$17B54DD0A$$},”total_amount”:qf_field.{订单总金额(单位为元)$$17B54DD0B$$},”subject”:qf_field.{订单标题$$17B44C092$$},”goods_detail”:${JSON.stringify(goodsDetail)}}`;// 第一步:剔除sign字段
const { sign, …dataWithoutSign } = data;// 第二步:剔除值为空的参数
const filteredData = Object.fromEntries(Object.entries(dataWithoutSign).filter(([key, value]) => value !== ”));// 第三步:按键的ASCII码值递增排序
const sortedParams = Object.keys(filteredData).sort();// 第四步:将排序后的参数与其对应的值按照”参数=参数值”的格式组合起来
const concatenatedParams = sortedParams.map(key => `${key}=${filteredData[key]}`).join(‘&’);// 第五步:生成待签名字符串
const signString = concatenatedParams;// 使用私钥对待签名字符串进行加密
const privateKey = `—–BEGIN PRIVATE KEY—–
MI****DANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAA……………
—–END PRIVATE KEY—–`;const signer = crypto.createSign(‘RSA-SHA256’);
signer.update(signString, ‘utf8’);
const signature = signer.sign(privateKey);
const sig=signature.toString(‘base64’);
const encodedSig = encodeURIComponent(sig);let config = {
method: ‘get’,
maxBodyLength: Infinity,
url: `https://openapi.alipay.com/gateway.do?timestamp=${formattedDate}&method=${data.method}&app_id=${data.app_id}&sign_type=${data.sign_type}&sign=${encodedSig}&version=${data.version}&charset=${data.charset}&biz_content=${data.biz_content}`
};axios(config).then(function (res) {
qf_output={‘data’:res.data}
});
1.1.2. 代码测试
1.1.3. 解析配置
1.1.4. 字段接收配置
1.2. 生成支付二维码
该字段为Q_Linker,调用接口将支付链接转为支付二维码(具体接口文档:生成二维码)
- 需要点击申请appid,去获取对应的app_id和app_srcret
1.2.1. Q-Linker 配置
- URL:https://www.mxnzp.com/api/qrcode/create/single
- QueryParam:
- Method:GET
- Json path:
- 字段接收配置
1.3. 确认支付状态
1.3.1. 代码注释具体调用接口文档:alipay.trade.query(统一收单交易查询)
app_id:需要替换成具体调用的应用ID
qf_field.{商户订单号$$17B54DD0A$$},为表单中商户订单号字段
应用私钥:只需要替换成具体调用的应用私钥
确认支付状态:
const crypto = require(‘crypto’);
const { format } = require(‘date-fns’);const currentDate = new Date();
const formattedDate = format(currentDate, ‘yyyy-MM-dd HH:mm:ss’);
// 原始数据
const data = {
“timestamp”:formattedDate,
“method”: “alipay.trade.query”,
“app_id”: “2021003155650***”,
“sign_type”: “RSA2”,
“version”: “1.0”,
“charset”: “UTF-8”
};
data.biz_content =`{“out_trade_no”: qf_field.{商户订单号$$17B54DD0A$$}}`;
// 第一步:剔除sign字段
const { sign, …dataWithoutSign } = data;
// 第二步:剔除值为空的参数
const filteredData = Object.fromEntries(Object.entries(dataWithoutSign).filter(([key, value]) => value !== ”));// 第三步:按键的ASCII码值递增排序
const sortedParams = Object.keys(filteredData).sort();// 第四步:将排序后的参数与其对应的值按照”参数=参数值”的格式组合起来
const concatenatedParams = sortedParams.map(key => `${key}=${filteredData[key]}`).join(‘&’);// 第五步:生成待签名字符串
const signString = concatenatedParams;// 使用私钥对待签名字符串进行加密
const privateKey = `—–BEGIN PRIVATE KEY—–
MII****BgkqhkiG9w0BAQEFAASCBKcwggSjAgEAA………………….
—–END PRIVATE KEY—–`;const signer = crypto.createSign(‘RSA-SHA256’);
signer.update(signString, ‘utf8’);
const signature = signer.sign(privateKey);
const sig=signature.toString(‘base64’);
const encodedSig = encodeURIComponent(sig);
let config = {
method: ‘get’,
maxBodyLength: Infinity,
url: `https://openapi.alipay.com/gateway.do?timestamp=${formattedDate}&method=${data.method}&app_id=${data.app_id}&sign_type=${data.sign_type}&sign=${encodedSig}&version=${data.version}&charset=${data.charset}&biz_content=${data.biz_content}`
};
axios(config).then(function (res) {
qf_output={‘data’:res.data}
});
1.3.2. 代码测试
1.3.3. 解析配置
1.3.4. 字段接收配置
1.4. 流程配置
1.4.1. 申请人节点配置
1.4.2. 确认支付信息并扫码支付
1.4.3. 告知用户
1.4.4. webhook节点
- accessToken:轻商城——拓展插件——openAPI
- 节点ID获取方式
2. 支付宝申请订单退款
2.1. 商户订单号
2.2. 订单总金额
2.3. 退款
2.3.1. 代码注释具体调用接口文档:alipay.trade.refund(统一收单交易退款接口)
⚠️注意:交易超过约定时间(签约时设置的可退款时间)的订单无法进行退款。
app_id:需要替换成具体调用的应用ID
qf_field.{商户订单号$$17B598970$$}:表单中商户订单号字段
qf_field.{退款金额$$17B598089$$}:表单中退款金额字段
qf_field.{退款原因说明$$17B59808A$$}:表单中退款原因说明字段
应用私钥:只需要替换成具体调用的应用私钥
退款:
const crypto = require(‘crypto’);
const { format } = require(‘date-fns’);const currentDate = new Date();
const formattedDate = format(currentDate, ‘yyyy-MM-dd HH:mm:ss’);
// 原始数据
const data = {
“timestamp”:formattedDate,
“method”: “alipay.trade.refund”,
“app_id”: “2021003155650***”,
“sign_type”: “RSA2”,
“version”: “1.0”,
“charset”: “UTF-8”
};
data.biz_content =`{“out_trade_no”:qf_field.{商户订单号$$17B598970$$},”refund_amount”:qf_field.{退款金额$$17B598089$$},”refund_reason”:qf_field.{退款原因说明$$17B59808A$$}}`;
// 第一步:剔除sign字段
const { sign, …dataWithoutSign } = data;
// 第二步:剔除值为空的参数
const filteredData = Object.fromEntries(Object.entries(dataWithoutSign).filter(([key, value]) => value !== ”));// 第三步:按键的ASCII码值递增排序
const sortedParams = Object.keys(filteredData).sort();// 第四步:将排序后的参数与其对应的值按照”参数=参数值”的格式组合起来
const concatenatedParams = sortedParams.map(key => `${key}=${filteredData[key]}`).join(‘&’);// 第五步:生成待签名字符串
const signString = concatenatedParams;// 使用私钥对待签名字符串进行加密
const privateKey = `—–BEGIN PRIVATE KEY—–
MII*****NBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAA……………………
—–END PRIVATE KEY—–`;const signer = crypto.createSign(‘RSA-SHA256’);
signer.update(signString, ‘utf8’);
const signature = signer.sign(privateKey);
const sig=signature.toString(‘base64’);
const encodedSig = encodeURIComponent(sig);
let config = {
method: ‘get’,
maxBodyLength: Infinity,
url: `https://openapi.alipay.com/gateway.do?timestamp=${formattedDate}&method=${data.method}&app_id=${data.app_id}&sign_type=${data.sign_type}&sign=${encodedSig}&version=${data.version}&charset=${data.charset}&biz_content=${data.biz_content}`
};axios(config).then(function (res) {
qf_output={‘data’:res.data}});
2.3.2. 解析配置
2.3.3. 字段接收配置
银企直联(中国银行)教程
在轻流实现中国银行的公对公转账,公对私转账,查询流水,获取电子回执单功能
1、落地方案
需要二次开发,由中间件根据中国银行银企对接接口规范要求,封装以下四个接口:
-
- 公对公转账
- 公对私转账
- 查询流水
- 电子回单查询
1.1 转账
转账效果有两种:直接转出不提示,需要在中国银行app上确认,两种方式需要在银行柜台做变更
📚方案简述:
-
- 在轻流发起转账,通过webhook携带三个参数访问中间件;
- 中间件根据参数访问对应的前置机接口,前置机执行对应操作将转账消息加入银行队列
- 根据转账的表现,银行执行转账操作时,会给中国银行app发送确认消息(或者直接转账成功,具体表现需要银行柜台进行修改)
1.2 查询流水
📚方案简述:
-
- 查询银行流水为定时任务,根据业务需要可设置多久拉取一次
- 拉取昨天和今天的流水数据,并在中间件解析成符合轻流要求的格式
- 中间件通过QS被动将数据推送至轻流应用中,并比对更新
1.3 电子回单查询
📚方案简述:
- 中间件访问回单下载接口后,前置机返回回单文件的名称
- 银行生成请求时间段内的回单,回单为PDF文件,统一放在压缩文件内。
- 中间件轮询是否已经生成回单文件,生成后将文件上传至对象存储服务,并返回文件的路径;
- 中间件将返回的文件路径推送至轻流,并匹配至轻流上传附件字段。
🎉最终效果
- 在轻流实现转账服务
- 可在轻流应用中查询到流水和电子回单
⚠️备注
- 银企直联属于银行系统,每年需要付款使用
- 银行会提供前置机架构,所有对银行的请求都会先到前置机处理
- 只有Windows的系统,不能云部署
- 访问中国银行银企对接接口需要先签到,签到有效期为15分钟,可缓存登录状态