- A+
背景
最近项目使用uni-app实现微信支付,把过程简单记录下,帮助那些刚刚基础uni-app,苦于文档的同学们。
整体来说实现过程和非uni-app的实现方式没有太大不同,难点就在于uni-app对于orderInfo的格式没有说明。
准备工作
- 申请了商户号,拿到了API秘钥。这个需要微信开发平台,相关的工作大家百度。
- 后面代码里用到的appid和秘钥之类需要实现申请号。
- 在uni-app manifest.json 配置sdk支付权限
前端代码
- onload阶段获取了可用支付列表,这里我们只用到了微信支付。
- 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("¬ify_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("×tamp=" + timestamp) .append("&key=" + Constant.API_KEY); System.out.println("排序后的拼接参数:" + sb.toString()); return CommonUtil.getMD5(sb.toString().trim()).toUpperCase(); } }
代码说明
以上代码goWeChatPay从controller层跳转并返回结果给controller接口。所有过程参考微信官方文档的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