最近写了一个微信小程序的后端,微信支付(Java版)。在这里分享一下开发过程吧。
首先我们要先去了解微信小程序支付开发的过程。
这里说一下流程。
- 微信小程序端获取到code,由微信小程序前端传code到后端,
- 后端接收到code去调用微信官方接口换取openid。
- 生成商户订单,即通过xml格式包含文档中必填的参数去调用统一下单接口(附下图),返回prepay_id.
- 当然微信小程序需要二次签名,同样通过必填参数封装,返回给微信小程序端。(其中有很多坑,下文慢慢讲解)
正文开始
1、微信小程序前端获取到code
//获取code的方法
wx.login({
success: res => {
console.log("向后台传递获取用户的信息的地址");
console.log(res.code);
// 发送 res.code 到后台换取 openId
//此处可以请求后台接口获取openid
}
})
2.后台接收到code后,通过拼接字符串请求地址,返回openid(在后续附上工具类)
public static String getOpenId(String code) {
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + 你的APP_ID +
"&secret=" + 你的APP_SECRET + "&js_code=" + code + "&grant_type=authorization_code";
HttpUtil httpUtil = new HttpUtil();
try {
HttpResult httpResult = httpUtil.doGet(url, null, null);
if (httpResult.getStatusCode() == 200) {
JsonParser jsonParser = new JsonParser();
JsonObject obj = (JsonObject) jsonParser.parse(httpResult.getBody());
System.out.println("getOpenId: " + obj.toString());
if (obj.get("errcode") != null) {
System.out.println("getOpenId returns errcode: " + obj.get("errcode"));
return "";
} else {
return obj.get("openid").toString();
}
//return httpResult.getBody();
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
3.其次是调用统一下单接口,(这一步也是很多人采坑的地方)官方详细文档
- 统一下单接口访问的接口为:https://api.mch.weixin.qq.com/pay/unifiedorder
- 统一下单接口必填参数为:[共十个]appid(小程序ID),mch_id(商户号),nonce_str(随机字符串),sign(签名),body(商品描述),out_trade_no(商户订单号),total_fee(标价金额),spbill_create_ip(终端ip),notify_url(回调地址),trade_type(交易类型)
**注意:**这里的nonce_str必须与再次签名的随机字符串为同一值。
我这里是通过封装一个实体类,对类进行赋值取值。 (当然,嫌麻烦的可以直接全部写在配置类中)
public class PayInfo implements Serializable {
private String appid;//appid
private String body;//商品描述
private String mch_id;//商户号
//private String device_info; //设备号,小程序传"WEB"
private String nonce_str;//随机字符串
private String notify_url;//通知地址
private String openid;//用户标识
private String out_trade_no;//商户订单号
private String sign;//签名
private String sign_type; //签名类型
private String spbill_create_ip;//终端ip
//private String detail;//商品详情
//private String attach;附加数据
private String time_expire;//交易结束时间
private String time_start;//起始交易时间
private int total_fee;//商户金额
private String trade_type; //交易类型,JSAPI
//private String limit_pay; //指定支付方式,no_credit
private String TimeStamp;
private String repay_id;//这里应该设置package,但是是关键字。
private String key;
…省略get和set…
这一步是 对实体类设置需要的值。(酌情修改参数,不必填的可以注释掉,上面写了必填参数!) 注意:微信官方要求md5加密必须符合字母表顺序,这一步在写实体类的时候要注意(属性名要按字母表写)。
set和get方法不影响。
private PayInfo createPayInfo(String openId, String clientIP, String randomNonceStr) {
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("JSAPI支付测试");//这里如果是中文需要进行转码,不然可能会报错
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("JSAPI");
payInfo.setLimit_pay("no_credit");
payInfo.setOpenid(openId);
return payInfo;
设置好参数,这一步是对字符串进行拼接。目的是md5加密!(酌情删减)
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.APP_KEY);
log.error("排序后的拼接参数:" + sb.toString());
return CommonUtil.getMD5(sb.toString().trim()).toUpperCase