❓每次打款前,你都要核对三四遍卡号金额,生怕输错账户,打错款目
❓繁杂的付款工作占用大部分的工作时间,财务产出的整体价值不高
❓依靠人工手动从银端获取回款,不仅耗时长,而且效率低
用轻流,这些烦恼都可以统统甩开!
通过轻流丰富的 Open API 接口和强大的 Q-Linker、Webhook数据传输能力,直接在轻流系统内就可以实现支付宝收款/退款啦!
口说无凭,一起来看看实现效果吧
实现步骤
⚠️Tips:因步骤十分详尽所以较长,耐心些,按步骤操作就可以实现视频中的同款效果
免费试用轻流:www.qingflow.com
一、支付宝扫码支付 教程
1、准备工作
- 点击登录 https://open.alipay.com/
- 进入后台,创建网页应用
- 绑定产品——【当面付】,并进行开通
- 接口加签方式设置
2、获取调用接口所需具体公共参数
以扫码付接口为例:https://opendocs.alipay.com/open/f540afd8_alipay.trade.precreate?pathHash=d3c84596&ref=api&scene=19
- appid:应用ID
其余参数根据文档要求来进行获取
3、表单设计
1. 支付宝订单预创建并支付
其中【生成支付链接】与【确认支付状态】为代码块,【生成支付二维码】为Q-Linker字段
1.1. 生成支付链接
1.1.1. 代码注释
具体调用接口文档:https://opendocs.alipay.com/open/f540afd8_alipay.trade.precreate?pathHash=d3c84596&ref=api&scene=19
⚠️注意:所生成的二维码有效时间为2小时
app_id:需要替换成具体调用的应用ID
qf_field.{商品列表信息$$17B54DD0C$$},为表单中商品列表信息表格字段
其中以 field_169208787 为例,表示商品列表信息表格中商品数量子字段 ID
field_id 获取方式 https://exiao.yuque.com/rlf3k1/oanb79/ml0u7lgb3wa0vhiz?singleDoc#
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****DANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCSDlNKcCP4F68iQFV4ytD5yQ0BI4med+ol2rKOKG3Zy8QKJlmUqMSaOM6zBIGknf8BsWwFuj48MC7KPuRusENpk5A9FJ01qr+yQo1JuAvm+VMTqXPHfDAYhl0HW6vYuwJ/i35Utca+r3q9nn6QmLp9mtigFLj3kwWewz5mEkPacx5p4XhXtUcWCpKJ+Fluu8Y3x0/SoodoIu+7w/gjFpFi4d/eXBsAb/7uS2ko8Q3i02RilR7SSqBHJ9WkHTDlDia2/JHmwBeqgLW60ipLG9cUpLrE2DIVPUw5Sf94FQKfJOju7HWDlDM/WvhlLhB8cRhsaV9BOlafdaoGPYMKnCRDAgMBAAECggEAKRa4nddgdxoKngMlSH8ePTcvXmOmApvPlmipFM8Q4FWvx/8z8ltsO8rBc6FP64S7SbmlHxNfEMWpVCJPV0hlHp1x1Y0oEQIBPd/1KYkKaCc6FOz18mfgi/W41CoY9TbfJAyVNLWSvPBXAmNFTd55kH5wdNB/nL6StTBOLJ700c5h5vjhcY0rZrEhmD4QChlmghHXSeFTyAHaLy1hUKVhIACWQNnSSwlg3WOjS4ddonwdXRi3L203FxlmyJBM1Gb3fv7KFZPnNjLfU8szuvL8SzJSLnRqfnafiA5x7ulxByst/KqoGArYWV37zOAzHj9YMckYGG6S00C8LLPwGrx0yQKBgQD/4AD1e2r1uzvbda70lLO2y0CsBXye7WYTRbYVX3u5Z8D++I3b2beebSPflS9wWWsw2urYj8AKSzGiUuVh8/Gy3hcWZ2GyPdNlywe8UkmgN4BjVcj5THoW5VPi+o1FbfPitKLsL9ccXYKTBnwNTuVMyblEGzWZx3uSoBNUqHImTwKBgQCSIJbRKqZAeOq7OcgCKvnDlztSbK35H4DxuAdMI04sV3fvRs5bE4H9hUySy2mfgrCrewdQkxjmtTzrRJFHVHnU1doDH0/HMDE2ry0I0RX79esaEJAartZerqxW+8EyTgAQL3nG1BfyvH1c38kMRDbMaUhmVEK0EheefsgSU35ZzQKBgQCa8gTiopgEshrvLHaDuUCSosZI5RGwE8ZKSV6X1rhPb9rZC56r0U117FVa4TZW7G8SqJ6qAXKjSSGHUHeDN3vXetG0SWpJy1KmQ6otig73rGRcwufuvzb3gmun+V1u+8RCNmyqZdX3YVDew4B/dpU4SLed3HQ66SeVhMXv8Akf1QKBgEPuBm5iBzs9etFigQoQ9F7qdNdSUmXXMVgtFqdcWkDewJBpOBC6ttkmGHy9NvLCDGMLJFFesFq/sfwkueyQ5rn8WAbPjYSpcJddQ/AvUqr4nTxWqsbctCb7yH3/Nsat3/WmPFQj0KY03YpJNesP4vGPl+qKDSAssPIrOdLbIYeZAoGADyKnem0BQK2dTRETTcH2AlYw3E4gVZipqmge2FziMM+QKwzKUa2w0m7ReamForpwoVD/rjPnhP8kf/wtM2R8BnQD25tEN9uplZfJguHEvhDrt9xKkshntFQz0Qcwk7CiHvQH2+zOJLykcu0HU2K9oEoxTkZzsoHLAbzI7CLKF4k=
-----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,调用接口将支付链接转为支付二维码(具体接口文档:https://www.mxnzp.com/doc/detail?id=2)
- 需要点击申请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. 代码注释
具体调用接口文档:https://opendocs.alipay.com/open/02ekfh?pathHash=925e7dfc&ref=api&scene=23
app_id:需要替换成具体调用的应用ID
qf_field.{商户订单号$$17B54DD0A$$},为表单中商户订单号字段
应用私钥:只需要替换成具体调用的应用私钥
确认支付状态:
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.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****BgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCSDlNKcCP4F68iQFV4ytD5yQ0BI4med+ol2rKOKG3Zy8QKJlmUqMSaOM6zBIGknf8BsWwFuj48MC7KPuRusENpk5A9FJ01qr+yQo1JuAvm+VMTqXPHfDAYhl0HW6vYuwJ/i35Utca+r3q9nn6QmLp9mtigFLj3kwWewz5mEkPacx5p4XhXtUcWCpKJ+Fluu8Y3x0/SoodoIu+7w/gjFpFi4d/eXBsAb/7uS2ko8Q3i02RilR7SSqBHJ9WkHTDlDia2/JHmwBeqgLW60ipLG9cUpLrE2DIVPUw5Sf94FQKfJOju7HWDlDM/WvhlLhB8cRhsaV9BOlafdaoGPYMKnCRDAgMBAAECggEAKRa4nddgdxoKngMlSH8ePTcvXmOmApvPlmipFM8Q4FWvx/8z8ltsO8rBc6FP64S7SbmlHxNfEMWpVCJPV0hlHp1x1Y0oEQIBPd/1KYkKaCc6FOz18mfgi/W41CoY9TbfJAyVNLWSvPBXAmNFTd55kH5wdNB/nL6StTBOLJ700c5h5vjhcY0rZrEhmD4QChlmghHXSeFTyAHaLy1hUKVhIACWQNnSSwlg3WOjS4ddonwdXRi3L203FxlmyJBM1Gb3fv7KFZPnNjLfU8szuvL8SzJSLnRqfnafiA5x7ulxByst/KqoGArYWV37zOAzHj9YMckYGG6S00C8LLPwGrx0yQKBgQD/4AD1e2r1uzvbda70lLO2y0CsBXye7WYTRbYVX3u5Z8D++I3b2beebSPflS9wWWsw2urYj8AKSzGiUuVh8/Gy3hcWZ2GyPdNlywe8UkmgN4BjVcj5THoW5VPi+o1FbfPitKLsL9ccXYKTBnwNTuVMyblEGzWZx3uSoBNUqHImTwKBgQCSIJbRKqZAeOq7OcgCKvnDlztSbK35H4DxuAdMI04sV3fvRs5bE4H9hUySy2mfgrCrewdQkxjmtTzrRJFHVHnU1doDH0/HMDE2ry0I0RX79esaEJAartZerqxW+8EyTgAQL3nG1BfyvH1c38kMRDbMaUhmVEK0EheefsgSU35ZzQKBgQCa8gTiopgEshrvLHaDuUCSosZI5RGwE8ZKSV6X1rhPb9rZC56r0U117FVa4TZW7G8SqJ6qAXKjSSGHUHeDN3vXetG0SWpJy1KmQ6otig73rGRcwufuvzb3gmun+V1u+8RCNmyqZdX3YVDew4B/dpU4SLed3HQ66SeVhMXv8Akf1QKBgEPuBm5iBzs9etFigQoQ9F7qdNdSUmXXMVgtFqdcWkDewJBpOBC6ttkmGHy9NvLCDGMLJFFesFq/sfwkueyQ5rn8WAbPjYSpcJddQ/AvUqr4nTxWqsbctCb7yH3/Nsat3/WmPFQj0KY03YpJNesP4vGPl+qKDSAssPIrOdLbIYeZAoGADyKnem0BQK2dTRETTcH2AlYw3E4gVZipqmge2FziMM+QKwzKUa2w0m7ReamForpwoVD/rjPnhP8kf/wtM2R8BnQD25tEN9uplZfJguHEvhDrt9xKkshntFQz0Qcwk7CiHvQH2+zOJLykcu0HU2K9oEoxTkZzsoHLAbzI7CLKF4k=
-----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节点
具体接口文档:https://help.qingflow.com/help-docs?type=qingCode&docId=fc8e64656428e08a06fdf030002e2a74&parentId=
- accessToken:轻商城——拓展插件——openAPI
- 节点ID获取方式
可以调用接口来进行获取:https://help.qingflow.com/help-docs?type=qingCode&docId=0122a5876428e120070b62b8001478f4&parentId=
2. 支付宝申请订单退款
2.1. 商户订单号
- 数据关联字段
2.2. 订单总金额
2.3. 退款
2.3.1. 代码注释
具体调用接口文档:https://opendocs.alipay.com/open/02ekfk?pathHash=b45b14f7&ref=api&scene=common
⚠️注意:交易超过约定时间(签约时设置的可退款时间)的订单无法进行退款。
app_id:需要替换成具体调用的应用ID
qf_field.{商户订单号$$17B598970$$}:表单中商户订单号字段
qf_field.{退款金额$$17B598089$$}:表单中退款金额字段
qf_field.{退款原因说明$$17B59808A$$}:表单中退款原因说明字段
应用私钥:只需要替换成具体调用的应用私钥
退款:
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.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*****NBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCSDlNKcCP4F68iQFV4ytD5yQ0BI4med+ol2rKOKG3Zy8QKJlmUqMSaOM6zBIGknf8BsWwFuj48MC7KPuRusENpk5A9FJ01qr+yQo1JuAvm+VMTqXPHfDAYhl0HW6vYuwJ/i35Utca+r3q9nn6QmLp9mtigFLj3kwWewz5mEkPacx5p4XhXtUcWCpKJ+Fluu8Y3x0/SoodoIu+7w/gjFpFi4d/eXBsAb/7uS2ko8Q3i02RilR7SSqBHJ9WkHTDlDia2/JHmwBeqgLW60ipLG9cUpLrE2DIVPUw5Sf94FQKfJOju7HWDlDM/WvhlLhB8cRhsaV9BOlafdaoGPYMKnCRDAgMBAAECggEAKRa4nddgdxoKngMlSH8ePTcvXmOmApvPlmipFM8Q4FWvx/8z8ltsO8rBc6FP64S7SbmlHxNfEMWpVCJPV0hlHp1x1Y0oEQIBPd/1KYkKaCc6FOz18mfgi/W41CoY9TbfJAyVNLWSvPBXAmNFTd55kH5wdNB/nL6StTBOLJ700c5h5vjhcY0rZrEhmD4QChlmghHXSeFTyAHaLy1hUKVhIACWQNnSSwlg3WOjS4ddonwdXRi3L203FxlmyJBM1Gb3fv7KFZPnNjLfU8szuvL8SzJSLnRqfnafiA5x7ulxByst/KqoGArYWV37zOAzHj9YMckYGG6S00C8LLPwGrx0yQKBgQD/4AD1e2r1uzvbda70lLO2y0CsBXye7WYTRbYVX3u5Z8D++I3b2beebSPflS9wWWsw2urYj8AKSzGiUuVh8/Gy3hcWZ2GyPdNlywe8UkmgN4BjVcj5THoW5VPi+o1FbfPitKLsL9ccXYKTBnwNTuVMyblEGzWZx3uSoBNUqHImTwKBgQCSIJbRKqZAeOq7OcgCKvnDlztSbK35H4DxuAdMI04sV3fvRs5bE4H9hUySy2mfgrCrewdQkxjmtTzrRJFHVHnU1doDH0/HMDE2ry0I0RX79esaEJAartZerqxW+8EyTgAQL3nG1BfyvH1c38kMRDbMaUhmVEK0EheefsgSU35ZzQKBgQCa8gTiopgEshrvLHaDuUCSosZI5RGwE8ZKSV6X1rhPb9rZC56r0U117FVa4TZW7G8SqJ6qAXKjSSGHUHeDN3vXetG0SWpJy1KmQ6otig73rGRcwufuvzb3gmun+V1u+8RCNmyqZdX3YVDew4B/dpU4SLed3HQ66SeVhMXv8Akf1QKBgEPuBm5iBzs9etFigQoQ9F7qdNdSUmXXMVgtFqdcWkDewJBpOBC6ttkmGHy9NvLCDGMLJFFesFq/sfwkueyQ5rn8WAbPjYSpcJddQ/AvUqr4nTxWqsbctCb7yH3/Nsat3/WmPFQj0KY03YpJNesP4vGPl+qKDSAssPIrOdLbIYeZAoGADyKnem0BQK2dTRETTcH2AlYw3E4gVZipqmge2FziMM+QKwzKUa2w0m7ReamForpwoVD/rjPnhP8kf/wtM2R8BnQD25tEN9uplZfJguHEvhDrt9xKkshntFQz0Qcwk7CiHvQH2+zOJLykcu0HU2K9oEoxTkZzsoHLAbzI7CLKF4k=
-----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. 字段接收配置