java开发小程序-微信支付
废话不多说。
预下单文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1
小程序文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5
前端部分要两步:
1、调起预支付,获得一些数据
2、然后将预支付数据用于调起微信支付
首先是小程序发起预支付:
start_pay: function (e) {
var that = this;
//获取session3rd
var state = wx.getStorageSync("state")
wx.getUserInfo({
success: res => {
wx.request({
url: app.globalData.reqfristUrl + app.globalData.xmName + 'wx_pay/startPay',
method: 'post',
data: {
payUser: wx.getStorageSync("users"),//支付对象,业务需要
payType:2,//支付类型,业务需要
session3rd: state,//必传,文档要求,session_key
encryptedData: res.encryptedData,//必传,文档要求
iv: res.iv,//必传,文档要求
fee: that.data.moneyOnline//支付费用,一般业务也是要传的
},
success: function (res) {
console.log(res.data)
if(res.data.isCheck == "true"){
that.do_pay(res.data)
}else{
utils.alertMsg("充值失败")
}
}
})
}
})
}
后端java预支付处理:
@ResponseBody
@RequestMapping(value = "/startPay", method = RequestMethod.POST, produces = "application/json;charset=utf-8")
public Object startPay(HttpServletRequest request, @RequestBody Map obj) {
HashMap<String, Object> map = new HashMap<String, Object>();
try {
//支付类型 1:其它 2:微信 3:微信代充 4:支付宝 5:支付宝代充
String payType = String.valueOf(obj.get("payType"));
//充值对象 自己或者别人的手机号码
String payUser = String.valueOf(obj.get("payUser"));
UserBeforeMoney userBeforeMoney = userService.findByMobile(payUser);//查找对应账户
if(userBeforeMoney == null) {
map.put("isCheck", "false");
map.put("errorMsg", "账户不存在");
return map;
}
//充值对象的userId
Long payUserId = userBeforeMoney.getUserId();
String fee = String.valueOf(obj.get("fee"));
//数字校验
String pattern = "([1-9]\\d*\\.?\\d*)|(0\\.\\d*[1-9])";
boolean check = Pattern.matches(pattern, fee);
if(!check) {
map.put("isCheck", "false");
map.put("errorMsg", "请输入正确的数字");
return map;
}
String encryptedData = String.valueOf(obj.get("encryptedData"));
String iv = String.valueOf(obj.get("iv"));
//根据支付类型判断是去哪个redis库取session3rd和appId
String session3rd = "";String appid = "";// 小程序ID
if("2".equals(payType)) {
appid = AppID;//AppID是properties里面定义的常量
session3rd = jedisClientSingle.get(String.valueOf(obj.get("session3rd")));
}
if("3".equals(payType)) {
appid = Property_AppID;
session3rd = jedisClientSingle_wy.get(String.valueOf(obj.get("session3rd")));
}
String[] session_key = session3rd.split(";");
//解析用户信息用于获取openid和unionId
JSONObject userInfo = EncryptedDataUtils.getUserInfo(encryptedData, session_key[1], iv);
if (userInfo != null) {
String unionId = userInfo.getString("userInfo");// 用户标识
String openId = userInfo.getString("openId");// 用户标识
//数据准备
String timeStamp = new Date().getTime() / 1000 + "";
String nonce_str = RandomUtil.getUUID();// 随机字符串
String pack_age = "prepay_id=";
String signType = "MD5";
String mch_id = Mch_Id;// 商户号
String key = Mch_Key;//商户key
String paySign = "";
String orderSn = RandomUtil.getOrderIdByUUId();//uuid当订单号
// 生成预支付订单
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("appid", appid);
paramMap.put("mch_id", mch_id);
paramMap.put("nonce_str", nonce_str);
paramMap.put("sign_type", signType);
paramMap.put("body", "微信支付");//这边windows server会出现中文乱码问题,结尾会给出解决方案
paramMap.put("out_trade_no", orderSn);// 编号
paramMap.put("total_fee", String.valueOf(new BigDecimal(fee).multiply(new BigDecimal(100)).intValue()));// 价格
//paramMap.put("total_fee", "10");// 价格
paramMap.put("openid", openId);// openId
String ip = HttpUtil.getIpAddr(request);
// String ip2 = HttpUtil.getIpAddrAdvanced(request);
paramMap.put("spbill_create_ip", ip);// 用户端ip
paramMap.put("trade_type", "JSAPI");// 这个api有,固定的
paramMap.put("notify_url", "https://www.xxx.com/yac_web/wx_pay_notify/notify");//回调地址
//字典序排序,最后附上key
String sign2param = new String((MapUtil.sortMap(paramMap)+ "key=" + key).getBytes(),"utf-8");
String pay_sign2 = MD5.md5(sign2param).toUpperCase();
paramMap.put("sign", pay_sign2);
//变成xml字符串
String xmlParam=XmlUtil.getRequestXml(paramMap);
String result = HttpUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST",
xmlParam);
log.info("预支付返回结果xml:" + result);
//预支付订单xml,用来获取prepay_id
Map<String,String> prepayResult = XmlUtil.xml2Map(result);
// 小程序调起支付,所需参数
map.put("appId", appid);
map.put("nonceStr", nonce_str);
map.put("package", pack_age+prepayResult.get("prepay_id"));
map.put("signType", signType);
map.put("timeStamp", timeStamp);
String param = MapUtil.sortMap(map)+ "key=" + key;
paySign=MD5.md5(param).toUpperCase();
map.put("paySign", paySign);
map.put("isCheck", "true");
}
} catch (Exception e) {
map.put("isCheck", "false");
map.put("errorMsg", "充值失败");
log.error(e.getMessage(), e);
}
return map;
}
获取ip:
/**
* 获取ip
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
解密获取用户信息json对象:
/**
* 获取信息
*/
public static JSONObject getUserInfo(String encryptedData,String sessionkey,String iv){
// 被加密的数据
byte[] dataByte = Base64.decode(encryptedData);
// 加密秘钥
byte[] keyByte = Base64.decode(sessionkey);
// 偏移量
byte[] ivByte = Base64.decode(iv);
try {
// 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
int base = 16;
if (keyByte.length % base != 0) {
int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
byte[] temp = new byte[groups * base];
Arrays.fill(temp, (byte) 0);
System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
keyByte = temp;
}
// 初始化
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding","BC");
SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
parameters.init(new IvParameterSpec(ivByte));
cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
byte[] resultByte = cipher.doFinal(dataByte);
if (null != resultByte && resultByte.length > 0) {
String result = new String(resultByte, "UTF-8");
return JSONObject.parseObject(result);
}
} catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | InvalidParameterSpecException | UnsupportedEncodingException | IllegalStateException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
return null;
}
字典序排序:
/**
* 字典序排序,返回参数拼接字符串
* @param paramMap
* @return 例如a="1"&b="2"&...
*/
public static String sortMap(Map<String, Object> paramMap){
Collection<String> keyset2 = paramMap.keySet();
List list2 = new ArrayList<String>(keyset2);
Collections.sort(list2);
// 这种打印出的字符串顺序和微信官网提供的字典序顺序是一致的
String paramStr = "";
for (int i = 0; i < list2.size(); i++) {
paramStr = paramStr + list2.get(i) + "=" + paramMap.get(list2.get(i)) + "&";
}
return paramStr;
}
map转xml:
/**
* map转xml
* @param parameters
* @return
*/
public static String getRequestXml(Map<String,Object> parameters){
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String key = (String)entry.getKey();
String value = (String)entry.getValue();
if ("attach".equalsIgnoreCase(key)||"body".equalsIgnoreCase(key)||"sign".equalsIgnoreCase(key)) {
sb.append("<"+key+">"+"<![CDATA["+value+"]]></"+key+">");
}else {
sb.append("<"+key+">"+value+"</"+key+">");
}
}
sb.append("</xml>");
return sb.toString();
}
xml2Map:
/**
* xml字符串转map
* @param xml
* @return
* @throws IOException
*/
public static Map<String, String> xml2Map(String xml) throws Exception {
Map<String, String> map = new HashMap<String, String>();
SAXReader reader = new SAXReader();
setFeature(reader);
InputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
Document doc = null;
try {
doc = reader.read(is);
Element root = doc.getRootElement();
List<Element> list = root.elements();
for (Element e : list) {
map.put(e.getName(), e.getText());
}
} catch (DocumentException e) {
e.printStackTrace();
} finally {
is.close();
}
return map;
}
/**
* 禁用外部实体
* @return
* @throws SAXException
*/
private static void setFeature(SAXReader reader) throws SAXException {
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
reader.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true);
// 是否包含外部生成的实体。当正在解析文档时为只读属性,未解析文档的状态下为读写。
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
// 是否包含外部的参数,包括外部DTD子集。当正在解析文档时为只读属性,未解析文档的状态下为读写。
reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
reader.setIncludeExternalDTDDeclarations(false);
}
http请求:
//请求方法
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
try {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
return buffer.toString();
} catch (ConnectException ce) {
log.error("连接超时:{}"+ ce,ce);
} catch (Exception e) {
log.error("https请求异常:{}"+ e,e);
}
return null;
}
小程序调起支付:
do_pay: function (param) {
var that = this;
wx.requestPayment({
'timeStamp': param.timeStamp,
'nonceStr': param.nonceStr,
'package': param.package,
'signType': param.signType,
'paySign': param.paySign,
'success': function (res) {
utils.alertMsg("充值成功")
},
'fail': function (res) {
utils.alertMsg("充值失败")
console.log(res)
}
})
}
支付的回调:
@RequestMapping(value = "/notify", method = { RequestMethod.GET, RequestMethod.POST })
public void startPay(HttpServletRequest request, HttpServletResponse response) {
try {
Map<String, Object> reqMap = XmlUtil.xml2Map(request);
String openId = String.valueOf(reqMap.get("openid"));
String outtradeno = String.valueOf(reqMap.get("out_trade_no"));
String total_fee = String.valueOf(reqMap.get("total_fee"));
BigDecimal fee = new BigDecimal(total_fee).divide(new BigDecimal(100));
String time_end = String.valueOf(reqMap.get("time_end"));
//TODO 自己的业务逻辑
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
注意:微信预支付过程中,商品描述因为使用中文而导致乱码使结果为前面不正确,此问题在windows server下才会发生,因为windows server编码是根据地区决定的,反正在中国肯定不是utf-8,所以要修改tomcat bin文件夹下面的catalina.bat文件

浙公网安备 33010602011771号