首先感谢一下【架构之路】盆友!我就是在他的基础之上改的。。再三感谢!
在最下,我会贴上所有的代码。我代码log日志打的非常的多,可能会显得很罗嗦。见谅。
前面的一些纯文字介绍看的会很累。但是建议好好看一下。尤其是我贴上的微信的官方文档。里面其实已经写的非常清楚了。
前言
- 微信公众号内支付在页面端较为简单。只需要调用微信内置的js方法,将微信需要的参数传递过去。
- 与其他支付方式不同的是,公众号支付需要的参数有一项名为open_id。这个参数需要客户端请求固定连接、用户手动同意后才可以获取到。
开发
开发前的准备
-
- 申请微信商户
略
-
- 微信相关设置
IP白名单:首页->基本配置->IP白名单。写入服务器的地址。
进入微信商户中心,下载证书,安装控件、证书,设置API密钥(KEY)。
设置支付授权目录。http://www.*.com/cms/wepay/。
-
- 获取微信商户信息
MCH_ID:首页->微信支付。可以查看到微信支付的商户号。
APPID、APP_SECRET:首页->基本配置;可以找到开发者ID。同时重置APP_SECRET。
-
- 注意1:我们已获得了4个微信支付的常量。包括APPID、APP_SECRET、MCH_ID、KEY;我们再加一个支付类型的常量:TRADE_TYPE=JSAPI。
- 注意2:授权目录要求:1必须为通过ICP备案的地址。2访问项目端口必须为80或334。像我的项目最早部署在8080端口,就一直在报错。其中cms为项目名;wepay为controller的mapping值。下面是微信的要求。
所有使用公众号支付方式发起支付请求的链接地址,都必须在支付授权目录之下;
最多设置5个支付授权目录,且域名必须通过ICP备案;
头部要包含http或https,须细化到二级或三级目录,以左斜杠“/”结尾。
正式开发
步骤1:【微信网页授权】获取用户open_id 。
(在线调试工具)
简单说。设置好授权方式,来获取code。由code获取accessToken,由accessToken再获取用户的open_id和其他信息。
。
由微信的tab直接请求toPay方法。这个过程,首先是发起授权请求。用户允许后,开始获取openid。之后,再跳转至购买页面。
首先强调第一点:token是有区别的。通过code换取的access_token(第二步)和之后拉取用户时的access_token(第四步)不一样。这点尤其需要注意。第二步的token是oauth2加密的token,而第四步的token只是普通的token。具体请看官方文档。其次:token的使用。下面代码中的使用方式为:每次使用时,都去微信api申请一个token。但是微信api每日调用token接口的次数是有限的(一天1万次)。所以需要更精确的对其进行控制。可以将token存入数据库。使用定时任务刷新token。我采用了比较简单的方式,将信息存到了session里面。取出时,判断下时间即可。
步骤2 :【统一下单】准备工作完成。开始正式支付
、
本项目操作过程为:用户手动点击支付按钮->获取支付需要的参数->将预付款订单参数传递给微信。
1 function onBridgeReady(){ 2 WeixinJSBridge.invoke( 3 'getBrandWCPayRequest', { 4 "appId":appId, //公众号名称,由商户传入 5 "paySign":sign, //微信签名 6 "timeStamp":timeStamp, //时间戳,自1970年以来的秒数 7 "nonceStr":nonceStr , //随机串 8 "package":packageStr, //预支付交易会话标识 9 "signType":signType //微信签名方式10 },11 function(res){12 alert(JSON.stringify(res));//出错可以在这里看看.....13 if(res.err_msg == "get_brand_wcpay_request:ok" ) {14 //window.location.replace("index.html");15 alert('支付成功');16 }else if(res.err_msg == "get_brand_wcpay_request:cancel"){17 alert('支付取消');18 }else if(res.err_msg == "get_brand_wcpay_request:fail" ){19 alert('支付失败');20 }21 //使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。22 }23 );24 }
步骤3:【异步结果通知】微信支付异步回调通知
-
- 该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。
- 通知url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”
如果出现签名错误相关错误,我是用的此页面的校验工具进行测试的。。
dto实体类。
用户信息类。如果只做支付不保存用户信息,此类可无。
public class UserInfo { private String openid; private String unionid; private String headimgurl; private String nickname; private String refreshtoken; private int siteid;}
微信常量信息类。
public class WeChatConst { // 公众号支付APPID public static final String APPID = WePropertieUtil.getValue("APPID"); // 公众号支付AppSecret public static final String APP_SECRET = WePropertieUtil.getValue("APP_SECRET"); // 公众号支付商户号 public static final String MCH_ID = WePropertieUtil.getValue("MCH_ID"); // 商户后台配置的一个32位的key,位置:微信商户平台-账户中心-API安全 public static final String KEY = WePropertieUtil.getValue("KEY"); // 交易类型 public static final String TRADE_TYPE = WePropertieUtil.getValue("TRADE_TYPE"); // 根路径 public static final String BASE_URL = WePropertieUtil.getValue("BASE_URL");}
用户信息类。如果只做支付不保存用户信息,此类可无。
public class WeixinLoginUser implements Serializable { private static final long serialVersionUID = -8449856597137213512L; private String openID; private String unionID; private String headImageUrl; private String nickName; private String refreshToken; private int siteID;}
微信支付参数。事实上我只用了里面极少一部分。
public class WxPaySendData { /** * 公众账号ID */ private String appid; /** * 附加数据 */ private String attach; /** * 商品描述 */ private String body; /** * 商户号 */ private String mch_id; /** * 随机字符串 */ private String nonce_str; /** * 通知地址 */ private String notiry_url; /** * 商户订单号 */ private String out_trade_no; /** * 标价金额 */ private String total_fee; /** * 交易类型 */ private String trade_type; /** * 终端IP */ private String spbill_create_ip; /** * 用户标识 */ private String openid; /** * 签名 */ private String sign; /** * 预支付id */ private String prepay_id; /** * 签名类型:MD5 */ private String signType; /** * 时间戳 */ private String timeStamp; /** * 微信支付时用到的prepay_id */ private String packageStr; private String return_code; private String return_msg; private String result_code; private String bank_type; private Integer cash_fee; private String fee_type; private String is_subscribe; private String time_end; /** * 微信支付订单号 */ private String transaction_id;}
所用的工具类
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.URL;import java.net.URLConnection;import java.util.Iterator;import java.util.Map;import java.util.Map.Entry;import javax.servlet.http.HttpServletRequest;/** * @Description http请求工具类 */public class HttpUtil { /** * 向指定URL发送GET方法的请求 * * @param url * 发送请求的URL * @param param * 请求Map参数,请求参数应该是 {"name1":"value1","name2":"value2"}的形式。 * @param charset * 发送和接收的格式 * @return URL 所代表远程资源的响应结果 */ public static String sendGet(String url, Mapmap) { StringBuffer sb = new StringBuffer(); // 构建请求参数 if (map != null && map.size() > 0) { Iterator it = map.entrySet().iterator(); // 定义迭代器 while (it.hasNext()) { Map.Entry er = (Entry ) it.next(); sb.append(er.getKey()); sb.append("="); sb.append(er.getValue()); sb.append("&"); } } return sendGet(url, sb.toString()); } /** * 向指定URL发送POST方法的请求 * * @param url * 发送请求的URL * @param param * 请求Map参数,请求参数应该是 {"name1":"value1","name2":"value2"}的形式。 * @param charset * 发送和接收的格式 * @return URL 所代表远程资源的响应结果 */ public static String sendPost(String url, Map map) { StringBuffer sb = new StringBuffer(); // 构建请求参数 if (map != null && map.size() > 0) { for (Entry e : map.entrySet()) { sb.append(e.getKey()); sb.append("="); sb.append(e.getValue()); sb.append("&"); } } return sendPost(url, sb.toString()); } /** * 向指定URL发送GET方法的请求 * * @param url * 发送请求的URL * @param param * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @param charset * 发送和接收的格式 * @return URL 所代表远程资源的响应结果 */ public static String sendGet(String url, String param) { String result = ""; String line; StringBuffer sb = new StringBuffer(); BufferedReader in = null; try { String urlNameString = url + "?" + param; URL realUrl = new URL(urlNameString); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 设置请求格式 conn.setRequestProperty("contentType", "utf-8"); conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); // 设置超时时间 conn.setConnectTimeout(600); conn.setReadTimeout(600); // 建立实际的连接 conn.connect(); // 定义 BufferedReader输入流来读取URL的响应,设置接收格式 in = new BufferedReader(new InputStreamReader( conn.getInputStream(), "utf-8")); while ((line = in.readLine()) != null) { sb.append(line); } result = sb.toString(); } catch (Exception e) { System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } // 使用finally块来关闭输入流 finally { try { if (in != null) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return result; } /** * 向指定 URL 发送POST方法的请求 * * @param url * 发送请求的 URL * @param param * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @param charset * 发送和接收的格式 * @return 所代表远程资源的响应结果 */ public static String sendPost(String url, String param) { PrintWriter out = null; BufferedReader in = null; String result = ""; String line; StringBuffer sb = new StringBuffer(); try { URL realUrl = new URL(url); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 设置请求格式 conn.setRequestProperty("contentType", "utf-8"); conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); // 设置超时时间 conn.setConnectTimeout(600); conn.setReadTimeout(600); // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); // 获取URLConnection对象对应的输出流 out = new PrintWriter(conn.getOutputStream()); // 发送请求参数 out.print(param); // flush输出流的缓冲 out.flush(); // 定义BufferedReader输入流来读取URL的响应 设置接收格式 in = new BufferedReader(new InputStreamReader( conn.getInputStream(), "utf-8")); while ((line = in.readLine()) != null) { sb.append(line); } result = sb.toString(); } catch (Exception e) { System.out.println("发送 POST请求出现异常!" + e); e.printStackTrace(); } // 使用finally块来关闭输出流、输入流 finally { try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException ex) { ex.printStackTrace(); } } return result; } public static String getRemoteIpAddr(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(); } // 如果手机是双卡双待,那么会获取到两个IP。我们仅需要有一个。 return ip.split(",")[0]; } }
import java.security.MessageDigest; public class MD5Util { private final static String[] hexDigits = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; /** * MD5加密解密工具类 * * @param b * 字节数组 * @return 16进制字串 */ public 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 + n; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } public static String MD5Encode(String origin) { String resultString = null; try { resultString = new String(origin); MessageDigest md = MessageDigest.getInstance("MD5"); resultString = byteArrayToHexString(md.digest(resultString .getBytes("utf-8"))); } catch (Exception ex) { } return resultString; } public static boolean isValidate(String input,String output){ boolean status = false; if(MD5Util.MD5Encode(input).equals(output)){ status = true; }else{ status = false; } return status; } }
import java.io.ByteArrayOutputStream;import java.io.InputStream;import java.nio.charset.Charset;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Random;import java.util.SortedMap;import java.util.TreeMap;import org.apache.http.HttpResponse;import org.apache.http.client.methods.HttpPost;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.DefaultHttpClient;import org.apache.http.util.EntityUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.meihe.dto.wePay.WeChatConst;@SuppressWarnings("deprecation")public class WeChatUtil { private static final Logger log = LoggerFactory.getLogger(WeChatUtil.class); /** * 统一下单 获得PrePayId * * @param body * 商品或支付单简要描述 * @param out_trade_no * 商户系统内部的订单号,32个字符内、可包含字母 * @param total_fee * 订单总金额,单位为分 * @param IP * APP和网页支付提交用户端ip * @param notify_url * 接收微信支付异步通知回调地址 * @param openid * 用户openId * @throws Exception */ public static String unifiedorder(String body, String out_trade_no, Integer total_fee, String ip, String notify_url, String openId) throws Exception { log.debug(">>>>>>>>>>>>>>>>>开始准备统一下单流程...."); /** * 组装请求参数 按照ASCII排序 */ SortedMap
import java.io.IOException;import java.text.MessageFormat;import java.util.Properties;/** * 读取微信properties配置文件 * * */public class WePropertieUtil { private static Properties env; private static Properties getEnv() { try { if (env == null) { env = new Properties(); env.load(WePropertieUtil.class.getClassLoader().getResourceAsStream("config/wechat-const.properties")); } } catch (IOException e) { e.printStackTrace(); } return env; } /** * 获取对应的key * * @param key * @return */ public static String getValue(String key) { if (env == null) { return getEnv().getProperty(key); } else { return env.getProperty(key); } } /** * 获取key对应的值,并替换其中的参数 * * @param key * @param arguments * @return */ public static String getValue(String key, Object... arguments) { return MessageFormat.format(getValue(key), arguments); } }
import java.util.Iterator;import java.util.Map;import java.util.Set;import java.util.SortedMap;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class WxSign { private static final Logger log = LoggerFactory.getLogger(WeChatUtil.class); /** * 签名封装类。创建签名。 * @param parameters * @param key * @return * @throws Exception */ @SuppressWarnings("rawtypes") public static String createSign(SortedMapparameters,String key) throws Exception { log.info(">>>>>>>>>>>>>>>>>开始封装数据,创建签名...."); StringBuffer sb = new StringBuffer(); // 所有参与传参的参数按照accsii排序(升序) 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=" + key); log.info(">>>>>>>>>>>>>>>>>封装数据{}",sb.toString()); String str = sb.toString(); log.info(str); String sign = MD5Util.MD5Encode(str).toUpperCase(); log.info(">>>>>>>>>>>>>>>>>封装数据完成,签名为:{}",sign); return sign; } }
import java.text.ParseException;import java.text.SimpleDateFormat;import java.time.Instant;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.ZoneId;import java.time.format.DateTimeFormatter;import java.util.Date;import java.util.TimeZone;import org.apache.commons.lang3.StringUtils;public abstract class DateTimeUtils { /** * 获取今天日期 * * @return */ public static String getToday(String format) { if (StringUtils.isEmpty(format)) { format = "yyyyMMdd"; } return LocalDate.now().format(DateTimeFormatter.ofPattern(format)); } /** * 获取当前时间 * * @param format * @return */ public static String getCurrentTime(String format) { if (StringUtils.isEmpty(format)) { format = "HHmmss"; } return LocalTime.now().format(DateTimeFormatter.ofPattern(format)); } /** * 将字符串转换成LocalDate格式 * * @param format * @return * @throws ParseException */ public static LocalDate toLocalDate(String LocalDate) throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); Date date = sdf.parse(LocalDate); Instant instant = date.toInstant(); ZoneId zone = ZoneId.systemDefault(); LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); LocalDate localDate = localDateTime.toLocalDate(); return localDate; } /** * 将字符串转换成LocalTime格式 * * @param format * @return * @throws ParseException */ public static LocalTime toLocalTime(String LocalTime) throws ParseException{ SimpleDateFormat sdf = new SimpleDateFormat("HHmmss"); sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); Date time = sdf.parse(LocalTime); Instant instant = time.toInstant(); ZoneId zone = ZoneId.systemDefault(); LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); LocalTime localTime = localDateTime.toLocalTime(); return localTime; }}
准备完成!开始进行业务操作。
代码流程:页面->controller->service;
支付页面:
// 页面在执行成功之前,pay方法只允许调用一次。 clickstatus=true; var sign ; var appId ; var timeStamp ; var nonceStr ; var packageStr ; var signType, var url = 'http://**/cms/wepay/'; function pay(){ if(clickstatus){ clickstatus=false; $.ajax({ type:"post", url:url+'pay', dataType:"json", data:{openId:'${openId}',totalFee:totalFee,pointAmount:pointAmount,memberId:$.cookie('memberId'),cellphone: $.cookie('cellphone'),accessToken: $.cookie('accessToken')}, success:function(data) { if(data.result_code == 'SUCCESS'){ appId = data.appid; sign = data.sign; timeStamp = data.timeStamp; nonceStr = data.nonce_str; packageStr = data.packageStr; signType = data.signType; // 调用微信支付控件 if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', onBridgeReady); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } }else{ onBridgeReady(); } }else{ alert("统一下单失败"); } } }) } } function onBridgeReady(){ WeixinJSBridge.invoke('getBrandWCPayRequest', { "appId":appId, "timeStamp":timeStamp, "nonceStr":nonceStr, "package": packageStr, "signType":signType, "paySign": sign },function (res) { if (res.err_msg == "get_brand_wcpay_request:ok") { window.location.href = url+'paySuccess'; } else if (res.err_msg == "get_brand_wcpay_request:fail") { window.location.href = url+'payFailure'; } else if (res.err_msg == "get_brand_wcpay_request:cancel") { alert("支付取消"); } }) }
Controller
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.servlet.ModelAndView;import com.meihe.dto.wePay.WxPaySendData;import com.meihe.service.wePay.IPreparedToPayService;@RestController@RequestMapping("wepay")public class wePayController { @Autowired private IPreparedToPayService preparedToPayService; /** * 跳转支付界面,将code带过去 **/ @ResponseBody @RequestMapping("toPay") public ModelAndView toPay(HttpServletRequest request,HttpServletResponse response){ ModelAndView res = new ModelAndView(); try { res = preparedToPayService.getUserOpenId(request, response); } catch (Exception e) { e.printStackTrace(); } return res; } /** * 点击确认充值 统一下单,获得预付id(prepay_id) * @param request * @param response * @return */ @ResponseBody @RequestMapping("pay") public WxPaySendData prePay(HttpServletRequest request,HttpServletResponse response,String openId, String totalFee, String pointAmount, String memberId){ WxPaySendData data = new WxPaySendData(); try { data = preparedToPayService.prePay(request, response, openId,totalFee,pointAmount,memberId); } catch (Exception e) { e.printStackTrace(); } return data; } /** * 微信支付回调接口 * @param request * @param response * @throws Exception */ @ResponseBody @RequestMapping("callback") public void callBack(HttpServletRequest request, HttpServletResponse response) { try { preparedToPayService.callBack(request, response); } catch (Exception e) { e.printStackTrace(); } } /** * 支付成功 **/ @ResponseBody @RequestMapping("paySuccess") public ModelAndView paySuccess(HttpServletRequest request,HttpServletResponse response){ return new ModelAndView("wepay/paySuccess"); } /** * 支付失败 **/ @ResponseBody @RequestMapping("payFailure") public ModelAndView payFailure(HttpServletRequest request,HttpServletResponse response){ return new ModelAndView("wepay/payFailure"); }
Service
import java.io.IOException;import java.io.InputStream;import java.net.URLEncoder;import java.util.Map;import java.util.SortedMap;import java.util.TreeMap;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.commons.io.IOUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.web.servlet.ModelAndView;import com.alibaba.fastjson.JSON;import com.meihe.dto.AjaxResult;import com.meihe.dto.wePay.UserInfo;import com.meihe.dto.wePay.WeChatConst;import com.meihe.dto.wePay.WeixinLoginUser;import com.meihe.dto.wePay.WxPaySendData;import com.meihe.model.marketing.MarketingOrder;import com.meihe.service.marketing.IMarketingOrderService;import com.meihe.service.wePay.IOauthService;import com.meihe.service.wePay.IPreparedToPayService;import com.meihe.utils.wePayUtils.HttpUtil;import com.meihe.utils.wePayUtils.WeChatUtil;import com.meihe.utils.wePayUtils.WxSign;import com.thoughtworks.xstream.XStream;import com.thoughtworks.xstream.io.xml.DomDriver;@Servicepublic class PreparedToPayServiceImpl implements IPreparedToPayService { @Autowired private IOauthService oauth = null; @Autowired private IMarketingOrderService mOrder = null; private static final Logger log = LoggerFactory.getLogger(PreparedToPayServiceImpl.class); @Override public ModelAndView getUserOpenId(HttpServletRequest request,HttpServletResponse response) throws Exception { log.info(">>>>>>>>>>>>>>>>>准备获取用户code...."); ModelAndView modelAndView = new ModelAndView(); @SuppressWarnings("deprecation") String redirecUri = URLEncoder.encode(WeChatConst.BASE_URL + "toPay"); String code = null; if (request.getParameter("code") != null) { code = request.getParameter("code"); } if (code == null) { log.info(">>>>>>>>>>>>>>>>>用户首次登陆,授权中...准备获取code...."); String siteURL = "redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid=" +WeChatConst.APPID+"&redirect_uri="+redirecUri+ "&response_type=code&scope=snsapi_userinfo&state=1234#wechat_redirect"; return new ModelAndView(siteURL); } code = request.getParameter("code"); log.debug(">>>>>>>>>>>>>>>>>获得用户code[{}]成功!准备获取用户信息...",code); WeixinLoginUser weixinLoginUser = getWeixinLoginUser(code,request,response); if(null == weixinLoginUser){ log.debug(">>>>>>>>>>>>>>>>>code[{}]已被使用,重新获取code...",code); // 进入页面后,如果用户刷新,那么会提示code被使用的错误。此判断主要应对获取用户信息后刷新的操作。 String siteURL = "redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid=" +WeChatConst.APPID+"&redirect_uri="+redirecUri+ "&response_type=code&scope=snsapi_userinfo&state=1234#wechat_redirect"; return new ModelAndView(siteURL); } modelAndView.addObject("openId", weixinLoginUser.getOpenID()); request.setAttribute("openId", weixinLoginUser.getOpenID()); log.debug(">>>>>>>>>>>>>>>>>准备工作完成!跳转至购买页面..."); String viewName = "wepay/payIndex"; modelAndView.setViewName(viewName); return modelAndView; } /** * 获取微信授权登陆用户 * * @param code * @return * @throws Exception */ private WeixinLoginUser getWeixinLoginUser(String code,HttpServletRequest request,HttpServletResponse response) throws Exception { String str = oauth.getToken(code, WeChatConst.APPID,WeChatConst.APP_SECRET); String openID = (String) JSON.parseObject(str, Map.class).get("openid"); String accessToken = (String) JSON.parseObject(str, Map.class).get("access_token"); String refreshToken = (String) JSON.parseObject(str, Map.class).get("refresh_token"); if(null == openID || null == accessToken || null == refreshToken){ return null; } log.debug(">>>>>>>>>>>>>>>>>openid[{}],access_token[{}],refresh_token[{}]..转JSON为String中...",openID,accessToken,refreshToken); // 获取到的用户信息。暂时没用。后台暂时只保存一个openId。 // 支付只需要用户的openId即可完成。这个方法可以在此结束。 UserInfo userInfo = oauth.getSnsUserInfo(openID, accessToken); WeixinLoginUser weixinLoginUser = new WeixinLoginUser(); weixinLoginUser.setOpenID(openID); weixinLoginUser.setUnionID(userInfo.getUnionid()); weixinLoginUser.setHeadImageUrl(userInfo.getHeadimgurl()); weixinLoginUser.setNickName(userInfo.getNickname()); weixinLoginUser.setRefreshToken(refreshToken); log.debug(">>>>>>>>>>>>>>>>>获取用户信息中完成!"); return weixinLoginUser; } @Override public WxPaySendData prePay(HttpServletRequest request,HttpServletResponse response, String openId, String totalFee, String pointAmount, String memberId) throws Exception { log.debug(">>>>>>>>>>>>>>>>>预备工作完成。开始准备预付订单信息..."); WxPaySendData result = new WxPaySendData(); MarketingOrder marketingOrder = new MarketingOrder(); // 商户订单号 String out_trade_no = WeChatUtil.getOut_trade_no(); // 产品价格,单位:分 Double total_fee = Double.parseDouble(totalFee)*100; // 客户端IP String ip = HttpUtil.getRemoteIpAddr(request); // 支付成功后回调的url地址 String notify_url = WeChatConst.BASE_URL+"callback"; // 商品或支付产品的简要描述 String body = pointAmount+"积分"; log.debug(">>>>>>>>>>>>>>>>>预付订单信息准备完成。"); log.debug(">>>>>>>>>>>>>>>>>订单号:[{}],价格:[{}],客户端IP[{}],回调URL:[{}],商品支付信息:[{}]",out_trade_no,total_fee,ip,notify_url,body); //统一下单 String strResult = WeChatUtil.unifiedorder(body, out_trade_no, total_fee.intValue(), ip, notify_url,openId); log.debug(">>>>>>>>>>>>>>>>>解析统一下单XML...."); XStream stream = new XStream(new DomDriver()); stream.alias("xml", WxPaySendData.class); WxPaySendData wxReturnData = (WxPaySendData)stream.fromXML(strResult); log.debug(">>>>>>>>>>>>>>>>>统一下单XML解析完成....[{}]",wxReturnData.toString()); //两者都为SUCCESS才能获取prepay_id if(wxReturnData.getResult_code().equals("SUCCESS") &&wxReturnData.getReturn_code().equals("SUCCESS") ){ log.debug(">>>>>>>>>>>>>>>>>统一下单请求成功!开始准备正式支付...."); //时间戳、随机字符串 String timeStamp = WeChatUtil.getTimeStamp(); String nonce_str = WeChatUtil.getNonceStr(); // 注:上面这两个参数,一定要拿出来作为后续的value,不能每步都创建新的时间戳跟随机字符串,不然H5调支付API,会报签名参数错误 result.setResult_code(wxReturnData.getResult_code()); result.setAppid(WeChatConst.APPID); result.setTimeStamp(timeStamp); result.setNonce_str(nonce_str); result.setPackageStr("prepay_id="+wxReturnData.getPrepay_id()); result.setSignType("MD5"); // 第二次签名,将微信返回的数据再进行签名 SortedMapsignMap = new TreeMap (); signMap.put("appId", WeChatConst.APPID); signMap.put("nonceStr", nonce_str); // 值为:prepay_id=xxx signMap.put("package", "prepay_id="+wxReturnData.getPrepay_id()); signMap.put("signType", "MD5"); signMap.put("timeStamp", timeStamp); log.debug(">>>>>>>>>>>>>>>>>根据预支付订单号再次进行签名...."); String paySign = WxSign.createSign(signMap, WeChatConst.KEY); log.debug(">>>>>>>>>>>>>>>>>签名完成!准备生成本地支付订单...."); result.setSign(paySign); } else { result.setResult_code("fail"); } log.debug(">>>>>>>>>>>>>>>>>开始生成一条新的本地支付订单。"); marketingOrder.setPaymentType(1); marketingOrder.setMemberId(Integer.parseInt(memberId)); marketingOrder.setOutTradeNo(out_trade_no); marketingOrder.setTotalFee(total_fee.toString()); marketingOrder.setPointAmount(pointAmount); marketingOrder.setOpenId(openId); AjaxResult ajaxResult = mOrder.insertMarketingOrder(marketingOrder); if(!ajaxResult.getSuccess()){ result.setResult_code("fail"); } log.debug(">>>>>>>>>>>>>>>>>开始正式支付!"); return result; } @Override public void callBack(HttpServletRequest request, HttpServletResponse response) throws Exception{ log.debug(">>>>>>>>>>>>>>>>>微信开始回调本地。校验签名。准备校验签名...."); response.setContentType("text/xml;charset=UTF-8"); try { InputStream is = request.getInputStream(); String result = IOUtils.toString(is, "UTF-8"); if("".equals(result)){ response.getWriter().write(" "); return ; } // 解析xml XStream stream = new XStream(new DomDriver()); stream.alias("xml", WxPaySendData.class); WxPaySendData wxPaySendData = (WxPaySendData)stream.fromXML(result); System.out.println(wxPaySendData.toString()); String appid = wxPaySendData.getAppid(); String mch_id =wxPaySendData.getMch_id(); String nonce_str = wxPaySendData.getNonce_str(); String out_trade_no = wxPaySendData.getOut_trade_no(); String total_fee = wxPaySendData.getTotal_fee(); String trade_type = wxPaySendData.getTrade_type(); String openid =wxPaySendData.getOpenid(); String return_code = wxPaySendData.getReturn_code(); String result_code = wxPaySendData.getResult_code(); String bank_type = wxPaySendData.getBank_type(); Integer cash_fee = wxPaySendData.getCash_fee(); String fee_type = wxPaySendData.getFee_type(); String is_subscribe = wxPaySendData.getIs_subscribe(); String time_end = wxPaySendData.getTime_end(); String transaction_id = wxPaySendData.getTransaction_id(); String sign = wxPaySendData.getSign(); //签名验证 SortedMap parameters = new TreeMap (); parameters.put("appid",appid); parameters.put("mch_id",mch_id); parameters.put("nonce_str",nonce_str); parameters.put("out_trade_no",out_trade_no); parameters.put("total_fee",total_fee); parameters.put("trade_type",trade_type); parameters.put("openid",openid); parameters.put("return_code",return_code); parameters.put("result_code",result_code); parameters.put("bank_type",bank_type); parameters.put("cash_fee",cash_fee); parameters.put("fee_type",fee_type); parameters.put("is_subscribe",is_subscribe); parameters.put("time_end",time_end); parameters.put("transaction_id",transaction_id); String sign2 = WxSign.createSign(parameters, WeChatConst.KEY); if(sign.equals(sign2)){ log.debug(">>>>>>>>>>>>>>>>>校验签名完成。正常交易。开始校验支付状态..."); if(return_code.equals("SUCCESS") && result_code.equals("SUCCESS")){ log.debug(">>>>>>>>>>>>>>>>>校验支付状态完成。支付成功!"); // 业务逻辑(先判断数据库中订单号是否存在,并且订单状态为未支付状态) MarketingOrder marketingOrder = new MarketingOrder(); marketingOrder.setOutTradeNo(out_trade_no); marketingOrder.setPayAmount(total_fee); AjaxResult affordOrder = mOrder.affordOrder(marketingOrder); if(!affordOrder.getSuccess()){ response.getWriter().write(" "); } // TODO:这个有什么用吗?暂未确定... request.setAttribute("out_trade_no", out_trade_no); // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了. log.debug(">>>>>>>>>>>>>>>>>本地异步确认支付成功!交易结束!"); response.getWriter().write(" "); }else{ log.debug(">>>>>>>>>>>>>>>>>本地异步确认支付失败!交易结束!"); response.getWriter().write(" "); } }else{ log.debug(">>>>>>>>>>>>>>>>>校验签名完成。异常交易!结束交易流程。"); response.getWriter().write(" "); } response.getWriter().flush(); response.getWriter().close(); return ; } catch (IOException e) { e.printStackTrace(); } } }
import java.util.HashMap;import java.util.Map;import org.apache.commons.lang3.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Service;import com.alibaba.fastjson.JSONObject;import com.meihe.dto.wePay.UserInfo;import com.meihe.service.wePay.IOauthService;import com.meihe.utils.wePayUtils.HttpUtil;@Servicepublic class OauthServiceImpl implements IOauthService { private static final Logger log = LoggerFactory.getLogger(PreparedToPayServiceImpl.class); // private static final String CODE_URI = "http://open.weixin.qq.com/connect/oauth2/authorize"; private static final String TOKEN_URI = "https://api.weixin.qq.com/sns/oauth2/access_token"; private static final String REFRESH_TOKEN_URI = "https://api.weixin.qq.com/sns/oauth2/refresh_token"; private static final String SNS_USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo"; public String getToken(String code, String appid, String secret) throws Exception { log.debug(">>>>>>>>>>>>>>>>>准备根据code获取用户open_id/accessToken..",code,appid,secret); Mapparams = new HashMap (); params.put("appid", appid); params.put("secret", secret); params.put("code", code); params.put("grant_type", "authorization_code"); String result = HttpUtil.sendGet(TOKEN_URI,params); log.debug(">>>>>>>>>>>>>>>>>获取信息完成[{}]...",result); return result; } public String getRefreshToken(String refreshToken, String appid,String secret) throws Exception { Map params = new HashMap (); params.put("appid", appid); params.put("grant_type", "refresh_token"); params.put("refresh_token", refreshToken); return HttpUtil.sendGet(REFRESH_TOKEN_URI, params); } public UserInfo getSnsUserInfo(String openid, String accessToken)throws Exception { Map params = new HashMap (); log.info(">>>>>>>>>>>>>>>>>转格式成功!准备根据open_id/accessToken获取用户信息...!"); params.put("access_token", accessToken); params.put("openid", openid); params.put("lang", "zh_CN"); log.debug(">>>>>>>>>>>>>>>>>token:[{}],openid[{}].开始连接微信获取信息..",accessToken,openid); String jsonStr = HttpUtil.sendGet(SNS_USER_INFO_URL, params); log.debug(">>>>>>>>>>>>>>>>>成功获取信息[{}]。转JSON为UserInfo..",jsonStr); if (StringUtils.isNotEmpty(jsonStr)) { JSONObject obj = JSONObject.parseObject(jsonStr); if (obj.get("errcode") != null) { throw new Exception(obj.getString("errmsg")); } UserInfo user = (UserInfo) JSONObject.toJavaObject(obj,UserInfo.class); log.debug(">>>>>>>>>>>>>>>>>转UserInfo格式完成!"); return user; } return null; }}