uni-app开发经验分享八: 实现微信APP支付的全过程详解

  • A+
所属分类:Web前端
摘要

最近项目使用uni-app实现微信支付,把过程简单记录下,帮助那些刚刚基础uni-app,苦于文档的同学们。
整体来说实现过程和非uni-app的实现方式没有太大不同,难点就在于uni-app对于orderInfo的格式没有说明。


背景

最近项目使用uni-app实现微信支付,把过程简单记录下,帮助那些刚刚基础uni-app,苦于文档的同学们。
整体来说实现过程和非uni-app的实现方式没有太大不同,难点就在于uni-app对于orderInfo的格式没有说明。

准备工作

  1. 申请了商户号,拿到了API秘钥。这个需要微信开发平台,相关的工作大家百度。
  2. 后面代码里用到的appid和秘钥之类需要实现申请号。
  3. 在uni-app manifest.json 配置sdk支付权限

uni-app开发经验分享八: 实现微信APP支付的全过程详解

 

 

 

前端代码

  1. onload阶段获取了可用支付列表,这里我们只用到了微信支付。
  2. requestPayment  

          a. getOrderInfo 获取到订单信息,主要是prepayid,对应统一下单api的返回值。

          b. uni.requestPayment发起支付,效果就是弹出微信支付框输入密码支付。第一个参数是“wxpay”,第二个参数就是OrderInfo.

前端代码很简单,重点是如何让后端返回OrderInfo以及OrderInfo的格式。 

  

前端代码如下:

<template>     <view>         <page-head :title="title"></page-head>         <view class="uni-padding-wrap">             <view style="background:#FFF; padding:50upx 0;">                 <view class="uni-hello-text uni-center">支付金额</text></view>                 <view class="uni-h1 uni-center uni-common-mt">                     <text class="rmbLogo">¥</text>                     <input class="price" type="digit" :value="price" maxlength="4" @input="priceChange" />                 </view>             </view>             <view class="uni-btn-v uni-common-mt">                 <!-- #ifdef APP-PLUS -->                 <template v-if="providerList.length > 0">                     <button v-for="(item,index) in providerList" :key="index" @click="requestPayment(item,index)"                         :loading="item.loading">{{item.name}}支付</button>                 </template>                 <!-- #endif -->             </view>         </view>     </view>     </view> </template> <script>     export default {         data() {             return {                 title: 'request-payment',                 loading: false,                 price: 1,                 providerList: []             }         },         onLoad: function() {             // #ifdef APP-PLUS             uni.getProvider({                 service: "payment",                 success: (e) => {                     console.log("payment success:" + JSON.stringify(e));                     let providerList = [];                     e.provider.map((value) => {                         switch (value) {                             case 'alipay':                                 providerList.push({                                     name: '支付宝',                                     id: value,                                     loading: false                                 });                                 break;                             case 'wxpay':                                 providerList.push({                                     name: '微信',                                     id: value,                                     loading: false                                 });                                 break;                             default:                                 break;                         }                     })                     this.providerList = providerList;                 },                 fail: (e) => {                     console.log("获取支付通道失败:", e);                 }             });             // #endif         },         methods: {             async requestPayment(e, index) {                 this.providerList[index].loading = true;                 let orderInfo = await this.getOrderInfo(e.id);                 console.log("得到订单信息", orderInfo);                 if (orderInfo.statusCode !== 200) {                     console.log("获得订单信息失败", orderInfo);                     uni.showModal({                         content: "获得订单信息失败",                         showCancel: false                     })                     return;                 }                 uni.requestPayment({                     provider: e.id,                     orderInfo: orderInfo.data.data,                     success: (e) => {                         console.log("success", e);                         uni.showToast({                             title: "感谢您的赞助!"                         })                     },                     fail: (e) => {                         console.log("fail", e);                         uni.showModal({                             content: "支付失败,原因为: " + e.errMsg,                             showCancel: false                         })                     },                     complete: () => {                         this.providerList[index].loading = false;                     }                 })             },             getOrderInfo(e) {                 let appid = "";                 // #ifdef APP-PLUS                 appid = plus.runtime.appid;                 // #endif                 let url = 'http://10.10.60.200:8070/sc-admin/sales/wx/prepay/?brokerId=shba01';                 return new Promise((res) => {                     uni.request({                         url: url,                         success: (result) => {                             res(result);                         },                         fail: (e) => {                             res(e);                         }                     })                 })             },             priceChange(e) {                 console.log(e.detail.value)                 this.price = e.detail.value;             }         }     } </script>  <style>     .rmbLogo {         font-size: 40upx;     }      button {         background-color: #007aff;         color: #ffffff;     }      .uni-h1.uni-center {         display: flex;         flex-direction: row;         justify-content: center;         align-items: flex-end;     }      .price {         border-bottom: 1px solid #eee;         width: 200upx;         height: 80upx;         padding-bottom: 4upx;     }      .ipaPayBtn {         margin-top: 30upx;     } </style> 

  

后端代码(springboot)

核心代码

import com.alibaba.fastjson.JSONObject; import com.bian.common.core.domain.AjaxResult; import com.bian.common.utils.StringUtils; import com.bian.framework.config.jwt.AuthService; import com.bian.sales.entity.Constant; import com.bian.sales.entity.PayInfo; import com.bian.sales.service.IWxService; import com.bian.sales.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import com.thoughtworks.xstream.XStream; import org.springframework.http.HttpEntity; import org.slf4j.Logger; import javax.servlet.http.HttpServletRequest; import java.util.*;  @Service public class WxServiceImpl implements IWxService {     Logger logger = LoggerFactory.getLogger(WxServiceImpl.class);      @Override     public AjaxResult goWeChatPay(String brokerId, HttpServletRequest request) throws Exception {         String clientIP = CommonUtil.getClientIp(request);         logger.error("openId: " + brokerId + ", clientIP: " + clientIP);         String randomNonceStr = RandomUtils.generateMixString(32);         Map<String, String> result = unifiedOrder(brokerId, clientIP, randomNonceStr);         System.out.println(request.toString());         if(StringUtils.isBlank(result.get("prepay_id"))) {            return AjaxResult.error("支付错误");         } else {             logger.info("支付成功");             Map <String,Object>jsonObject = new LinkedHashMap();             String noncestr = RandomUtils.generateMixString(32);             String prepayid = result.get("prepay_id");             String timestamp = String.valueOf(new Date().getTime()/1000);             jsonObject.put("appid",Constant.APP_ID);             jsonObject.put("noncestr",noncestr);             jsonObject.put("package","Sign=WXPay");             jsonObject.put("partnerid",Constant.MCH_ID);             jsonObject.put("prepayid",result.get("prepay_id"));             jsonObject.put("timestamp",new Date().getTime()/1000);             jsonObject.put("sign",getSignforPayment(noncestr,prepayid,timestamp ));             return AjaxResult.success(jsonObject);         }     }      /**      * @Function: 去支付      * @author:   YangXueFeng      * @Date:     2019/6/14 16:50      */     /**      * 调用统一下单接口      * @param brokerId      */     private Map<String, String>       (String brokerId, String clientIP, String randomNonceStr) {          try {              //生成预支付交易单,返回正确的预支付交易会话标识后再在APP里面调起支付             String url = Constant.URL_UNIFIED_ORDER;              PayInfo payInfo = createPayInfo(brokerId, clientIP, randomNonceStr);             String md5 = getSign(payInfo);             payInfo.setSign(md5);              logger.error("md5 value: " + md5);              String xml = CommonUtil.payInfoToXML(payInfo);             xml = xml.replace("__", "_").replace("<![CDATA[1]]>", "1");             //xml = xml.replace("__", "_").replace("<![CDATA[", "").replace("]]>", "");             logger.error(xml);              StringBuffer buffer = HttpUtil.httpsRequest(url, "POST", xml);             logger.error("unifiedOrder request return body: n" + buffer.toString());             Map<String, String> result = CommonUtil.parseXml(buffer.toString());               String return_code = result.get("return_code");             if(StringUtils.isNotBlank(return_code) && return_code.equals("SUCCESS")) {                  String return_msg = result.get("return_msg");                 if(StringUtils.isNotBlank(return_msg) && !return_msg.equals("OK")) {                     logger.error("统一下单错误!");                     return null;                 }                  String prepay_Id = result.get("prepay_id");                 return result;              } else {                 return null;             }          } catch (Exception e) {             e.printStackTrace();         }          return null;     }      /**      * 生成统一下单接口的请求参数      * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1      * @param brokerId      * @param clientIP      * @param randomNonceStr      * @return      */     private PayInfo createPayInfo(String brokerId, String clientIP, String randomNonceStr) {         clientIP ="222.72.148.34";         Date date = new Date();         String timeStart = TimeUtils.getFormatTime(date, Constant.TIME_FORMAT);         String timeExpire = TimeUtils.getFormatTime(TimeUtils.addDay(date, Constant.TIME_EXPIRE), Constant.TIME_FORMAT);          String randomOrderId = CommonUtil.getRandomOrderId(); //生成随机商品订单号          PayInfo payInfo = new PayInfo();         payInfo.setAppid(Constant.APP_ID);         payInfo.setMch_id(Constant.MCH_ID);         payInfo.setDevice_info("WEB");         payInfo.setNonce_str(randomNonceStr);         payInfo.setSign_type("MD5");  //默认即为MD5         payInfo.setBody("必安glJSAPI支付测试");         payInfo.setAttach("支付测试4luluteam");         payInfo.setOut_trade_no(randomOrderId);         payInfo.setTotal_fee(1);         payInfo.setSpbill_create_ip(clientIP);         payInfo.setTime_start(timeStart);         payInfo.setTime_expire(timeExpire);         payInfo.setNotify_url(Constant.URL_NOTIFY);         payInfo.setTrade_type("APP");         payInfo.setLimit_pay("no_credit"); //        payInfo.setOpenid(brokerId);         return payInfo;     }      private String getSign(PayInfo payInfo) throws Exception {         StringBuffer sb = new StringBuffer();         sb.append("appid=" + payInfo.getAppid())                 .append("&attach=" + payInfo.getAttach())                 .append("&body=" + payInfo.getBody())                 .append("&device_info=" + payInfo.getDevice_info())                 .append("&limit_pay=" + payInfo.getLimit_pay())                 .append("&mch_id=" + payInfo.getMch_id())                 .append("&nonce_str=" + payInfo.getNonce_str())                 .append("&notify_url=" + payInfo.getNotify_url()) //                .append("&openid=" + payInfo.getOpenid())                 .append("&out_trade_no=" + payInfo.getOut_trade_no())                 .append("&sign_type=" + payInfo.getSign_type())                 .append("&spbill_create_ip=" + payInfo.getSpbill_create_ip())                 .append("&time_expire=" + payInfo.getTime_expire())                 .append("&time_start=" + payInfo.getTime_start())                 .append("&total_fee=" + payInfo.getTotal_fee())                 .append("&trade_type=" + payInfo.getTrade_type())                 .append("&key=" + Constant.API_KEY);          System.out.println("排序后的拼接参数:" + sb.toString());         return CommonUtil.getMD5(sb.toString().trim()).toUpperCase();     }      private String getSignforPayment(String noncestr,String prepayid,String timestamp) throws Exception {         StringBuffer sb = new StringBuffer();         sb.append("appid=" +Constant.APP_ID)                 .append("&noncestr=" + noncestr)                 .append("&package=" + "Sign=WXPay")                 .append("&partnerid=" + Constant.MCH_ID)                 .append("&prepayid=" + prepayid)                 .append("&timestamp=" + timestamp)                 .append("&key=" + Constant.API_KEY);          System.out.println("排序后的拼接参数:" + sb.toString());         return CommonUtil.getMD5(sb.toString().trim()).toUpperCase();     }  } 

  

代码说明
以上代码goWeChatPay从controller层跳转并返回结果给controller接口。所有过程参考微信官方文档的2个接口

  1. 统一下单接口后端
  2. 调起支付接口前端已实现

unifiedOrder对应了统一下单接口,看起来很复杂,其实就做了一件事就是拼接参数。拼接参数时涉及到了签名算法,理解这个算法是关键,建议花时间理解透彻这个算法。

createPayInfo,创建一个PayInfo的类,对应了提交的各个参数。
getSign,签名算法的具体实现,获得提交参数的sign。

以上完成了统一下单接口的过程,如果return_code返回“SUCCESS”,result_code返回OK,我们会获得prepay_id(预支付交易会话标识),到这里已经完成了后端内容。但为了使用uni-app我们需要按照如下格式返回给前端,

 

格式如下:

{"data": { 		"appid": "wx0411fa6a39d61297", 		"noncestr": "5JigiIJicbq8hQI2", 		"package": "Sign=WXPay", 		"partnerid": "1230636401", 		"prepayid": "wx21204902147233e222e12d451613768000", 		"timestamp": 1571662142, 		"sign": "0E5C9B9B1C8D7497A234CCC3C721AB1F" 	}, 	"statusCode": 200, 	"header": { 		"Content-Type": "text/plain;charset=UTF-8", 		"X-Android-Response-Source": "NETWORK 200", 		"Date": "Mon, 21 Oct 2019 12:49:02 GMT", 		"EagleId": "74cf71a315716621419605339e", 		"Vary": "[Accept-Encoding, Accept-Encoding]", 		"X-Android-Received-Millis": "1571662145735", 		"Timing-Allow-Origin": "*", 		"_": "HTTP/1.1 200 OK", 		"X-Android-Selected-Protocol": "http/1.1", 		"Connection": "keep-alive", 		"Via": "cache28.l2et15-1[208,0], kunlun5.cn1241[504,0]", 		"X-Android-Sent-Millis": "1571662145071", 		"Access-Control-Allow-Origin": "*", 		"Server": "Tengine", 		"Transfer-Encoding": "chunked" 	}, 	"errMsg": "request:ok" } 

  

重点是data部分,就是uni-app要求的OrderInfo的格式,getSignforPayment就是该部分的签名算法。

以上如果实行正确,应该就可以正常发起微信支付。

参考文档

https://blog.csdn.net/zhuoganliwanjin/article/details/81872215