跳到主要内容

如何实现支付宝扫码支付?

一、场景

使用轻流系统实现与支付宝进行支付操作,该场景是实现支付宝扫码付的场景。

二、实现效果

1、发起支付订单

image.png

image.png

2、确认支付订单并进行扫码支付

image.png

image.png

image.png

3、确认订单支付状态

image.png

企业微信截图_385a5a6c-65b1-419a-bce6-aa690659cc83.png

4、退款申请

企业微信截图_ca8a7b66-7441-4eac-9ad3-a40b3fe5a5c6.png

企业微信截图_0c692d39-78e1-489f-b4e2-28e5a08d5a3c.png

三、具体配置

1、准备工作

image.png

  • 进行后台,创建网页应用

image.png

  • 绑定产品——【当面付】,并进行开通

image.png

  • 接口加签方式设置

image.png

2、获取调用接口所需具体公共参数

以扫码付接口为例:

https://opendocs.alipay.com/open/f540afd8_alipay.trade.precreate?pathHash=d3c84596&ref=api&scene=19

image.png

  • appid:应用ID

image.png

其余参数根据文档要求来进行获取

3、表单设计

支付宝订单预创建并支付

image.png

image.png

其中【生成支付链接】与【确认支付状态】为代码块

生成支付链接
代码注释

具体调用接口文档:

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# 《如何在代码块中分辨表格的不同列》

image.png

qf_field.{商户订单号$$17B54DD0A$$},为表单中商户订单号字段

qf_field.{订单总金额(单位为元)$$17B54DD0B$$},为表单中订单总金额(单位为元)字段

qf_field.{订单标题$$17B44C092$$},为表单中订单标题字段

应用私钥:只需要替换成具体调用的应用私钥

image.png

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}
});
代码测试

image.png

解析配置

image.png

字段接收配置

image.png

生成支付二维码

该字段为Q_Linker,调用接口将支付链接转为支付二维码

具体接口文档:

https://www.mxnzp.com/doc/detail?id=2

  • 需要点击申请appid,去获取对应的app_id和app_srcret

image.png

Q-linker配置

image.png

image.png

  • Method:GET
  • Json path:

image.png

  • 字段接收配置

image.png

确认支付状态
代码注释

具体调用接口文档:

https://opendocs.alipay.com/open/02ekfh?pathHash=925e7dfc&ref=api&scene=23

app_id:需要替换成具体调用的应用ID

qf_field.{商户订单号$$17B54DD0A$$},为表单中商户订单号字段

应用私钥:只需要替换成具体调用的应用私钥

image.png

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}
});
代码测试

image.png

解析配置

image.png

字段接收配置

image.png

image.png

image.png

流程配置

image.png

image.png

申请人节点配置

image.png

确认支付信息并扫码支付

image.png

image.png

image.png

image.png

告知用户

image.png

webhook节点

具体接口文档:

https://help.qingflow.com/help-docs?type=qingCode&docId=fc8e64656428e08a06fdf030002e2a74&parentId=

  • accessToken:轻商城——拓展插件——openAPI

image.png

  • 节点ID获取方式

可以调用接口来进行获取:

https://help.qingflow.com/help-docs?type=qingCode&docId=0122a5876428e120070b62b8001478f4&parentId=

image.png

支付宝申请订单退款

image.png

商户订单号
  • 数据关联字段

image.png

订单总金额

image.png

退款
代码注释

具体调用接口文档:

https://opendocs.alipay.com/open/02ekfk?pathHash=b45b14f7&ref=api&scene=common

注意:交易超过约定时间(签约时设置的可退款时间)的订单无法进行退款。

app_id:需要替换成具体调用的应用ID

qf_field.{商户订单号$$17B598970$$}:表单中商户订单号字段

qf_field.{退款金额$$17B598089$$}:表单中退款金额字段

qf_field.{退款原因说明$$17B59808A$$}:表单中退款原因说明字段

应用私钥:只需要替换成具体调用的应用私钥

image.png

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}
});
解析配置

image.png

字段接收配置

image.png