【微信支付】商品支付流程实例

微信支付流程实例

0.准备

三种支付方式的流程梳理可见:微信支付三种常见支付方式流程梳理

微信支付准备过程中的重要参数

  1. 选择【接入模式】
  2. 参数申请:
    • 【AppId】
    • 【商户号 mchid】
    • 完成以上两项参数的申请后,还需要绑定 AppId 与 mchid
  3. 配置【API key】,即 API v3 密钥,用于平台证书的解密、回调信息解密等
  4. 配置商户证书

以上操作均由平台完成,与实际开发步骤无关,但开发过程中可能会使用到这些参数

1. Native 方式

1.1 接入前准备

要接入 Native 方式支付,需要完成第一节中的全部4个步骤。

1.2 Native 方式流程图

在这里插入图片描述
接下来我们就按步骤一步步进行。

1.3 开发实现
1. 前期准备
  1. 首先我们要拿到 AppId,这是我们后续开发过程中的一个重要参数。但这个参数一般不会由前端获取,而是后端获取后,向前端提供一个拿取的接口。所以这里我们直接调用接口拿到即可。

    // 获取微信appId
    async getWxAppId() {const res = await wxModel.getWxAppId({func: 'getWxAppId',id: 2,});this.appId = res.result.appId;
    },
    
  2. 接下来,获取本平台订单的相关信息展示在页面中,另外,页面的设计必须要遵守微信 Native 方式的设计要求!

    JS

    // 获取订单信息
    async prePlaceOrder() {const res = await publicModel.xxxxxxxxx({func: 'xxxxxxxxx',});this.orderInfo = { ...res.result };
    },
    

    HTML

    <div class="member-pay"><div class="desc"><div class="left"><span>有效期至:</span><span>金额:</span><span>收款方:</span><span v-if="orderInfo.firstBuy">优惠券码:</span><span>实际金额:</span></div><div class="right"><span>{{ orderInfo.expirationTimeOfMembership }}</span><span class="money"><strong>{{ (orderInfo.price || 0) / 100 }}元</strong></span><span>{{ orderInfo.payee || '' }}</span><span class="code" v-if="orderInfo.firstBuy"><el-inputv-model="orderInfo.couponCode"placeholder="请输入优惠券码"size="medium"clearable:disabled="orderInfo.lockCouponCode"style="width: 232px; height: 38px"></el-input><spanclass="coupon-code":class="{ lockCode: orderInfo.lockCouponCode }"@click="validateCouponCode">校验</span></span><span class="money">{{ (orderInfo.finallyVipPrice || 0) / 100 }}元</span></div></div><div class="code-img"><imgv-show="!showQR"src="xxx"/><VueQrv-cloakv-show="showQR":correct-level="3":text="wechatPayUrl":size="198":margin="0"/></div><div class="btn"><LButtonclass="pay-btn"v-show="!showQR"tip="微信支付"type="stema"@click="placeOrder"/><LButtonclass="pay-btn"v-show="showQR"tip="我已支付"type="stema"@click="checkOrder"/></div><div class="refund-paperwork">xxxxxxxxx</div>
    </div>
    

    这里使用了一个vue的二维码插件 vue-qr 来生成微信支付的二维码。(PS: LButton是自己封装的组件,用其他组件库里的一样)

2. 具体流程实现

根据我们之前梳理的 Native 支付流程,接下来需要对用户点击微信支付按钮进行处理。

当用户点击微信支付时,前端需要调用后端接口,后端调用微信平台API,进行下单,并返回预支付交易链接 code_url。这里我们只关注前端的开发实现:

// placeOrder 方法
placeOrder() {// 请求后端接口,获取code_urlconst res = await placeOrderInterface({// 这里需要传递一些参数func: 'placeOrder',payMethod: 'NATIVE'	// NATIVE支付方式// 还可能会需要传一些业务相关的参数,例如:// coupon: xxx,  使用优惠券// category: xxx, 商品的类型// isActivity: xxx, 是否是平台活动期间的订单// ...})/*后端会返回三个重要的参数:1. uuid --- 订单在商户平台中的订单号2. code_url --- 预支付交易链接*/const { uuid, code_url } = res;// 先将这两个数据存放在组件中this.orderUuid = uuid;this.code_url = code_url;// 展示支付二维码this.showQR = true
}

接下来,需要根据我们拿到的 code_url 生成微信支付二维码,这里使用 vue-qr 插件直接生成然后展示出来即可。

同时,在二维码的下方展示一个【我已支付】按钮。

然后,根据梳理的流程,用户需要扫描二维码,在手机上进行支付操作,待用户完成操作后,可以主动点击按钮,查询订单状态,所以这里我们需要准备一个查询订单的方法:

// 调用后端接口,查询订单状态(后端实际上是调取的微信提供的查单API)
async checkOrder = () => {const res = await checkOrderInterface({func: 'xxxCheckOrder',uuid: this.orderUuid,	// 这里的uuid是商户平台内部的订单id})if(res.code === 0) {const { status } = res.result// 注意,这里的订单状态码是前后端约定的,与微信平台没有关系// 这里我们约定:0 --- 未支付;1 --- 已支付;2 --- 订单已关闭if(status === 0) {// 未支付时,输出提示,但不要关闭弹窗,因为此时并不是真的确定了用户没有支付成功// 如果用户想要关闭弹窗重新支付,需要点击【我已支付】下方的【支付遇到问题...】按钮来关闭弹窗this.$message({message: '订单未支付',type: 'warning',});} else if (status === 1) {// 已支付时,首先输出提示,然后关闭弹窗this.$message({message: this.orderInfo.firstBuy ? '订单已支付' : '续费成功',type: 'success',});this.modalCheckOrder = false;// 后续根据业务需求进行下一步处理...} else if (status === 2) {// 订单已关闭时,输出提示,同样不要关闭弹窗this.$message({message: '订单已关闭',type: 'warning',});}} else {// 错误处理...}
}

这样,就完成了一个 Native 方式的简单 demo。在 Native 方式中,很多工作都是由后端完成的,前端并没有承担很多工作量。

但在实际开发中,由于存在不同的需求,所以可能会需要对具体的支付流程进行各种修改。但 Native 方式内部的核心流程是基本上不会变动的。

2. H5 方式

2.1 接入前准备

在 H5 方式中,除了需要完成我们前面所说的4个步骤,还需要前往微信支付商户平台,开通H5支付。

2.2 H5 方式流程图

在这里插入图片描述

接下来按照流程进行实现。

另外,这里由于H5、JSAPI支付一般是针对移动端,所以后面的具体实现中,都是在 React 中进行的,当然也可以在其它框架中实现,其核心流程是不变的。

2.3 开发实现
1. 前期准备

后端需要准备好 appId 等参数,在调取 H5 支付API时使用。

首先同样需要拿到平台内部的订单数据,展示在页面中:

const index = () => {const [orderInfo, setOrderInfo] = useState({})const getOrderInfo = async () => {const { orderInfo } = await prePlaceOrderInfo()setOrderInfo(orderInfo)}useEffect(() => {getOrderInfo()}, [])...return (<div className='pay'><Form><FormItem label="商品名称" name="name">{orderInfo.spuName}</FormItem><FormItem label="金额" name="price"><span className="price">{orderInfo.price / 100}</span></FormItem><FormItem label="收款方" name="payee">xxxxx公司</FormItem>...</Form><div className='toPay'><div className='tip'>该商品一经购买xxxxxxxx</div><div className='finalPrice'>总计{orderInfo.finalPrice / 100}</div><div className='btn' onClick={toPay}>立即购买</div></div>...</div>)
}
2. 具体流程实现

根据梳理好的流程,接下来,用户点击【立即支付】按钮,商户平台前端发起请求,后端调用微信提供的 H5 下单API,生成预支付订单,并向前端返回一个H5支付中间页链接 h5_url

那么我们前端拿到 h5_url 后,还不能直接跳转,因为相比于 Native 方式,用户是在PC上扫描二维码,然后在手机上完成支付操作,并不存在后续的跳转逻辑。但在 H5 方式中则不同,用户的下单、支付、查单操作均在手机上进行,所以我们需要在用户支付完成后,让其跳转回到我们的页面中。这一点也是 Native 方式与 H5 方式的主要区别。

所以,后续我们需要向 h5_url 中添加一些参数。

const [orderUuid, setOrderUuid] = useState(0)const toPay = async () => {// 调取后端下单接口,在商户平台内部进行下单,同时后端调取微信API生成预付单const {h5_url, orderUuid, needPay} = await placeOrder({payMethod: 'MWEB',// 这里可能会有一些业务相关的参数,比如:categroyId: xxx,	// 商品类型idorderType: xxx,		// 订单类型couponId: xxx,		// 优惠券...,})// 首先把订单id存起来,后续查单时需要用到setOrderUuid(orderUuid)// 判断是否需要支付if(needPay) {// 需要支付时// 1. 准备好重定向链接,这里添加了一个标志位flag,用于标明是h5支付后跳转回来的情况const redirect_url = `${window.location.href}?flag=1&orderUuid=${orderUuid}`// 2. 向 h5_url 中添加参数const toPayUrl = `${h5_url}&redirect_url=${encodeURIComponent(redirect_url)}&date=${Date.now()}`// 3. 跳转支付中间页window.location.href = toPayUrl} else {// 不需要支付时,直接进行下一步操作即可...}
}

接下来,用户跳转支付中间页,调起微信支付,进行支付操作,此时订单可能处于三种状态:

  1. 未完成/支付失败
  2. 已完成
  3. 已关闭

无论处于哪种状态,一旦用户完成操作后,都会跳转至我们指定的 redirect_url,即,回到我们的前端页面。

接下来的操作,微信的官方文档中给出了标准:

由于设置 redirect_url 后,回跳指定页面的操作可能发生在:

  • 微信支付中间页调起微信收银台后超过5秒。
  • 用户点击“取消支付”或支付完成后点击“完成”按钮。因此无法保证页面回跳时,支付流程已结束,所以商户设置的redirect_url地址不能自动执行查单操作,应让用户去点击按钮触发查单操作。

所以我们需要在跳转回来时弹出一个弹框,供用户主动触发查单操作:

首先准备好弹框,这里是直接用的组件库里弹框

<Dialogid="checkOrderDia"title="订单支付"showCancelButtonshowConfirmButtoncloseOnClickOverlay={false}cancelButtonText="支付失败,重新支付"confirmButtonText="我已支付"show={modalCheckOrder}onCancel={repayment}onConfirm={checkOrder}
><div style={{ textAlign: "center" }}>请确认微信支付是否已经完成</div>
</Dialog>

接下来,我们需要准备跳转回来后弹框的逻辑,由于是跳转回来的,所以直接加在页面加载的时候就可以了:

// 控制弹框显隐
const [modalCheckOrder, setModalCheckOrder] = useState(false)// 从路径中获取参数的方法
const getQuery = (url = window.location.search, key?: string) => {var query = {}var locationSearch = url || window.location.searchif (locationSearch) {locationSearch = locationSearch.replace('?', '')var params = locationSearch.split('&')params.forEach((param) => {var keyValue = param.split('=')if(keyValue[0])query[keyValue[0]] = decodeURIComponent(keyValue[1])})}return key ? query[key] : query
}useEffect(() => {getOrderInfo()// 1. 从链接中获取参数const searchUrl = window.location.href.split("?")[1]const flag = getQuery(searchUrl, 'flag')const orderUuid = getQuery(searchUrl, 'orderUuid')// 2. 判断是否是跳转回来的情况if(flag) {// 此时是从h5支付跳转回来的情况,更新订单idif(orderUuid) {setOrderUuid(orderUuid)}// 弹框setModalCheckOrder(true)}
}, [])

这样就完成了弹框的展示,但我们还需要准备两个处理函数,分别对应用户点击【我已支付】和【支付失败,重新支付】按钮的情况:

// 点击我已支付时,调取接口查单
const checkOrder = async () => {const res = await checkOrderInterface({ uuid: orderUuid})// 这里的订单状态码是前后端共同约定的,与微信平台无关// 比如这里:0 --- 订单未支付;1 --- 订单已完成;2 --- 订单支付失败if(res.status == 0) {// 订单未支付时,输出提示this.showToast({ type: 'error', content: '订单未支付'})} else if(res.status == 2) {// 订单支付失败时,同样,输出提示this.showToast({ type: 'error', content: '订单支付失败'})} else if(res.status == 1) {// 订单支付成功,此时输出提示,然后进行下一步处理this.showToast({ type: 'success', content: '订单支付成功'})...}
}// 点击支付失败重新支付
const repayment = () => {setModalCheckOrder(false)// 这里可以根据业务需求进行不同的处理,一般是重新进入支付页面,或者退出但是保留订单为未支付状态...
}

3. JSAPI 方式

3.1 接入前准备

在 JSAPI 方式中,除了需要完成选择接入模式、申请参数以外,还需要进入微信支付商户平台进行相关配置。

  1. 设置支付授权目录
  2. 设置授权域名

具体的设置要求可见:微信JSAPI方式接入前准备

3.2 JSAPI 方式流程图

在这里插入图片描述

3.3 开发实现
1. 前期准备

与 Native、H5 方式不同,使用 JSAPI 方式时,我们需要提前准备好两个模块:

  • 微信授权模块 — 获取微信授权码
  • 微信支付模块

为什么需要这两个模块?

因为在上面的接入前准备中,我们也提到过,需要配置支付授权目录以及授权域名。但是考虑下面这种情况:商户分为多个端和平台,各端中均需要支持微信支付,那么我们应该如何配置这两项呢?不可能为每一个端的域名都配置一次,所以,我们最好的解决方法就是封装两个独立的模块,拥有独立的域名,拿着这两个模块的域名进行配置,然后各端需要进行微信授权、微信支付时,直接带参数访问这两个模块的域名,然后在这两个模块中实现授权即可。

下面对这两个模块的实现进行说明:

1.1 微信授权模块

由于模块中的内容并不多,所以放在一个 .html 文件中即可。

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>微信授权</title></head><body><script>...</script></body></html>

接下来首先准备好一个对象,用于存放一系列从路由中获取的数据以及方法

const wechatAuth = {urlParams: {},...,
}

然后,由于我们从其它页面跳转至该模块时,会携带一些参数,所以我们这里需要一个从url中获取参数的方法:

const wechatAuth = {urlParams: {},getUrlParams: function() {// 先用 location.search 获取url中的参数字符串,例如:'?a=xx&b=xx&c=cc&...'// 并用 substring 处理掉最前面的'?'let paramStr = loction.search.substring(1)let keyValueList = paramStr.split('&')	// 将参数字符串根据&分割得到一个键值对数组// 处理参数数组中的各键值对for (let i = 0; i < keyValueList.length; i++) {// 找到键值对中'='的位置let equalIndex = keyValueList[i].indexOf('=')if(equalIndex === -1){// 找不到等号位置时,直接进行下一轮循环,去处理下一对键值对continue}// 找到等号后,只需要将键值对转换为对象属性即可// 等号前的为keylet key = keyValueList[i].substring(0, equalIndex)// 等号后的为value,但需要注意,我们从其它页面中携带参数跳转进来时,参数是经过了 urlencode 处理的!let encodedValue = keyValueList[i].substring(equalIndex + 1)let decodedValue = decodeURIComponent(encodedValue)wechatAuth.urlParams[key] = decodedValue}}
}// 进入模块时,从路径上获取参数
wechatAuth.getUrlParams();

后续,我们需要跳转微信授权页面完成授权操作,但是跳转时我们同样需要在路径中携带参数,所以这里先准备一个向路径中添加参数的方法:

const wechatAuth = {...,appendParamsToUrl: function(url, params) {if(params) {// 传入的url中可能携带有 wechat_redirect 标识,这里先将其拿出来let baseWithSearch = url.split('#')[1] === 'wechat_redirect' ? url.split('#')[0] : urllet hash = url.split('#')[1]// 处理传入的需要向url上添加的参数对象for (let key in params) {// 拿到参数值let attrValue = params[key]if(attrValue !== undefined) {// 构建键值对let newParam = key + "=" + attrValue// 检查此时的 baseWithSearch 中是否已经添加了参数if (baseWithSearch.indexOf('?') > 0) {// 若添加了参数,则后续的参数需要用 '&' 连接} else {// 若还没有添加参数,则将当前的参数作为第一个参数通过 '?' 连接到 baseWithSearch 上baseWithSearch += "?" + newParamlet hasOldParamReg = new RegExp('^' + key + '=[-%.!~*\'\(\)\\w]*', 'g')// 检查现在的 baseWithSearch 上是不是已经有与当前要添加的参数同名的旧参数if (hasOldParamReg.test(baseWithSearch)) {// 若有,则用新参数替换baseWithSearch = baseWithSearch.replace(hasOldParamReg, newParam)} else {// 没有,则直接用 '&' 连接上去baseWithSearch += "&" + newParam}}}}// 将前面取出的标识加回去if (hash === 'wechat_redirect') {url = baseWithSearch + '#' + hash;} else {url = baseWithSearch;}}return url}
}

接下来,就可以编写进行授权操作的函数了,该函数中有两种情况:

  1. 此时是完成了授权,从微信授权页中跳转回来的,当前的路径参数中有授权码code字段
  2. 此时是刚从其它页面跳转至该模块,还没有进行授权操作,即当前路径参数中没有授权码code字段

所以,我们需要根据当前路径中有没有code参数,分情况进行处理

const wechatAuth = {urlParams: {...},...,authorize: function () {// 首先拿到我们前面从路径中取出的参数const code = wechatAuth.urlParams["code"];// appId 是其它页面在微信浏览器中的唯一应用编号,是必须的!const appId = wechatAuth.urlParams["appid"];const scope = wechatAuth.urlParams["scope"];const state = wechatAuth.urlParams["state"];/*params 是我们从其它页面中跳转进入该模块时携带的各种参数由于无法保证各页面跳转时携带的参数都是统一的,所以将这些参数统统放在 params 中。同时,其它页面跳转进来时,之所以要携带这些参数,往往是为了从该模块跳转回去时使用所以我们也不需要进行处理,只需要在跳转回去时给它携带上即可*/const params = wechatAuth.urlParams["params"];// 准备好跳转的 toUrl、baseUrl 以及重定向所需的 redirect_urilet baseUrl;let redirectUri;let toUrl;// 此时是否拿到了codeif (!code) {// 情况1 --- 没有拿到code,需要跳转微信授权页,获取code// 设置 baseUrl 为微信开发平台授权链接baseUrl ="https://open.weixin.qq.com/connect/oauth2/authorize#wechat_redirect";// 设置目标链接toUrl = wechatAuth.appendParams(baseUrl, {appid: appId,     // appId// 由于现在是去微信平台授权,所以将重定向地址设置为当前模块的域名即可,完成授权后才是跳转其它页面redirect_uri: encodeURIComponent(location.href),response_type: "code",// 这些原来进入模块时携带的参数也要带上,不然等会儿跳转回来的时候参数就丢失了scope: scope,state: state,params: params,});} else {// 情况2 --- 已经拿到了code,需要跳转回其它页面// 此时需要将 baseUrl 置为我们进入该模块的页面链接,即我们在params中携带的 origin 字段const redirectParams = JSON.parse(params)const origin = redirectParams.origin// 将 redirectParams 中的 origin 字段删掉delete redirectParams.origin// 设置跳转链接,此时用我们的origin作为baseUrltoUrl = wechatAuth.appendParams(origin, {// 带上授权得到的授权码code: code,// 从其它页面进入该模块时的参数也携带上,不要丢弃了state: state,...redirectParams})}// 跳转location.href = toUrl},
}

这样,就实现了一个公共的微信授权模块。

完整代码见:公共微信授权模块demo

1.2 微信支付模块

与授权模块相同,支付模块的内容直接放在一个 .html 文件中即可

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>微信支付</title><!-- 引入axios用于发送请求 --><script src="https://web-common-resource.stem86.com/axios/axios-1.4.0.min.js"></script><!-- 引入微信依赖 --><script src="https://web-common-resource.stem86.com/wechat/weixin-1.6.0.min.js"></script><script>...</script>
</head>
<body></body>
</html>

首先我们需要引入 axios,以及微信支付的依赖包。

然后,我们需要在模块加载时,调取平台后端接口,获取平台的微信相关配置

function initWxConfig() {// 请求接口获取平台微信配置axios.post("https://api.xxx.com",{func: "wxConfig",url: window.location.href.split("?")[0], // 这里将模块的域名传递过去,用于校验},{headers: {"Content-Type": "application/json",},}).then((response) => {// 调取微信API添加微信配置信息,这些信息都是由后端调取微信API获得的wx.config({debug: false, // 是否开启调试模式appId: response.data.result.appId, // 必填,唯一应用标识signature: response.data.result.signature, // 必填,签名,即微信的认证信息timestamp: response.data.result.timestamp, // 必填,生成签名的时间戳nonceStr: response.data.result.nonceStr, // 必填,生成签名的随机串jsApiList: [// 需要调用的JS接口列表"chooseWXPay",],});}).catch((err) => {console.log(JSON.stringify(err))})
}window.onload = function() {initWxConfig()...
}

由于从其它页面跳转至该模块时,会在路径中携带参数,而最后重定向离开时,又需要将参数添加回去,所以我们需要准备一个从路径中获取参数的方法以及一个向路径中添加参数的方法

// 从url中获取指定参数
function getUrlParams(paramKey) {let paramStr = location.href.slice("?")[1];let searchParams = new URLSearchParams(paramStr);// 这里通过 entries() 方法获取 searchParams 对象的迭代器// 然后通过 fromEntries() 方法,将 searchParams 这一键值对列表转换为对象let result = Object.fromEntries(searchParams.entries());return result[paramKey];
}// 向url中添加参数
function addParams2Url(url, params) {// 取出baseUrlvar baseUrl = url.split("?")[0];// 取出后续的参数部分var searchParams = new URLSearchParams(url.split("?")[1]);// 向 searchParams 键值对列表中添加参数for (var key in params) {searchParams.set(key, params[key]);}return baseUrl + "?" + searchParams.toString();
}

然后,从路径中获取其它页面跳转至该模块时携带的参数:

const postParam = {}
const redirectParam = {}window.onload = function() {// 1. 初始化微信模块initWxConfig();// 2. 从路径中取出参数let params = getUrlParams("params"); // 进入该模块时携带的参数// 取出调取接口时需要传递的参数postParam = JSON.parse(params).postParam;// 取出重定向时需要的参数redirectParam = JSON.parse(params).redirectParam;...
}

但此时,我们还不能进行下单,因为有一个问题还未解决:我们如何知道当前的下单用户是谁呢?

如果我们不知道是谁下的单,那还怎么调取接口?

所以,为了解决这一问题,我们需要拿 postParam 中的 userTicket 去找后端换取用户 token,先来看实现,然后再解释原因:

// 通过 userTicket 换取用户token
function getTokenByUserTicket(userTicket) {axios.post("https://api.xxx.com",{func: "getTokenByUserTicket",userTicket: userTicket,},{headers: {"Content-Type": "application/json",},}).then(function (response) {const token = response.data.result.token;return token}).catch(function (error) {console.log(JSON.stringify(error));});
}

下面来解释一下原因:

由于平台各端内部都是使用 token 来标识当前使用用户,但当各端互通时,不可能将 token 通过 url 进行传递。所以,在某一端需要跳转至另一个端时,需要将 token 通过平台内部的处理兑换为 ticket,然后通过 url 跳转并携带 ticket。 当另一端接收到 ticket 后,再使用平台内部的相同逻辑将 ticket 兑换为 token,这样不同端之间就能识别到同一个用户了。

这里其它页面跳转时携带 userTicket 以及模块内部拿 userTicket 换取 token 就是该过程的具体体现。

那么,问题解决了,接下来我们就可以拿到用户的下单信息,进行真正的下单操作了:

// 调取后端接口下单
function placeOrder(token, postParam) {axios.post("https://api.xxx.com",{func: "placeOrder",...postParam,token: token,},{headers: {"Content-Type": "application/json",},}).then((response) => {if (response.data.code == 0) {// 下单成功,进行支付payByWeChat(response.data);} else {// 下单失败,报错,然后跳转回其它页面alert(response.data.message);payEndRedirectUrl();}}).catch(function (error) {console.log(JSON.stringify(error));});
}window.onload = function() {...// 下单placeOrder(token, postParam)
}

完成下单后,若下单成功,则调取微信支付的方法:

// 微信支付
function payByWeChat({result}) {wx.chooseWXPay({// 支付签名时间戳,注意微信JS SDK中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符timestamp: result.timeStamp,// 支付签名随机串,不长于 32 位nonceStr: result.nonceStr,// 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)package: result.package,// 签名方式,默认为'SHA1',使用新版支付需传入'MD5'signType: result.signType,// 支付签名paySign: result.paySign,// 支付成功后的回调函数success: function (res) {// res.errMsg === 'chooseWXPay:ok'方式判断前端返回, 微信团队郑重提示:// res.errMsg将在用户支付成功后返回ok,但并不保证它绝对可靠,切记。if (res.errMsg === "chooseWXPay:ok") {alert("支付成功");// 支付成功后,将支付状态置为1,并进行后续跳转payStatus = 1;payEndDoRedirect();}},// 接口调用完成时执行的回调函数,无论成功或失败都会执行complete: function (res) {// 进行收尾工作...// 完成后,进行后续跳转payEndDoRedirect();/*** iOS和Android支付成功点击“完成”后都会进入success和complete函数,都返回'chooseWXPay:ok'* 但也有人说Android支付成功不进入success函数,* 原因是【iOS和Android返回数据不同。支付成功后Android返回 {"errMsg":"getBrandWCPayRequest:ok"},iOS返回{"err_Info":"success","errMsg":"chooseWXPay:ok"},故Android找不到success方法,导致失败】* */},// 支付取消回调函数cancel: function (res) {alert("用户取消支付");// 支付取消后,进行后续跳转payEndDoRedirect();},// 支付失败回调函数fail: function (res) {alert("接口调用失败");// 失败后,进行后续跳转payEndDoRedirect();},});
}

接下来,页面会调起微信支付页面,用户完成操作后,可能会有以下几种情况:

  • 支付成功
  • 支付失败
  • 支付取消

这三种情况均需要我们配置相应的回调函数。

另外,在支付的调用完成后,还会触发一个【接口调用完成】的回调函数,其无论支付成功或是失败,都会执行,我们一般在其中进行一些收尾性的工作。

在这些回调函数中,我们最后一般会进行跳转回到进入本模块的页面中:

// 完成支付后进行跳转
function payEndDoRedirect() {// 从重定向参数中取得一开始跳转到该模块的页面的域名const origin = redirectParam.origin;delete redirectParam.origin;// 向重定向路径中添加订单支付状态 payStatus,后续支付页面可以拿该字段进行进一步的操作redirectParam.payStatus = payStatus;// 将这些参数添加到要跳转的域名后面const originUrl = addUrlParams(origin, redirectParam);window.location.href = originUrl;
}

这样,就实现了一个简单的公共JSAPI支付模块。

代码可见:公共微信JSAPI支付模块demo

1.3 将模块域名加入微信平台授权目录中

这一步一般是由商户平台统一完成。

2. 具体流程实现

完成以上两个模块后,就可以在具体的支付页面中实现 JSAPI 支付流程了。

根据流程图以及我们梳理好的支付流程,首先由用户点击【立即支付】按钮,发起支付,此时我们还不能跳转微信支付模块,而是需要先去授权模块,拿到微信授权码:

const is_weixin = () => {var ua = navigator.userAgent.toLowerCase();return ua.indexOf('micromessenger') !== -1;
}const toPay = async () => {// 首先需要判断是否处于微信浏览器中if(is_weixin()) {// 是在微信浏览器中,则使用 JSAPI 支付,否则可以选择其它方式// 准备好参数,跳转授权模块域名,获取微信授权码const params = {// 当前页面的地址,用于重定向回来origin: window.location.href.split("?")[0],...};// appIdconst appId = 'wx...........'// 注意params要做urlencode处理const to = 'https://agent.xxx.com/getwxcode.html?appid=' + appId + '&params=' + encodeURIComponent(JSON.stringify(params))// 跳转授权window.location.href = to}
}

然后进入我们前面准备好的授权模块中,拿到微信授权码 auth_code,再跳转回来,并携带授权码以及我们传递的一些参数,所以,我们下一步实际上是需要在本页面加载的时候进行处理,判断此时是否从授权模块跳转回来,并进行进一步的处理:

useEffect(() => {// 初次加载时,可能会需要进行一些业务相关的处理,需要与我们的支付逻辑区分开来...// 从路径中获取参数const urlParams = window.location.href.split("?")[1];const authCode = getQuery(urlParams, "code");	// 授权码...		// 可能有其它参数// 判断路径参数中是否有 authCodeif(authCode) {// 有authCode说明是从授权模块且拿到了授权码// 1. 用当前平台内部的token换取用户ticketconst { userTicket } = await getUserTicket();// 2. 准备好支付需要的参数const params = {// 下单需要的相关参数postParam: {userTicket,				// 用户ticketpayMethod: "JSAPI",      // 支付方式loginCode: authCode,     // 授权码...,                     // 还可以带一些参数,比如商品种类等,这些参数都是平台内部下单时要使用的},// 重定向时需要的相关参数redirectParam: {origin: window.location.href.split("?")[0],	// 当前页面的地址...,	// 其它业务相关参数},}// 3. 跳转支付模块const to = 'https://pay.xxx.com/wechat/pay.html?params=' + encodeURIComponent(JSON.stringify(params))window.location.href = to}
}, [])

根据我们上面的微信支付模块中的逻辑,接下来就是调起微信支付,由用户进行操作完成支付,完成操作后,无论支付成功、失败或是被取消,最后都会跳转回进入模块的页面,也就是本页面。

同时,还会携带一个 payStatus 字段,这样,我们就可以判断订单的状态并进行相应的处理了。

由于这里也是跳转回来,所以也需要在页面加载时进行处理:

useEffect(() => {// 业务逻辑...// 从路径中获取参数const urlParams = window.location.href.split("?")[1];const authCode = getQuery(urlParams, "code");           // 授权码const payStatus = getQuery(urlParams, "payStatus");     //订单支付状态...// 拿到授权码后跳转支付逻辑if(authCode) {...}// 支付完成后跳转回来的处理逻辑if(payStatus !== undefined) {if (payStatus == 1) {// 支付成功...} else if (payStatus == 2) {// 支付失败...}}
}, [])

注意,这里在页面初次加载时有三个处理逻辑,需要彼此进行区分。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/267759.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【操作系统到计网从入门到深入】(一)Linux基础知识预备

前言 这个专栏其实是博主在复习操作系统和计算机网络时候的笔记&#xff0c;所以如果是博主比较熟悉的知识点&#xff0c;博主可能就直接跳过了&#xff0c;但是所有重要的知识点&#xff0c;在这个专栏里面都会提到&#xff01;而且我也一定会保证这个专栏知识点的完整性&…

开源框架Apache NiFi调研

开源框架Apache NiFi调研 NiFi背景介绍一、什么是NiFi1.1 Apache NiFi特点&#xff1a;流管理、易用性、安全性、可扩展的体系结构和灵活的伸缩模型。1.2 Apache NiFi特性1.2 Apache NiFi核心概念1.3架构 二、NiFi的诞生&#xff0c;要致力于解决的问题有哪些&#xff1f;三、为…

家具制造ERP软件包含哪些功能?家具制造业ERP系统哪个好

不同的家具有不同的用料、品质、制造工时、营销渠道等&#xff0c;而有些家具制造企业采用传统的管理方式在处理物料BOM、生产实际成本核算、库存盘点、供应商选择、班组计件核对、生产领用以及物料追溯等方面存在不少提升空间。 与此同时也有很多的皮具制造企业借助ERP软件优…

class077 区间dp-下【算法】

class077 区间dp-下【算法】 算法讲解077【必备】区间dp-下 code1 括号区间匹配 // 完成配对需要的最少字符数量 // 给定一个由’[‘、’]‘、’(‘&#xff0c;’)‘组成的字符串 // 请问最少插入多少个括号就能使这个字符串的所有括号正确配对 // 例如当前串是 “([[])”&a…

【触想智能】工业显示器的日常维护及分类知识分享

工业显示器不同于普通商业显示器&#xff0c;它的结构比较复杂&#xff0c;如果在使用的过程中出现产品故障&#xff0c;我们怎么处理呢?今天小编为大家介绍工业显示器日常维护以及分类方面的知识&#xff0c;希望对大家有所帮助。 1、 工业显示器整机无电。这其实是一个非常简…

2023年最新prometheus + grafana搭建和使用+gmail邮箱告警配置

一、安装prometheus 1.1 安装 prometheus官网下载地址 sudo -i mkdir -p /opt/prometheus #移动解压后的文件名到/opt/,并改名prometheus mv prometheus-2.45 /opt/prometheus/ #创建一个专门的prometheus用户&#xff1a; -M 不创建家目录&#xff0c; -s 不让登录 useradd…

新型生成式 AI 助手 Amazon Q 为 IT 专业人士与开发人员提供有力支持(预览版)

今天&#xff0c;我们发布 Amazon Q 预览版&#xff0c;这是一款专用于企业级业务的全新生成式 AI 助手&#xff0c;可根据客户的业务量身定制。 Amazon Q 为开发人员和 IT 专业人员带来了多种支持功能。现在&#xff0c;使用 Amazon Q&#xff0c;即可在亚马逊云科技上构建应…

计算机操作系统7

动态分区分配算法&#xff1a; 2.页表的逻辑地址和页内偏移量的计算 3. 两级页表 4.分段、分页管理的对比 5.传统存储方式的特征和缺点 6.虚拟存储 7.请求分页管理方式

2024世界燕窝滋补品展|上海燕博会推荐品牌天健燕窝集团-为消费者带来好燕窝!

天健燕窝集团拥有27年燕窝进出口贸易经验。是最早加入经营正规燕窝业务的企业之一&#xff0c;业务范围遍布全中国&#xff0c;2015 年至2019 年连续5年燕窝进口量全国第一。 一年一届的世界燕窝及天然滋补品博览会暨世界滋补生态发展大会&#xff08;简称上海燕博会&#xff…

IP与以太网的转发操作

TCP模块在执行连接、收发、断开等各阶段操作时&#xff0c;都需要委托IP模块将数据封装成包发送给通信对象。 网络中有路由器和集线器两种不同的转发设备&#xff0c;它们在传输网络包时有着各自的分工。 (1)路由器根据目标地址判断下一个路由器的位置 (2)集线器在子网中将网…

界面控件DevExpress中文教程 - 如何用Office File API组件填充PDF表单

DevExpress Office File API是一个专为C#, VB.NET 和 ASP.NET等开发人员提供的非可视化.NET库。有了这个库&#xff0c;不用安装Microsoft Office&#xff0c;就可以完全自动处理Excel、Word等文档。开发人员使用一个非常易于操作的API就可以生成XLS, XLSx, DOC, DOCx, RTF, CS…

友元c++

C快速入门 第二十讲&#xff1a;一种特殊的友情关系——友元关系 友元关系是类之间的一种特殊关系&#xff0c;这种关系不仅允许友元类访问对方的public方法和属性&#xff0c;还允许友元访问对方的protected和private方法和属性。 实例1&#xff1a;友元关系访问保护量 1 #i…