前端支付开发实战指南

前端支付开发实战指南
寒霜前端支付开发实战指南
支付功能是很多Web应用的核心功能。本文将详细介绍支付宝PC支付、微信PC扫码支付和微信公众号H5支付的前端实现方案。
1. 支付宝电脑支付
1.1 开发文档
1.2 实现流程
- 封装请求后台的接口
- 后端返回支付宝生成的form表单
- 前端打开表单,用户扫码支付
1.3 封装支付接口
import axios from 'axios';
import qs from 'qs';
import store from '@/store';
/**
* 支付宝支付
* @param {Number} goodsId - 商品ID,1为自定义
* @param {Number} quantity - 钻石数量
*/
export function zhifubao(goodsId, quantity) {
return axios.post(
// 后端支付接口
store.state.baseURL + "/sjsvoice/c/pms/create/zfb/pay",
qs.stringify({
goodsId: goodsId,
quantity: quantity,
}),
{
headers: {
"user-id": store.state.userInfo.userId,
"user-token": store.state.userToken,
channel: "pc",
"app-type": "3",
},
}
);
}
1.4 调用支付接口
// 引入支付接口
import { zhifubao } from '@/api/payment';
export default {
data() {
return {
html: '', // 存储返回的form表单
showPayPage: false
}
},
methods: {
handlePay(goodsId, quantity) {
zhifubao(goodsId, quantity)
.then((res) => {
console.log(res);
if (res.data) {
this.showPayPage = true;
// 提前预留一个dom元素区域来展示form表单
this.html = res.data;
this.$nextTick(() => {
// 直接提交form表单,跳转到支付宝支付页面
document.forms[0].submit();
});
}
})
.catch((err) => {
console.log(err);
this.$message.error('支付失败,请重试');
});
}
}
}
1.5 支付宝返回的表单数据
<form
name="punchout_form"
method="post"
action="https://openapi.alipay.com/gateway.do?charset=utf-8&method=alipay.trade.page.pay&sign=xxx&return_url=https://xxx.com/Recharge¬ify_url=https://xxx.com/callback&version=1.0&app_id=xxx&sign_type=RSA2×tamp=2021-12-31+17%3A45%3A19&alipay_sdk=alipay-sdk-java-dynamicVersionNo&format=json"
>
<input
type="hidden"
name="biz_content"
value='{"out_trade_no":"zfb1041640943919","total_amount":6,"subject":"支付充值","product_code":"FAST_INSTANT_TRADE_PAY"}'
/>
<input type="submit" value="立即支付" style="display:none" />
</form>
<script>
document.forms[0].submit();
</script>
1.6 注意事项
⚠️ 重要:扫码支付成功后,后端会控制回跳地址,这个需要和后端提前协商好
2. 微信PC扫码支付
2.1 准备工作
首先需要准备一个DOM容器,用于展示支付二维码。推荐使用vue-qr组件(支持在二维码中间添加logo):
<template>
<!-- 微信支付二维码弹窗 -->
<el-dialog :visible.sync="dialogVisible" title="微信扫码支付">
<vue-qr
:logoSrc="logo"
:text="codeURL"
:size="200"
></vue-qr>
<div slot="footer" class="dialog-footer">
<p>请使用微信扫描二维码完成支付</p>
</div>
</el-dialog>
</template>
<script>
import vueQr from 'vue-qr';
export default {
components: {
vueQr
},
data() {
return {
dialogVisible: false,
codeURL: '',
logo: require('@/assets/logo.png'), // 二维码中间的logo
timer: null // 支付结果查询定时器
}
}
}
</script>
2.2 封装微信支付接口
// 请求后台接口,获取支付二维码URL
export function weixin(goodsId, quantity) {
return axios.post(
url,
qs.stringify({
goodsId: goodsId,
quantity: quantity,
}),
{
headers: {}
}
);
}
// 查询微信支付结果
export function searchWxResult(outTradeNo) {
return axios.post(
'/sjsvoice/h5/sys/wx/pay/result',
qs.stringify({
outTradeNo: outTradeNo
})
);
}
2.3 完整支付流程
export default {
methods: {
handleWxPay(goodsId, quantity) {
let that = this;
weixin(goodsId, quantity)
.then((res) => {
console.log(res);
if (res.data.code == 0) {
this.showPayPage = false;
this.payMethod = "weixin";
this.dialogVisible = true;
this.codeURL = res.data.data.codeUrl; // 获取二维码URL
// 启动定时器,轮询查询支付结果
// tradeStatus: -1.失败 0.充值中 1.成功
this.timer = setInterval(function () {
searchWxResult(res.data.data.outTradeNo)
.then((res) => {
console.log(res);
if (res.data.data.tradeStatus == 1) {
// 支付成功
clearInterval(that.timer);
that.dialogVisible = false;
that.$message.success('支付成功');
that.load(); // 刷新数据
} else if (res.data.data.tradeStatus == -1) {
// 支付失败
clearInterval(that.timer);
that.dialogVisible = false;
that.$message.error('支付失败');
}
})
.catch((err) => {
clearInterval(that.timer);
console.log(err);
that.$message.error('查询支付结果失败');
});
}, 1000); // 每1秒查询一次
}
})
.catch((err) => {
console.log(err);
this.$message.error('发起支付失败');
});
},
// 关闭弹窗时清除定时器
handleCloseDialog() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
this.dialogVisible = false;
}
},
beforeDestroy() {
// 组件销毁时清除定时器
if (this.timer) {
clearInterval(this.timer);
}
}
}
2.4 注意事项
⚠️ 轮询策略:微信扫码支付需要前端轮询查询支付结果,建议间隔1-2秒,避免频繁请求服务器
3. 微信公众号H5支付
3.1 开发文档
3.2 实现流程
- 用户打开页面,请求业务接口
- 调用微信授权接口,获取code
- 通过code获取用户openid
- 调用微信支付接口
- 使用WeixinJSBridge唤起支付
3.3 引入微信JS-SDK
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
3.4 微信授权登录
// 准备页面回跳地址
let url = "https://your-domain.com/pay/callback";
// 请求业务接口,获取微信授权配置
axios
.post(
"https://your-api.com/sjsvoice/h5/sys/wechat/jssdk/config/query",
qs.stringify({ url: url })
)
.then((res) => {
if (res.data.code == 0) {
// 对回跳地址进行encodeURIComponent编码
// 如果需要传递参数,可以先拼接好再编码
let encodedUrl = encodeURIComponent(res.data.data.jsSdkConfig.url);
// 配置微信JS-SDK
wx.config({
debug: false, // 开启调试模式
appId: "your-appId", // 必填,公众号的唯一标识
timestamp: res.data.data.jsSdkConfig.timestamp, // 必填,生成签名的时间戳
nonceStr: res.data.data.jsSdkConfig.nonceStr, // 必填,生成签名的随机串
signature: res.data.data.jsSdkConfig.signature, // 必填,签名
jsApiList: ["chooseWXPay"], // 必填,需要使用的JS接口列表
});
// 跳转到微信授权页面
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx2d5c3d16592f8f3f&redirect_uri=${encodedUrl}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`;
} else {
this.$toast(res.data.msg);
}
})
.catch((err) => {
console.log(err);
});
⚠️ 注意:wx.config在回跳地址时不会输出自己携带的参数,但确实传输过去了,可以使用alert打印调试
3.5 解析回跳参数
// 解析URL参数
let url = window.location.href;
let reg = /[?&][^?&]+=[^?&]+/g;
let arr = url.match(reg);
let params = {};
if (arr) {
arr.forEach((item) => {
let tempArr = item.substring(1).split("=");
let key = tempArr[0];
let val = tempArr[1];
params[key] = val;
});
}
console.log(params); // { code: "xxx", state: "STATE" }
3.6 获取用户openid并调起支付
// 根据code获取用户openid
axios
.post("https://your-api.com/sjsvoice/h5/sys/wechat/oauth2/access_token", qs.stringify({
code: params.code
}))
.then((res) => {
console.log(res, "获取用户Token");
if (res.data.code == 0) {
const { accessToken } = res.data.data;
this.userId = params.userID;
// 构造支付参数
const payParams = {
uid: params.userID,
goodsId: params.goodsId,
openid: accessToken.openid,
quantity: params.number,
};
// 请求支付接口
axios
.post(
"/sjsvoice/h5/sys/create/wx/pay",
qs.stringify(payParams),
{
headers: {
channel: "wxH5",
app_type: "4",
},
}
)
.then((res) => {
console.log(res, "支付结果");
// 调用微信支付
this.callWeixinPay(res.data.data);
})
.catch((err) => {
console.log(err);
this.$toast("支付失败");
});
}
})
.catch((err) => {
console.log(err);
});
3.7 调用微信支付API
callWeixinPay(payData) {
// 微信H5支付API
function onBridgeReady() {
WeixinJSBridge.invoke(
"getBrandWCPayRequest",
{
appId: payData.appId, // 公众号ID
timeStamp: payData.timeStamp, // 时间戳
nonceStr: payData.nonceStr, // 随机串
package: "prepay_id=" + payData.package, // ⚠️ 一定要注意这个形式
signType: "RSA", // 微信签名方式
paySign: payData.paySign, // 微信签名
},
function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 支付成功
// 使用以上方式判断前端返回
// 微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠
console.log("支付成功");
// 跳转到成功页面或刷新数据
window.location.href = "/pay/success";
} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
// 用户取消支付
console.log("取消支付");
} else if (res.err_msg == "get_brand_wcpay_request:fail") {
// 支付失败
console.log("支付失败");
}
}
);
}
// 检测WeixinJSBridge是否就绪
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener(
"WeixinJSBridgeReady",
onBridgeReady,
false
);
} else if (document.attachEvent) {
document.attachEvent("WeixinJSBridgeReady", onBridgeReady);
document.attachEvent("onWeixinJSBridgeReady", onBridgeReady);
}
} else {
onBridgeReady();
}
}
3.8 支付方式对比
| 特性 | 支付宝PC | 微信PC扫码 | 微信H5 |
|---|---|---|---|
| 支付方式 | 跳转支付宝 | 扫码支付 | 微信内支付 |
| 返回方式 | form表单 | 二维码URL | WeixinJSBridge |
| 结果查询 | 回跳通知 | 轮询查询 | 回调通知 |
| 适用场景 | PC浏览器 | PC浏览器 | 微信内置浏览器 |
4. 支付安全注意事项
4.1 前端安全
- ❌ 不要在前端存储支付密钥
- ❌ 不要在前端计算签名
- ✅ 所有敏感操作由后端完成
- ✅ 使用HTTPS协议
- ✅ 验证支付结果时以服务器为准
4.2 支付流程安全
// ❌ 错误:仅依赖前端回调
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 直接认为支付成功
}
// ✅ 正确:前端通知 + 后端验证
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 通知后端验证支付结果
axios.post('/api/verify/payment', {
orderId: orderId
}).then(res => {
if (res.data verified) {
// 确认支付成功
}
});
}
4.3 用户体验优化
- 支付前明确展示商品信息和金额
- 支付过程中显示加载状态
- 支付结果明确提示用户
- 提供订单查询功能
- 异常情况提供重试机制
5. 总结
本文介绍了三种常见的支付方式:
- 支付宝PC支付:通过form表单跳转
- 微信PC扫码支付:通过二维码展示+轮询查询
- 微信H5支付:通过微信授权+JSAPI支付
每种方式都有其适用场景,在实际项目中需要根据用户环境选择合适的支付方式。记住,支付安全永远是第一位的,前端只是支付流程的一部分,真正的验证和资金操作都应该在后端完成。