⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/proteen/article/details/80875670 「我是一个程序媛」欢迎转载,保留摘要,谢谢!


如下介绍一个可运行的微信小程序登录+支付的demo。接触了小程序简易教程的,想必都知道我们必然有自己的后台应用服务器,来处理我们自己的业务逻辑、请求微信服务完成一定的功能。在此,我们的后台采用java环境,本文将首先介绍环境搭建的过程,随后介绍登录+支付的流程及代码。

一、后台web服务环境搭建

1.安装jdk、tomcat,ICP备案的域名准备

Linux安装 jdk:https://blog.csdn.net/zhang918784312/article/details/79751283

Linux 安装 tomcat:https://www.cnblogs.com/EasonJim/p/7202844.html

经过icp备案的域名,请自行准备。

2.配置https

由于小程序请求url必须是https,故而必须配置支持https请求。本人采用的是在阿里云购买的域名,故而采用的证书也是阿里云生成的ssl证书,可参考如下两篇博文进行配置。当然,你也可以通过别的方式生成证书,更或者通过nginx作反向代理到你的服务器。

https://blog.csdn.net/qq_28189091/article/details/75078164 https://blog.csdn.net/z_xuewen/article/details/78176509同时,务必将您的小程序域名绑定在小程序后端。登入小程序后台,【设置】-【开发设置】-【服务器域名】

3.部署web服务

如上两步完成后,请务必确认通过你的域名(https://...)可以展示tomcat的默认页之后,开始部署我们的web服务。在此,就简单粗暴的在webapps下建立小程序的根目录,我命名为wechatserver,在此目录下,创建WEB-INFO,下面的目录结构如下:

图片

classes存放自己写的类的classes文件,lib存放我们项目依赖的jar包,logs用于存放我们的日志输出,web.xml是我们这个项目的配置。demo中,我们只有一个servlet接收小程序前端请求,web.xml中增加配置如下:

<servlet>
<servlet-name>WechatServlet</servlet-name>
<servlet-class>com.icbc.servlet.WechatServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WechatServlet</servlet-name>
<url-pattern>/servlet/WechatServlet</url-pattern>
</servlet-mapping>

4. log4j 应用

在开发调试中,我们免不了需要通过打印日志进行调试,因此在此增加了日志的使用。web.xml中增加配置:

<context-param>  
<param-name>log4jConfigLocation</param-name>
<param-value>classes/log4j.properties</param-value>
</context-param>

在classes增加文件,log4j.properties,内容如下:

log4j.rootLogger = INFO,toConsole,D,E

log4j.appender.toConsole=org.apache.log4j.ConsoleAppender
log4j.appender.toConsole.Target=System.out
log4j.appender.toConsole.layout=org.apache.log4j.PatternLayout
log4j.appender.toConsole.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] [%p] %m%n

log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.file = 你的目录/common.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = info
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
log4j.appender.D.DatePattern='.'yyyy-MM-dd'.log'

log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.file = 你的目录/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
log4j.appender.D.DatePattern='.'yyyy-MM-dd'.log'

二、微信小程序登录+支付

1. 小程序前端目录准备

基于微信小程序工具生成的默认hello world程序,pages下先建立目录order,随后在order目录生成一个新的page,命名为order,结构如下图:

图片

在index中增加按钮,进入order。index.wxml

<view>
<navigator class="index-intro__btn btn btn-danger btn-md" url="/pages/order/order">进入商城</navigator>
</view>

order.xml中描述商品信息,增加支付按钮 order.js 支付事件处理。

2. 登录+支付 code

流程大概分为几步:1)登录,获取code(一个code只能用一次) 2)通过code获取openid(通过请求服务器,由服务器请求微信获取并返回小程序)。微信登录+获取openid接口

3)小程序请求服务器进行预下单,上送商品详情、金额、openid。4)服务器端接收请求,根据请求订单数据、生成第三方订单号,调用微信的统一下单接口。5)服务器收到预下单信息后,签名并组装支付数据,返回给小程序。所需数据见:小程序支付接口 6)小程序前端发起支付,并支付完成 7)服务器收到回调。

2.1 登录,获取code

onLoad: function (options) {

// 登录
wx.login({
success: function (res) {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
var that = this;
if (res.code) {
console.log('获取用户登录态success!' + res.code)
app.globalData.code = res.code
} else {
console.log('获取用户登录态失败!' + res.errMsg)
}
}
})
},

2.2 通过code 获取openid(前端)

getOpenId:function(that, code){
console.log(code);
let operFlag = "getOpenid";
console.log(operFlag);
wx.request({
url: 'https://xxx/wechatserver/servlet/WechatServlet',
data: {
code:code,
operFlag:operFlag
},
header: { 'content-type': 'application/json' },
success: function (res) {
console.log(res);
var openid = res.data.openid;
console.log(openid);
that.paypay(that, openid); //预下单并支付
},
fail: function (res) {
console.log(res.data.errmsg);
console.log(res.data.errcode);
},
complete:function(res){
}
})
},

2.3 服务器端servlet(复写HttpServlet的doGet doPost函数)doPost的代码片段:

paypay: function (that, openid) {
let operFlag = 'pay';
wx.request({
url: 'https://xxx/wechatserver/servlet/WechatServlet',
data: {
openid: openid,
operFlag: operFlag
},
header: { 'content-type': 'application/json' },
success: function (res) {
console.log(res);
wx.requestPayment({
'timeStamp': res.data.timeStamp,
'nonceStr': res.data.nonceStr,
'package': res.data.package,
'signType': 'MD5',
'paySign': res.data.sign,
'success': function (res) {
if (res.errMsg == "requestPayment:ok") {
wx.showToast({
title: '支付成功'
})
}
},
'fail': function (res) {
}
})
},
fail: function (res) {
console.log(res.data.errmsg);
console.log(res.data.errcode);
},
complete: function (res) {
}
})
},

2.4 前端上送订单信息、openid请求预下单(在此,为方便,订单信息直接写死在服务器端了),若成功,则根据服务器端返回数据发起支付。

paypay: function (that, openid) {
let operFlag = 'pay';
wx.request({
url: 'https://xxx/wechatserver/servlet/WechatServlet',
data: {
openid: openid,
operFlag: operFlag
},
header: { 'content-type': 'application/json' },
success: function (res) {
console.log(res);
wx.requestPayment({
'timeStamp': res.data.timeStamp,
'nonceStr': res.data.nonceStr,
'package': res.data.package,
'signType': 'MD5',
'paySign': res.data.sign,
'success': function (res) {
if (res.errMsg == "requestPayment:ok") {
wx.showToast({
title: '支付成功'
})
}
},
'fail': function (res) {
}
})
},
fail: function (res) {
console.log(res.data.errmsg);
console.log(res.data.errcode);
},
complete: function (res) {
}
})
},

2.5 服务器端预下单,2.6并签名返回支付请求数据

if("pay".equals(operFlag)){
String openid = request.getParameter("openid");
logger.info("openid = " + openid);
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String reqStr = getReqStr(openid); //组装预下单的请求数据
logger.info("reqStr=" + reqStr);
results = sendPost(url,reqStr);//发送post数据到微信预下单
logger.info("prepay from weixin: \n " + results);
Map<String,String> return_data = null;
try {
return_data = WXPayUtil.xmlToMap(results);//微信的一个工具类
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
logger.error(e.getMessage());
}
String return_code = return_data.get("return_code");
logger.info("return_code=" + return_code);
if("SUCCESS".equals(return_code)){
String prepay_id = return_data.get("prepay_id");
results = conPayParam(prepay_id); //组装返回数据
}else{
results ="{\"return_code\":\"fail\"}";
}
}

附函数

//组装预下单的请求数据
public static String getReqStr(String openid){
Map<String,String> data = new HashMap<String,String>();
String out_trade_no = setTradeNo();//
//
data.put("appid", appid);
data.put("mch_id",mer_id);
data.put("nonce_str", WXPayUtil.generateUUID());
data.put("sign_type", "MD5");
data.put("body", "spy test");
data.put("out_trade_no", out_trade_no);
data.put("device_info", "");
data.put("fee_type", "CNY");
data.put("total_fee", "1");//1分钱
data.put("spbill_create_ip", "123.12.12.123");
data.put("notify_url", "http://xxx/wxpay/notify");
data.put("trade_type", "JSAPI");
data.put("product_id", "12");
data.put("openid", openid);
try {
String sign = WXPayUtil.generateSignature(data, merKey, SignType.MD5);
data.put("sign", sign);
} catch (Exception e) {
e.printStackTrace();
logger.error("sign error");
}
String reqBody = null;
try {
reqBody = WXPayUtil.mapToXml(data);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return reqBody;
}

//保证唯一
public static String setTradeNo(){
String orderid = "20211909105011"+ getRandom(6);
logger.info("orderid = " + orderid);
return orderid;
}

//组装返回客户端的请求数据
public static String conPayParam(String prepayid){
logger.info("根据当前的prepayid构造返回参数= " + prepayid);
String results = "";
Map<String,String> map = new HashMap<String,String>();
map.put("appId", appid);
LocalDateTime time = LocalDateTime.now();
map.put("timeStamp", WXPayUtil.getCurrentTimestamp()+"");
map.put("nonceStr", WXPayUtil.generateUUID() );
map.put("package", "prepay_id=" + prepayid);
map.put("signType", "MD5");
String sign;
try {
sign = WXPayUtil.generateSignature(map, merKey, SignType.MD5);
map.put("sign", sign);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
logger.error(e.getMessage());
}
return JSON.toJSONString(map);
}

2.6 服务器端返回数据到微信小程序客户端后,发起微信支付,代码在2.3章节已附。2.8 服务器端收到微信的支付成功通知(省略)

三、实战中遇到的问题

1.预下单和支付请求中,签名的密钥使用的是商户密钥,但是用code获取openid是使用小程序对应的secret key,这个可以在小程序的后台看到。

2.微信小程序前端发起post请求到服务器端时,服务器端收不到请求参数。原因是:微信API接口wx.request中:

a) 对于 GET 方法的数据,会将数据转换成 query string(encodeURIComponent(k)=encodeURIComponent(v)&encodeURIComponent(k)=encodeURIComponent(v)…)

b1) 对于 POST 方法且 header[‘content-type’] 为 application/json 的数据,会对数据进行 JSON 序列化

b2) 对于 POST 方法且 header[‘content-type’] 为 application/x-www-form-urlencoded 的数据,会将数据转换成 query string (encodeURIComponent(k)=encodeURIComponent(v)&encodeURIComponent(k)=encodeURIComponent(v)…)

所以,如果post请求,为省去服务器端反序列化的操作时,可使用 header[‘content-type’] 为 application/x-www-form-urlencoded 的数据。

3.如果部署了servlet后,tomcat重启后,需要等几分钟才能生效(原因是我的机器内存比较小,而tomcat又很占用内存资源),待熟悉tomcat 调优。

文章目录
  1. 1. 一、后台web服务环境搭建
    1. 1.1. 1.安装jdk、tomcat,ICP备案的域名准备
    2. 1.2. 2.配置https
    3. 1.3. 3.部署web服务
    4. 1.4. 4. log4j 应用
  2. 2. 二、微信小程序登录+支付
    1. 2.1. 1. 小程序前端目录准备
    2. 2.2. 2. 登录+支付 code
      1. 2.2.1. 2.1 登录,获取code
      2. 2.2.2. 2.2 通过code 获取openid(前端)
      3. 2.2.3. 2.3 服务器端servlet(复写HttpServlet的doGet doPost函数)doPost的代码片段:
      4. 2.2.4. 2.4 前端上送订单信息、openid请求预下单(在此,为方便,订单信息直接写死在服务器端了),若成功,则根据服务器端返回数据发起支付。
      5. 2.2.5. 2.5 服务器端预下单,2.6并签名返回支付请求数据
  3. 3. 三、实战中遇到的问题