文章目录
- 概要
- 使用工具
- 微信支付
- H5支付
- JSAPI
- Native支付
- 支付宝支付、ApplePay支付
概要
最近公司开展支付,大概总结下支付流程以及遇到的问题
使用工具
 使用 uniapp 模块搭建H5页面
微信支付
| 支付方式 | 使用场景 | 
| H5支付    | H5支付主要是在手机、ipad等移动设备中通过第三方的浏览器来唤起微信支付的支付产品。 | 
| JSAPI支付 | JSAPI支付主要是用户通过消息或扫描二维码在微信内部打开网页时,可以调用微信支付完成下单购买的流程。 | 
| Native支付 | Native支付是指商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式,适用于PC网站、实体店单品或订单、媒体广告支付等场景 | 
| APP支付 | APP支付又称移动端支付,是商户通过在移动端应用APP中集成开放SDK调起微信支付模块完成支付的模式。(安卓IOS原生使用) | 
|  小程序支付   | 小程序支付是专门被定义使用在小程序中的支付产品。目前在小程序中能且只能使用小程序支付的方式来唤起微信支付。 | 
| 付款码支付    | 付款码支付是用户展示微信钱包内的“刷卡条码/二维码”给商户系统扫描后直接完成支付的模式。主要应用线下面对面收银的场景。 | 
本次涉及到: H5端的话,微信支付大概分为两种,一种是外部浏览器拉取微信支付--- H5支付,另外一种是微信内部浏览器拉取支付 --- JSAPIPC端,主要是使用微信支付---Native
H5支付
H5支付主要用于触屏版的手机浏览器请求微信支付的场景,方便从外部浏览器唤起微信支付。
- 准备工作
 申请对应商户属性的APPID以及mchid等 (一般是管理微信商户平台账号的人去申请)
- 后台搭建和配置开发环境,前端准备好对应页面,此处需要在微信商户平台设定回调页面链接redirect_url (主要用于支付完成后跳转从微信回归到H5页面)。
业务流程
- 前端调用支付接口,传递订单信息
- 后台创建订单信息,获取到微信的支付链接,传递给到前端
- 前端直接跳转该url拉取微信支付,支付成功之后,点击完成会自动跳转至回调页面redirect_url
- 在该回调页面上,前端可调用查询支付结果接口,根据结果返回跳转成功或失败页面,展示对应的结果
回调页面 主要功能 — 1.调用查询支付结果的接口 2.根据后台返回的结果数据跳转成功或失败页面等待,展示结果。
官方流程图
、支付宝支付、ApplePay流程_H5](https://s2.51cto.com/images/blog/202510/07070037_68e44a15837db68930.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
JSAPI
JSAPI支付适用于线下场所、公众号场景和PC网站场景,主要是用于微信内部支付的调用。
 同理
准备工作
 申请对应商户属性的APPID以及mchid等 ,配置授权目录
 业务流程类似,前端页面改变的地方是 需要调用微信内部方法获取code传递给后台,以便后台获取assess_token或openId
业务流程
- 前端 Home 页面 ,创建订单,调用订单信息接口,获取到订单Id等;
- 在 Home 页面,判断环境-是否是微信内部环境,如果 是 - 通过微信网页授权https://open.weixin.qq.com/connect/oauth2/authorize?appid=XXXXX&redirect_uri=${encodeURIComponent(需要跳转的页面)} &response_type=code&scope=snsapi_base&state=STATE#wechat_redirect
- 微信携带授权code重定向到 Home 页面, 后台可将订单数据拼接在重定向的地址后面,也可单独请求(注意:路由是history还是hash),将code传递给后台以便获取access_token、openid,保存本地;
- pay页面中选择支付的方式,调用后台接口,判断是否时微信内部浏览器支付
 ①YES,调用接口传递订单信息及openId,获取微信JS-API支付需要用到的参数;前端通过JSAPI接口,拉取微信支付;
 ②NO,则可以直接跳转后台返回的支付链接,拉取对应的支付;
- 同理,在回调页面上,前端可调用查询支付结果接口,根据结果返回跳转成功或失败页面,展示对应的结果
 微信官方文档-网页授权
网页授权流程:
- 引导用户进入授权页面同意授权,获取code
- 通过code换取网页授权access_token(与基础支持中的access_token不同)
- 如果需要,开发者可以刷新网页授权access_token,避免过期
- 通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)
Home 页面(一般进入H5页面就可以获取code)
onShow() {
   // 1. 判断环境 
   if(this.isWechat()){
   // 2.获取code
     this.getCode()
   }
},		
methods: {
   // 判断是否微信环境环境-(针对JS-API)
   isWechat() {
		 var ua = navigator.userAgent.toLowerCase()
		 var isWXWork = ua.match(/wxwork/i) == 'wxwork'
		 var isWeixin = !isWXWork && ua.match(/MicroMessenger/i) == 'micromessenger'
		 return isWeixin			
   },
   // 获取code
   getCode() { // 非静默授权,第一次有弹框  静默授权的话,scope=snsapi_base
        // 1.授权后重定向的回调链接地址(当前页面的地址(home 地址)) window.location.href
	    var redirect_URL = ' http://localhost:8080/#/Home?orderId=...' //  
	    // ps:该地址需要添加到公众号的一个设置里面去,作为授权的目录
	    
	    // 2.公众号ID:appid( 配置公众号时的信息,可自行查看)
		var appid = 'wxgongzhonghaopeizhi'
		
		// 3.截取code
		this.code = this.getUrlCode().code // 截取code	
		
		// 4.判断code的有无:
		// 无 - 直接跳转微信授权网页,来获取微信授权后携带code的回调地址链接;有 - 直接调用后台接口传递给后台
		if (this.code == null || this.code == '') {
			window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid +
				"&redirect_uri=" + encodeURIComponent(redirect_URL ) + "&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect"
			// STATE可携带信息,具体看官方文档
		} else {
			// 获取code后自己的业务逻辑 code只能用一次,会报错,注意
			//此处调用后端提供的接口,传递给后台code,后台返回access_token、openid
			this.getWechatCodeFn(this.code)
		}
},			
		// 截取url中的code方法
		getUrlCode() {
			var url = location.search
			var theRequest = new Object()
			if (url.indexOf("?") != -1) {
				var str = url.substr(1)
				var strs = str.split("&")
				for (var i = 0; i < strs.length; i++) {
					theRequest[strs[i].split("=")[0]] = (strs[i].split("=")[1])
				}
			}
			return theRequest
		},
       // 传递code给到后台
	async getWechatCodeFn(code) {
	    const res = await getWechatCode({
			code: code
		})
		this.openid= res.data.openid
		uni.setStorageSync('openid',this.openid) // 缓存该数据 - 支付完成后记得清除对应数据
	},		
 }pay 页面
onLoad(parms) {
	this.openid = uni.getStorageSync('openid')  // 获取在首页存到缓存的openid 
},
methods: {
      // 支付按钮
     async payBtn(payType) {
         // 提交给后端的东西,以获取到微信支付用到的参数
		const res = await OrderApi({
			orderId: this.orderId,
			payType: payType, // 1-微信支付为 2-支付宝支付 3-applePay支付
			openid : this.openid // 这个是在首页的时候通过获取用户的code,然后后端返回的一个openid
		})
	   // 微信内部支付
	   if(this.isWechat()&& payType==1){
			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 {
			    // 发起微信支付
				this.onBridgeReady(res.data);
			}
		}else{
		// 其他支付直接跳转后台返回的支付链接
			window.location.replace(res.data);
		}
	},	
	   // 判断是否微信环境环境-(针对JS-API)
    isWechat() {
		 var ua = navigator.userAgent.toLowerCase()
		 var isWXWork = ua.match(/wxwork/i) == 'wxwork'
		 var isWeixin = !isWXWork && ua.match(/MicroMessenger/i) == 'micromessenger'
		 return isWeixin			
    },
     // 发起微信支付
     onBridgeReady(data) {
       // 需要处理data数据:后台返回的JSON字符串,利用JSON.parse 方法将JSON字符串 构造成 JavaScript 值或对象形式。
       // 微信jsapi支付需要用到的参数,对应从返回的数据种获取即可
		let { appId,timeStamp,nonceStr,signType,paySign } = JSON.parse(data)
	 	WeixinJSBridge.invoke('getBrandWCPayRequest',{
				appId: appId,//公众号ID,由商户传入
				paySign: paySign,//微信签名方式
				signType: signType,//微信签名
				timeStamp: timeStamp,//时间戳,自1970年以来的秒数
				nonceStr: nonceStr,// //随机串
				package: JSON.parse(data).package, // 此处有坑,要小心后台返回的数据形式!!!
				debug: true
			}, (res)=> {
			    // 前端调用微信的api 返回的 res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
			    // !!!所以仍然需要调用后台接口进行查询订单的状态结果
				if (res.err_msg === 'get_brand_wcpay_request:ok') {
					// 支付成功的处理逻辑 
					uni.showToast({
						title: '微信支付成功',
						icon: 'none'
					})
				} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
					uni.showToast({
						title: '取消支付',
						icon: 'none'
					})
					// 刷新当前页面,重新获取code(有问题)
					// window.location.reload(xxxx);
				} else {
					uni.showToast({
						title: '支付失败',
						icon: 'none'
					})
				}
			});
	}注意:
 授权目录其实就是H5页面的域名,例如:https://www.haha.com/pay 的授权目录可以设定为https://www.haha.com;因为微信只校验顶级域名,如果支付授权目录设置为多级目录,就会进行全匹配
PS:code一般只能使用一次,下一次的刷新需要几分钟;
 第一次成功获取了 assess_token 和 openid,那么这个 code 就已经被使用过,并且会失效。如果你再次尝试使用这个 code 去获取 session_key 和 openid,微信服务器会返回 “code been used” 的错误。
1. 确保 code 只被使用一次:在服务器端,确保对于每个 code 只发起一次请求到微信服务器。如果由于某些原因(如重试机制)多次发送了相同的 code,就会遇到这个错误。
2. 检查 code 的生成和使用流程:确保 code 是在用户授权之后立即生成并发送到服务器端的。不要在用户授权之前就尝试获取 code,也不要在用户授权后长时间保存 code 并尝试再次使用。
3. 存储和使用 openid:一旦成功从微信服务器获取了 openid,你应该在服务器端存储它们,并在后续的用户请求中使用这些值,而不是每次都重新请求 code。(后台可做处理,前端可以保存到本地)
官方流程图
 整个流程走下来,发现微信对于不同入口拉取的支付管理的蛮严格的,需要使用对应的接口,申请对应参数 等等,不过主要是后台做处理,前端处理的逻辑不多。

Native支付
文档链接:微信Native支付 应用场景:Native支付适用于PC网站、实体店单品或订单、媒体广告支付等场景。
准备工作:
- 选择接入模式:普通商户或普通服务商
- 申请参数:AppID、商户号
- 配置应用
- 下载并配置商户证书
- Native支付设计指引(具体看官方文档)
业务流程
- home页面选择微信支付,调用接口,传递后台订单信息;
- 后台返回支付链接,前端通过① qrcode插件 将链接转换为二维码 或②后台直接转换成base64,前端进行展示;来供客户扫码进行支付;
- 扫码支付成功后,通知前端已完成支付,关闭弹框
 方案①:使用轮询,轮询后台查询结果的接口链接,获取到结果就关闭弹框;
 方案②:使用webSocket进行监听支付结果,检查支付状态;
 前端生成支付二维码
- 下载qrcode插件依赖包或者直接引入本地文件npm install qrcode --save-dev
- 对应页面中引入该插件
<template>
  <div>
    <button @click="getPhoto"></button>
    <el-dialog v-model="dialogVisible" title="支付二维码" width="800" @closed="handleClose">
        <canvas id="QRCode_Img" style="width: 280px; height: 280px"></canvas>
    </el-dialog>
  </div>
</template>
<script>
import QRCode from "qrcode"; //引入生成二维码插件
export default {
  data() {
    return {
      payUrl:"支付链接",
      dialogVisible:false 
    };
  },
  methods: {
    getPhoto(){
       // 获取支付链接
       getTypeList({orderId}, this.headers).then(res => {
        if(res.status == 0) {
             this.dialogVisible = true // 打开弹窗
             this.payUrl = res.data // 支付链接
             // 此处需要使用 this.$nextTick 保证弹框出现,避免弹框未出现,导致无法绘制图片
             this.$nextTick(()=>{
                generateQRCode()
             })
          }
        }).catch(err=>{console.log(err)})
    },
    // 支付payUrl链接 转变为 二维码
    generateQRCode() {  
      // 设置qrcode参数
      let opts = {
        errorCorrectionLevel: "H", //容错级别
        type: "image/png", //生成的二维码类型
        quality: 0.3, //二维码质量
        margin: 0, //二维码留白边距
        width: 180, //宽
        height: 180, //高
        text: this.payUrl, //二维码内容(支付的链接)
        color: {
          dark: "#666", //前景色
          light: "#fff", //背景色
        },
      };
      let photo= document.getElementById("QRCode_Img");
      // 绘制canvas
      QRCode.toCanvas(photo, this.qrUrl, opts, function (error) {
        if (error) {
          console.log("二维码加载失败", error);
        }
      });
      // 此处可开启 轮询后台接口,查询用户支付结果 或者 使用websocket
    },
    handleClose(){
      this.dialogVisible = false
   }
  }
};
</script>
<style>官方流程图

支付宝支付、ApplePay支付
支付宝文档:支付宝支付 ApplePay文档:ApplePay支付 流程和H5支付类似,前端的工作不多,主要是跳转 后台返回的支付链接拉取支付
 其中,ApplePay支付后台流程比较复杂,需要接入对应的供应商才可,而且收的手续费比较高。
补充说明:
H5如何判断所属的环境
 使用场景:用户一扫码,就开始获取用户所使用哪种支付进行扫码,前端 默认选择到对应的支付方式
getDefaultPay(){
      // window.navigator.userAgent属性包含了浏览器类型、版本、操作系统类型、浏览器引擎类型等信息,这个属性可以用来判断浏览器类型
	  var ua = window.navigator.userAgent.toLowerCase();
	  //通过正则表达式匹配ua中是否含有MicroMessenger字符串
	  if(ua.match(/MicroMessenger/i) == 'micromessenger'){ // 微信扫码  
		 this.payType = 1
	  }else if(ua.match(/AlipayClientHK/i) == 'alipayclienthk'){ // 支付宝港版扫码
		this.payType = 2
	  }else if(ua.match(/AlipayClient/i) == 'alipayclient'){ // 支付宝扫码
		this.payType = 3
	  }else{ // 浏览器扫码设置的默认值
		this.payType = 1
	  }
 }                
 
 
                     
            
        













 
                    

 
                 
                    