背景

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

准备工作

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

前端代码

  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

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

  1. “全栈2019”Java第八十三章:内部类与接口详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  2. 微信小程序开发之详解生命周期方法

    生命周期是指一个小程序从创建到销毁的一系列过程 在小程序中 ,通过App()来注册一个小程序 ,通过Page()来注册一个页面 先来看一张小程序项目结构 从上图可以看出,根目录下面有包含了app.js ...

  3. 微信小程序的配置详解

    1.配置详解: 使用app.json文件来对微信小程序进行全局配置,决定页面文件的路径.窗口表现.设置网络超时时间.设置多 tab 等. 1>pages 接受一个数组,每一项都是字符串,来指定小 ...

  4. 微信小程序页面传值详解

    我们知道,在微信小程序中,从一个页面转到另一个页面,一般情况下可以通过navigate或redirect时候的url来携带参数,然后在目标页面的onLoad函数参数中获取这些url参数.例如:   / ...

  5. Nodejs之MEAN栈开发(八)---- 用户认证与会话管理详解

    用户认证与会话管理基本上是每个网站必备的一个功能.在Asp.net下做的比较多,大体的思路都是先根据用户提供的用户名和密码到数据库找到用户信息,然后校验,校验成功之后记住用户的姓名和相关信息,这个信息 ...

  6. 微信CRM六大模块的详解

    微信团队一直强调企业微信的主要功能是服务而非营销工具,微信5.0将公众号区分为服务号和订阅号,10月底平台为服务号开放高级接口,包括客服接口.网页授权等,可见服务是微信公众号的核心价值和方向.前一阵很 ...

  7. Spring Security教程(八):用户认证流程源码详解

    本篇文章主要围绕下面几个问题来深入源码: 用户认证流程 认证结果如何在多个请求之间共享 获取认证用户信息 一.用户认证流程 上节中提到Spring Security核心就是一系列的过滤器链,当一个请求 ...

  8. IllegalStateException : Web app root system property already set to different value问题详解

    一.问题描述     最近公司有了一个新项目,这个项目最近部署到测试服务器上的时候出现了一个问题. 严重: Exception sending context initialized event to ...

  9. 【APP接口开发】常用HTTP响应头状态码详解

    1.200 OK,客户端请求城成功 2.400 Bad Request ,客服端请求语法错误,服务器无法理解和处理 3.401 unauthorized,请求未通过认证 4.403 permissio ...

随机推荐

  1. 开源OLAP引擎对比

    什么是olap 01.绝大多数请求都是读请求 02.数据以相当大的批次(>1000行)更新,而不是单行更新;或者它根本没有更新 03.数据已添加到数据库,但不会进行修改 04.对于读取,每次查询 ...

  2. 百度实习生,以修仙者的角度聊聊怎么学MySQL,不来看看你的修为如何吗?

    目录 因为我个人比较喜欢看修仙类的小说,所以本文的主体部分借用修仙者的修为等级,将学习旅程划分成:练气.筑基.结丹.元婴.化神.飞升六个段位,你可以看下你大概在哪个段位上哦! 本文目录: 我为什么要写 ...

  3. 使用mybatis-generator插件结合tk.mybatis自动生成mapper

    本篇文章将介绍使用spring boot框架,引入mybatis-generator插件,结合tk.mybatis自动生成Mapper和Entity的一整套流程,其中包括最重要的踩坑与填坑.     ...

  4. springBoot整合Sentinel实现降级限流熔断

    由于hystrix的停止更新,以及阿里Sentinel在历年双十一的贡献.项目中使用了Sentinel,今天我们来讲讲Sentinel的入门教程,本文使用1.6.3版本进行讲解 本文通过Sentine ...

  5. [PASECA2019]honey_shop writeup

    这是PASECA2019的一道web题 前端是一个蜂蜜商店,你有$1336,可以买蜂蜜和flag.但是flag要$1337,所以我们只能买蜂蜜. 一般这种题目的常规操作就是通过修改你的钱使你能够买到f ...

  6. 配置文件中配置集合类(Map、list)@Value注入map、List

    spel表达式就是spring表达式.在java代码中,还有这种写法: @Value("#{'${auth.filter.exclude-urls}'.split(',')}") ...

  7. 讲两个int 数组找出重复的数字 用最少的循环

    int a[] = {1,3}; int b[] = {1,3,5}; int size = a.length>b.length ?a.length:b.length; int valueA = ...

  8. [leetcode]103. Binary Tree Zigzag Level Order Traversal二叉树Z字形层序遍历

    相对于102题,稍微改变下方法就行 迭代方法: 在102题的基础上,加上一个变量来判断是不是需要反转 反转的话,当前list在for循环结束后用collection的反转方法就可以实现反转 递归方法: ...

  9. SpringBoot文件上传配置

    /** * 文件上传配置 * @return */ @Bean public MultipartConfigElement multipartConfigElement() { MultipartCo ...

  10. Java学习日报8.2

    package user;import java.util.*; public class User { private String kouling; private String u; priva ...