1.控制器
/**
* 后台发给微信服务器(放工具内,实例化然后调用)
* 统一下单
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/wechatPay")
public @ResponseBody
public JSONObject createUnifiedOrder(HttpServletRequest request,
HttpServletResponse response) {
System.out.println("微信 统一下单 接口调用");
//设置最终返回对象
JSONObject resultJson = new JSONObject();
//设置商户订单号
String outTradeNo = request.getParameter("orderNum");
//接受参数(金额)
String amount = request.getParameter("amount");
//接受参数(openid)
String openid = request.getParameter("openid");
//接口调用总金额单位为分换算一下(测试金额改成1,单位为分则是0.01,
//根据自己业务场景判断是转换成float类型还是int类型)
//String amountFen = Integer.valueOf((Integer.parseInt(amount)*100)).toString();
//String amountFen = Float.valueOf((Float.parseFloat(amount)*100)).toString();
//String amountFen = "1";
//创建hashmap(用户获得签名)
SortedMap<String, String> paraMap = new TreeMap<String, String>();
//设置body变量 (支付成功显示在微信支付 商品详情中)
String body = "微信支付JSAPI测试";
//设置随机字符串,自己用随机函数(如UUID类)生成的
String nonceStr = UUID.randomUUID() + RandomStringUtils.randomAlphabetic(30);
//设置请求参数(小程序ID)
paraMap.put("appid", WeChatPaymentUtil.APPLYID);
//设置请求参数(商户号)
paraMap.put("mch_id", WeChatPaymentUtil.MCHID);
//设置请求参数(随机字符串)
paraMap.put("nonce_str", nonceStr);
//设置请求参数(商品描述)
paraMap.put("body", body);
//设置请求参数(商户订单号)
paraMap.put("out_trade_no", outTradeNo);
//设置请求参数(总金额)
paraMap.put("total_fee", amount);
//设置请求参数(终端IP)
paraMap.put("spbill_create_ip", WeChatPaymentUtil.getIpAddress(request));
//设置请求参数(通知地址) ——> payCallback(request,response)
paraMap.put("notify_url", request.getScheme()+request.getServerName()
+request.getServerPort()+"/项目根路径/weChatPayCallback");
//设置请求参数(交易类型)
paraMap.put("trade_type", "JSAPI");
//设置请求参数(openid)(在接口文档中 该参数 是否必填项 但是一定要注意 如果交易类型设置
//成'JSAPI'则必须传入openid)
paraMap.put("openid", openid);
//调用逻辑传入参数按照字段名的 ASCII 码从小到大排序(字典序)
String stringA = formatUrlMap(paraMap, false, false);
//第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,
//再将得到的字符串所有字符转换为大写,得到sign值signValue。(签名)
String sign = MD5Util.MD5Encode(stringA+"&key="+KEY,null).toUpperCase();
//签名和总金额要存一下,用于后面支付结果的验证
request.getSession().setAttribute("weChatAmount",amountFen);
//将参数 编写XML格式
StringBuffer paramBuffer = new StringBuffer();
paramBuffer.append("<xml>");
paramBuffer.append("<appid>"+APPLYID+"</appid>");
paramBuffer.append("<mch_id>"+MCHID+"</mch_id>");
paramBuffer.append("<nonce_str>"+paraMap.get("nonce_str")+"</nonce_str>");
paramBuffer.append("<sign>"+sign+"</sign>");
paramBuffer.append("<body>"+body+"</body>");
paramBuffer.append("<out_trade_no>"+paraMap.get("out_trade_no")+"</out_trade_no>");
paramBuffer.append("<total_fee>"+paraMap.get("total_fee")+"</total_fee>");
paramBuffer.append("<spbill_create_ip>"+paraMap.get("spbill_create_ip")
+"</spbill_create_ip>");
paramBuffer.append("<notify_url>"+paraMap.get("notify_url")+"</notify_url>");
paramBuffer.append("<trade_type>"+paraMap.get("trade_type")+"</trade_type>");
paramBuffer.append("<openid>"+paraMap.get("openid")+"</openid>");
paramBuffer.append("</xml>");
try {
//发送请求(POST)(获得数据包ID)(这有个注意的地方 如果不转码成ISO8859-1
//则会告诉你body不是UTF8编码 就算你改成UTF8编码也一样不好使 所以修改成ISO8859-1)
Map<String,String> map =
WeChatPaymentUtil.doXMLParse(
WeChatPaymentUtil.getRemotePortData(URL, new String(paramBuffer.toString().getBytes()
, "ISO8859-1")));
//应该创建 支付表数据
if(map!=null){
//查找本地数据库的订单支付信息
//如果等于空 则证明是第一次支付,代码省略
if( ){
//创建支付信息对象
//代码省略
System.out.println("微信 统一下单 接口调用成功 并且新增支付信息成功");
return WeChatPaymentUtil.generateSignature(request,map.get("prepay_id"));
}else{
//判断 是否等于一条
if( == 1){
//更新支付信息对象
//代码省略
System.out.println("微信 统一下单 接口调用成功 修改支付信息成功");
return WeChatPaymentUtil.generateSignature(request,map.get("prepay_id"));
}
}
}
} catch (UnsupportedEncodingException e) {
System.out.println("微信 统一下单 异常:"+e.getMessage());
e.printStackTrace();
} catch (Exception e) {
System.out.println("微信 统一下单 异常:"+e.getMessage());
e.printStackTrace();
}
System.out.println("微信 统一下单 失败");
return resultJson;
}
/**
* 通知地址指向的控制器方法(Controller中的方法被微信服务器访问)->
* 接收服务器来的支付结果(这个回调 如果不返回给微信服务器 是否成功回调标示 则会一直回调8次
* 一直到返回成功标示位置)
* @param request
* @param response
*/
@RequestMapping(value = "/weChatPayCallback")
public @ResponseBody
public void payCallback(HttpServletRequest request,HttpServletResponse response) {
System.out.println("微信回调接口方法 start");
System.out.println("微信回调接口 操作逻辑 start");
String inputLine = "";
String notityXml = "";
try {
PrintWriter writer = response.getWriter();
while((inputLine = request.getReader().readLine()) != null){
notityXml += inputLine;
}
//关闭流
request.getReader().close();
System.out.println("微信回调内容信息:"+notityXml);
//解析成Map
Map<String,String> map = doXMLParse(notityXml);
//检查对应业务数据的状态,判断 支付是否成功
if("SUCCESS".equals(map.get("result_code"))){
System.out.println("微信回调返回是否支付成功:是");
//获得 返回的商户订单号
String outTradeNo = map.get("out_trade_no");
System.out.println("微信回调返回商户订单号:"+outTradeNo);
//对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致
//防止数据泄漏导致出现“假通知”,造成资金损失
String sign = request.getSession().getAttribute("weChatSign").toString();
String amount = request.getSession().getAttribute("weChatAmount").toString();
if (sign.equals(map.get("sign"))&&amount.equals(map.get("total_fee"))) {
//访问数据库,修改订单状态
//代码省略
if (订单状态为未付款) {
//修改支付状态
//代码省略
//封装 返回值
StringBuffer buffer = new StringBuffer();
buffer.append("<xml>");
buffer.append("<return_code>SUCCESS</return_code>");
buffer.append("<return_msg>OK</return_msg>");
buffer.append("</xml>");
//给微信服务器返回 成功标示 否则会一直询问 咱们服务器 是否回调成
//返回
writer.print(buffer.toString());
}
}
StringBuffer buffer = new StringBuffer();
buffer.append("<xml>");
buffer.append("<return_code>FAIL</return_code>");
buffer.append("<return_msg>签名失败或金额不符</return_msg>");
buffer.append("</xml>");
writer.print(buffer.toString());
}
StringBuffer buffer = new StringBuffer();
buffer.append("<xml>");
buffer.append("<return_code>FAIL</return_code>");
buffer.append("<return_msg>支付失败</return_msg>");
buffer.append("</xml>");
writer.print(buffer.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
2.工具类
import net.sf.json.JSONObject;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;
public class WeChatPaymentUtil {
//公众账号ID
private final static String APPLYID ="";
//商户号
private final static String MCHID = "";
//key为商户平台设置的密钥key
private final static String KEY = "";
//统一订单接口
private final static String URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 方法名: getRemotePortData
* 描述: 发送远程请求 获得代码示例
* 参数: @param urls 访问路径
* 参数: @param param 访问参数-字符串拼接格式, 例:port_d=10002&port_g=10007&country_a=
* 返回类型: String
*/
public static String getRemotePortData(String urls, String param){
try {
URL url = new URL(urls);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 设置连接超时时间
conn.setConnectTimeout(30000);
// 设置读取超时时间
conn.setReadTimeout(30000);
conn.setRequestMethod("POST");
if(StringUtils.isNotBlank(param)) {
conn.setRequestProperty("X-Requested-With", "XMLHttpRequest");// 主要参数
}
// 需要输出
conn.setDoInput(true);
// 需要输入
conn.setDoOutput(true);
// 设置是否使用缓存
conn.setUseCaches(false);
// 设置请求属性
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Connection", "Keep-Alive");// 维持长连接
conn.setRequestProperty("Charset", "UTF-8");
if(StringUtils.isNotBlank(param)) {
// 建立输入流,向指向的URL传入参数
DataOutputStream dos=new DataOutputStream(conn.getOutputStream());
dos.writeBytes(param);
dos.flush();
dos.close();
}
// 输出返回结果
InputStream input = conn.getInputStream();
int resLen =0;
byte[] res = new byte[1024];
StringBuilder sb=new StringBuilder();
while((resLen=input.read(res))!=-1){
sb.append(new String(res, 0, resLen));
}
return sb.toString();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
* @param strxml
* @return
* @throws
* @throws IOException
*/
@SuppressWarnings("rawtypes")
private Map<String,String> doXMLParse(String strxml) throws Exception {
if(null == strxml || "".equals(strxml)) {
return null;
}
Map<String,String> m = new HashMap<String,String>();
InputStream in = String2Inputstream(strxml);
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 k = e.getName();
String v = "";
List children = e.getChildren();
if(children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = getChildrenText(children);
}
m.put(k, v);
}
//关闭流
in.close();
return m;
}
public static InputStream String2Inputstream(String str) {
return new ByteArrayInputStream(str.getBytes());
}
/**
* 获取子结点的xml
* @param children
* @return String
*/
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(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
/**
* @Title: getIpAddress
* @Description: 获取客户端真实IP地址
* @author yihj
* @param @param request
* @param @param response
* @param @return 参数
* @return String 返回类型
* @throws
*/
public static String getIpAddress(HttpServletRequest request) {
// 避免反向代理不能获取真实地址, 取X-Forwarded-For中第一个非unknown的有效IP字符串
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;
}
/**
* 后台生成签名
* @param request
* @param
* @return 返回给小程序
*/
public static JSONObject generateSignature(HttpServletRequest request,
String prepayid) {
System.out.println("微信 支付接口生成签名 方法开始");
//实例化返回对象
JSONObject resultJson = new JSONObject();
//获得参数(微信统一下单接口生成的prepay_id )
String prepayId = prepayid;
//创建 时间戳
String timeStamp = Long.valueOf(System.currentTimeMillis()).toString();
//创建 随机串
String nonceStr = UUID.randomUUID() + RandomStringUtils.randomAlphabetic(30);
//创建 MD5
String signType = "MD5";
//创建hashmap(用户获得签名)
SortedMap<String, String> paraMap = new TreeMap<String, String>();
//设置(小程序ID)(这块一定要是大写)
paraMap.put("appId", APPLYID);
//设置(时间戳)
paraMap.put("timeStamp", timeStamp);
//设置(随机串)
paraMap.put("nonceStr", nonceStr);
//设置(数据包)
paraMap.put("package", "prepay_id="+prepayId);
//设置(签名方式)
paraMap.put("signType", signType);
//调用逻辑传入参数按照字段名的 ASCII 码从小到大排序(字典序)
String stringA = formatUrlMap(paraMap, false, false);
//第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,
//再将得到的字符串所有字符转换为大写,得到sign值signValue。(签名)
String sign = MD5Util.MD5Encode(stringA+"&key="+KEY,null).toUpperCase();
if(StringUtils.isNotBlank(sign)){
//签名和总金额要存一下,用于后面支付结果的验证
request.getSession().setAttribute("weChatSign",sign);
//返回签名信息
resultJson.put("paySign", sign);
//返回签名方式
resultJson.put("signType", signType);
//返回随机串(这个随机串是新创建的)
resultJson.put("nonceStr", nonceStr);
//返回时间戳
resultJson.put("timeStamp", timeStamp);
//返回数据包
resultJson.put("package", "prepay_id="+prepayId);
System.out.println("微信 支付接口生成签名 设置返回值");
}
System.out.println("微信 支付接口生成签名 方法结束");
return resultJson;
}
/**
*
* 方法用途: 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序),并且生成url参数串
* 实现步骤:
*
* @param paraMap 要排序的Map对象
* @param urlEncode 是否需要URLENCODE
* @param keyToLower 是否需要将Key转换为全小写
* true:key转化成小写,false:不转化
* @return
*/
private static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode
, boolean keyToLower){
String buff = "";
Map<String, String> tmpMap = paraMap;
try
{
List<Map.Entry<String, String>> infoIds =
new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet());
// 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>()
{
@Override
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2)
{
return (o1.getKey()).toString().compareTo(o2.getKey());
}
});
// 构造URL 键值对的格式
StringBuilder buf = new StringBuilder();
for (Map.Entry<String, String> item : infoIds)
{
if (StringUtils.isNotBlank(item.getKey()))
{
String key = item.getKey();
String val = item.getValue();
if (urlEncode)
{
val = URLEncoder.encode(val, "utf-8");
}
if (keyToLower)
{
buf.append(key.toLowerCase() + "=" + val);
} else
{
buf.append(key + "=" + val);
}
buf.append("&");
}
}
buff = buf.toString();
if (buff.isEmpty() == false)
{
buff = buff.substring(0, buff.length() - 1);
}
} catch (Exception e)
{
return null;
}
return buff;
}
}