背景
最近项目使用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