超详细教程!手把手教你如何在轻流内实现支付宝扫码支付

❓每次打款前,你都要核对三四遍卡号金额,生怕输错账户,打错款目

❓繁杂的付款工作占用大部分的工作时间,财务产出的整体价值不高

❓依靠人工手动从银端获取回款,不仅耗时长,而且效率低

用轻流,这些烦恼都可以统统甩开!

通过轻流丰富的 Open API 接口和强大的 Q-Linker、Webhook数据传输能力,直接在轻流系统内就可以实现支付宝收款/退款啦!

口说无凭,一起来看看实现效果吧

00:45

实现步骤

⚠️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 配置

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