实现Java (springMVC)后台端接入微信app支付,回调。
最近去实现Java端实现微信app的后台代码,自己也是查看了很多文档,最终实现了后台端代码的预订单生成,已经前段支付成功后的回调校验,好了废话不多说,直接编写我所实现的路程。(写的不好,大神们不要喷我,如果有雷同请联系我 qq:47092202)
微信APP支付实现
首先需要导入必须的jar包,根据个人而定,我是用了一下jar包
<dependency>
<groupId>jdom</groupId>
<artifactId>jdom</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.3.1</version>
</dependency>
首先先准备好,微信支付接口的申请,具体操作可以看官网的文档配置类
public class ConfigUtil {
public final static String APPID = "";// 服务号的应用号
public final static String MCH_ID = "";// 商户号
public final static String API_KEY = "";// API密钥
public final static String SIGN_TYPE = "MD5";// 签名加密方式
/**微信支付url 不用修改*/
public final static String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**微信订单查询url 不用修改*/
public final static String ORDER_QUERY = "https://api.mch.weixin.qq.com/pay/orderquery";
/**回调方法,是我们后台给微信回调的接口,下面会写到*/
public final static String NOTIFY_URL = "xxxxx/pay/wxNotify";
}
controller 代码 (让安卓,ios调用的接口)
@ResponseBody
@RequestMapping("/wxAppPay")
public Map<String, Object> wxpay(HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> resultMap = new HashMap<String, Object>();
Order order = orderService.queryOrder(Long.valueOf(request.getParameter("orderNo"))); //根据唯一订单号,查询订单 //业务代码,根据自己情况实现
BigDecimal price = order.getPayPrice();
int priceStr = price.multiply(new BigDecimal(100)).intValue(); //微信 金额 单位是分 所以要*100转化为元
if (priceStr <= 0) {
resultMap.put("msg", 金额不能为0!");
resultMap.put("code", "500");
return resultMap;
}
String requestXML = OrderUtil.createOrder(request, order, price);//生成有签字的预订单
// 调用统一下单接口
String result = PayCommonUtil.httpsRequest(ConfigUtil.UNIFIED_ORDER_URL, "POST", requestXML);
logger.info("\n"+result);
try {
SortedMap<Object, Object> paramMap = createPayInfo(result);
resultMap.put("code", "200");
resultMap.put("payInfo", paramMap);
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return resultMap;
}
微信回调地址 controller (注意回调地址接口不允许传参)
@RequestMapping("/wxNotify")
@ResponseBody
public void wxNotify(HttpServletRequest request, HttpServletResponse response) throws IOException, JDOMException, ParseException {
logger.info("微信回调方法");
// 读取参数
InputStream inputStream;
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null) {
sb.append(s);
}
in.close();
inputStream.close();
// 解析xml成map
Map<String, Object> m = new HashMap<String, Object>();
m = XMLUtil.doXMLParse(sb.toString());
// 过滤空 设置 TreeMap
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
Iterator<String> it = m.keySet().iterator();
while (it.hasNext()) {
String parameter = (String) it.next();
Object parameterValue = m.get(parameter);
String v = "";
if (null != parameterValue) {
v = ((String) parameterValue).trim();
}
packageParams.put(parameter, v);
}
// 判断签名是否正确
String resultXml = "";
if (PayCommonUtil.isWxSign("UTF-8", packageParams)) {
if ("SUCCESS".equals((String) packageParams.get("result_code"))) {
logger.info("微信支付回调,签字成功!");
String mch_id = (String) packageParams.get("mch_id"); // 商户号
String openid = (String) packageParams.get("openid"); // 用户标识
String out_trade_no = (String) packageParams.get("out_trade_no"); // 商户订单号 +随机6位数
String transaction_id = (String) packageParams.get("transaction_id"); // 微信支付订单号
Long orderNo = Long.valueOf(out_trade_no.substring(0, out_trade_no.length()-6)); //还原数据库存放的订单号
String cash_fee = (String) packageParams.get("cash_fee"); // 实际支付金额(如果有优惠或者没优惠)
String time_end = (String) packageParams.get("time_end"); //支付完成时间
Order order = this.orderService.queryOrder(orderNo);
if (!ConfigUtil.MCH_ID.equals(mch_id) || order == null) {
logger.info("支付失败,错误信息:" + "参数错误");
resultXml=PayCommonUtil.setXML("FAIL","参数错误");
} else {
//自己业务处理//
// 订单更新 ,保存支付信息
resultXml=PayCommonUtil.setXML("SUCCESS","OK");
}
} else {
logger.info("支付失败,错误信息:" + packageParams.get("err_code"));
resultXml=PayCommonUtil.setXML("FAIL","报文错误");
}
} else {
resultXml=PayCommonUtil.setXML("FAIL","签名验证失败");
logger.info("签名验证失败");
}
logger.info("resultXml :" + resultXml);
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resultXml.getBytes());
out.flush();
out.close();
}
根据微信的流水号订单查询
@ResponseBody
@RequestMapping("/wxPayOrderQuery")
public Map<String, Object> wxGetOrder(HttpServletRequest request, HttpServletResponse response,String transactionId) throws JDOMException, IOException {
Map<String, Object> resultMap = new HashMap<String, Object>();
SortedMap<Object, Object> reqMap = new TreeMap<Object, Object>();
reqMap.put("appid", ConfigUtil.APPID);
reqMap.put("mch_id", ConfigUtil.MCH_ID);
reqMap.put("nonce_str", PayCommonUtil.CreateNoncestr());
reqMap.put("transaction_id", transactionId); //微信支付成后的订单号
String sign = PayCommonUtil.createSign("UTF-8", reqMap);
reqMap.put("sign", sign);
String requestXML = PayCommonUtil.getRequestXml(reqMap);
String retStr = PayCommonUtil.httpsRequest(ConfigUtil.ORDER_QUERY, "POST", requestXML);
Map<String, Object> map = XMLUtil.doXMLParse(retStr);
String return_code = (String) map.get("return_code");
String result_code = (String) map.get("result_code");
if("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)){
resultMap.putAll(map);
}else{
String err_code = (String) map.get("err_code");
String err_code_des = (String) map.get("err_code_des");
resultMap.put("err_code", err_code);
resultMap.put("err_code_des", err_code_des);
}
return resultMap;
}
接下来是工具类:
OrderUtil
//统一下单的预订单生成
private static String createOrder(HttpServletRequest request, PordOrder order,BigDecimal price) {
SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
parameters.put("appid", ConfigUtil.APPID);
parameters.put("mch_id", ConfigUtil.MCH_ID);
parameters.put("nonce_str", PayCommonUtil.CreateNoncestr());
parameters.put("body", order.getOrderDesc());
String orderNo = order.getOrderNo() +PayCommonUtil.getRandByNum(6); //订单号后面带上6位随机数 解决数据库同一订单号不能多次提交给微信问题
parameters.put("out_trade_no", orderNo); // 订单编号+随机6位随机数
parameters.put("fee_type", "CNY");
parameters.put("total_fee", String.valueOf(price)); //支付总金额
parameters.put("spbill_create_ip", request.getParameter("createIp"));
parameters.put("notify_url", ConfigUtil.NOTIFY_URL);
parameters.put("trade_type", "APP");
// 设置签名
String sign = PayCommonUtil.createSign("UTF-8", parameters);
parameters.put("sign", sign);
// 封装请求参数结束
String requestXML = PayCommonUtil.getRequestXml(parameters);
return requestXML;
}
/** 根据预订单参数,生成ios,安卓使用的必要参数* */
private static SortedMap<Object, Object> createPayInfo(String result) throws JDOMException, IOException {
Map<String, Object> map = XMLUtil.doXMLParse(result);
SortedMap<Object, Object> paramMap = new TreeMap<Object, Object>();
paramMap.put("appid", ConfigUtil.APPID);
paramMap.put("partnerid", ConfigUtil.MCH_ID);
paramMap.put("prepayid", map.get("prepay_id"));
paramMap.put("package", "Sign=WXPay");
paramMap.put("noncestr", PayCommonUtil.CreateNoncestr());
// 本来生成的时间戳是13位,但是ios必须是10位,所以截取了一下
paramMap.put("timestamp", Long
.valueOf(Long.parseLong(String.valueOf(System.currentTimeMillis()).toString().substring(0, 10))));
String sign2 = PayCommonUtil.createSign("UTF-8", paramMap);
paramMap.put("sign", sign2);
return paramMap;
}
MD5Util
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString;
}
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
PayCommonUtil
/**
* 默认16 位随机字符串
*/
public static String CreateNoncestr() {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
String res = "";
for (int i = 0; i < 16; i++) {
Random rd = new Random();
res += chars.charAt(rd.nextInt(chars.length() - 1));
}
return res;
}
/**
* 是否签名正确,
*/
public static boolean isWxSign(String characterEncoding, SortedMap<Object, Object> packageParams) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + ConfigUtil.API_KEY);
String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
String tenpaySign = ((String)packageParams.get("sign")).toUpperCase();
return tenpaySign.equals(mysign);
}
/**
* 签名工具
* @Description:sign签名
* @param characterEncoding 编码格式 UTF-8
* @param parameters 请求参数
* @return
*/
public static String createSign(String characterEncoding,Map<String, Object> parameters) {
StringBuffer sb = new StringBuffer();
Iterator<Entry<String, Object>> it = parameters.entrySet().iterator();
while (it.hasNext()) {
Map.Entry <String,Object>entry = (Map.Entry<String,Object>) it.next();
String key = (String) entry.getKey();
Object value = entry.getValue();//去掉带sign的项
if (null != value && !"".equals(value) && !"sign".equals(key)
&& !"key".equals(key)) {
sb.append(key + "=" + value + "&");
}
}
sb.append("key=" + ConfigUtil.API_KEY);
//注意sign转为大写
return MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
}
/**
* @Description:sign签名
* @param characterEncoding 编码格式
* @param parameters 请求参数
* @return
*/
public static String createSign(String characterEncoding,SortedMap<Object,Object> parameters){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key="+ConfigUtil.API_KEY);
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
/**
* @Description:将请求参数转换为xml格式的string
* @param parameters 请求参数
* @return
*/
public static String getRequestXml(SortedMap<Object,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();
Object ks = entry.getKey();
Object vs = entry.getValue();
String k = ks.toString();
String v = vs.toString();
if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)) {
sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");
}else {
sb.append("<"+k+">"+v+"</"+k+">");
}
}
sb.append("</xml>");
return sb.toString();
}
/**
* @Description:返回给微信的参数
* @param return_code 返回编码
* @param return_msg 返回信息
* @return
*/
public static String setXML(String return_code, String return_msg) {
return "<xml><return_code><![CDATA[" + return_code
+ "]]></return_code><return_msg><![CDATA[" + return_msg
+ "]]></return_msg></xml>";
}
/**
* 发送https请求
* @param requestUrl 请求地址
* @param requestMethod 请求方式(GET、POST)
* @param outputStr 提交的数据
* @return 返回微信服务器响应的信息
*/
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
try {
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = { new WechatTrustManager() };
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
//conn.setSSLSocketFactory(ssf);
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) {
ce.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String urlEncodeUTF8(String source){
String result = source;
try {
result = java.net.URLEncoder.encode(source,"utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return result;
}
/**
* 产生num位的随机数
*/
public static String getRandByNum(int num){
String length = "1";
for(int i=0;i<num;i++){
length += "0";
}
Random rad=new Random();
String result = rad.nextInt(Integer.parseInt(length)) +"";
if(result.length()!=num){
return getRandByNum(num);
}
return result;
}
XMLUtil
/**
* 解析xml,返回第一级元素键值对。
* 如果第一级元素有子节点,
* 则此节点的值是子节点的xml数据。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static SortedMap<String, Object> doXMLParse(String strxml)
throws JDOMException, IOException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if (null == strxml || "".equals(strxml)) {
return null;
}
SortedMap<String, Object> map = new TreeMap<String, Object>();
InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String key = e.getName();
String value = "";
List children = e.getChildren();
if (children.isEmpty()) {
value = e.getTextNormalize();
} else {
value = XMLUtil.getChildrenText(children);
}
map.put(key, value);
}
// 关闭流
in.close();
return map;
}
/**
* 获取子结点的xml
* @param children
* @return
*/
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if (!children.isEmpty()) {
Iterator it = children.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if (!list.isEmpty()) {
sb.append(XMLUtil.getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
WechatTrustManager
/* 证书信任管理器(用于https请求)/
public class WechatTrustManager implements X509TrustManager{
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}