接着上文,做微信支付(h5)需要微信登录的环节。不多讲
在微信登录的时候可以获取到微信对于的微信的公众号的唯一标识即openid,这里我是将获取的openid存放在用户表的。需要将他加入参数列表进行生成预支付的订单号。
话归正题,微信支付需要做的准备工作。需要登录微信商户平台设置好秘钥。
设置支付安全目录,就是你要拉起支付的项目域名。
在商户平台获取到支付所需要的证书(微信升级后需要自己配置)
用微信的工具生成证书并放在服务器内。
获取商户号和秘钥
现在准备工具工作做完。
项目实战
1.导入jar包 wx 1.0.0.jar
2.生成参数列表生成预支付的订单id ,直接贴代码。
@SuppressWarnings("unchecked")
@RequestMapping(value = "gopay")
@ResponseBody
public synchronized String Gopay(HttpServletRequest request,Model model,HttpServletResponse resp) throws Exception {
SetRespHander.setHander(resp);
User currentUser = (User) request.getSession().getAttribute(Constants.FRONT_USER_SESSION);
if(currentUser==null){
return "{\"status\": 403}";
}
String id=request.getParameter("id");
String openId=currentUser.getWecatopenid();
double je=price;
if(activity.getHeadcount()-activity.getNowcount()<rs){//报名人数超出限制
return "{\"status\": 401,\"message\": \"报名人数超出限制\"}";
}
System.out.println(je);
// String totalPrices = request.getParameter("totalPrice");//商品价格(单位:元)
// String url = request.getParameter("url");//支付成功回调地址(默认前缀http://域名/kkqshop/f/)
// double totalPrice = Double.parseDouble(totalPrices);
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"+"f/";//项目路径
log.info("basePath=" + basePath);
/** 总金额(分为单位) */
int total = (int) (je*100);
// int total=1;
System.out.println(total);
SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
/** 公众号APPID */
parameters.put("appid", APPID);
/** 商户号 */
parameters.put("mch_id", MCH_ID);
/** 随机字符串 */
parameters.put("nonce_str", WXUtil.getNonceStr());
/** 商品名称 */
parameters.put("body", "商品");
//String oppId = user.getWxOpenId();
/** 当前时间 yyyyMMddHHmmss */
String currTime = TenpayUtil.getCurrTime();
/** 8位日期 */
String strTime = currTime.substring(8, currTime.length());
/** 四位随机数 */
String strRandom = TenpayUtil.buildRandom(4) + "";
/** 订单号 */
parameters.put("out_trade_no", strTime + strRandom);
/** 订单金额以分为单位,只能为整数 */
parameters.put("total_fee", total);
/** 客户端本地ip */
parameters.put("spbill_create_ip", request.getRemoteAddr());
/** 支付回调地址 */
parameters.put("notify_url", basePath + NOTIFY_URL);
/** 支付方式为JSAPI支付 */
parameters.put("trade_type", "JSAPI");
/** 用户微信的openid,当trade_type为JSAPI的时候,该属性字段必须设置 */
parameters.put("openid", openId);
/** MD5进行签名,必须为UTF-8编码,注意上面几个参数名称的大小写 */
String sign = createSign("UTF-8", parameters);
parameters.put("sign", sign);
String result =WeixinJSBridgeController.getResult(parameters);
try {
/** 解析微信返回的信息,以Map形式存储便于取值 */
Map<String, String> map = XMLUtil.doXMLParse(result);
SortedMap<Object, Object> params = new TreeMap<Object, Object>();
params.put("appId", APPID);
params.put("timeStamp", WXUtil.getTimeStamp());
params.put("nonceStr", WXUtil.getNonceStr());
/**
* 获取预支付单号prepay_id后,需要将它参与签名。
* 微信支付最新接口中,要求package的值的固定格式为prepay_id=...
*/
params.put("package", "prepay_id=" + map.get("prepay_id"));
/** 微信支付新版本签名算法使用MD5,不是SHA1 */
params.put("signType", "MD5");
/**
* 获取预支付prepay_id之后,需要再次进行签名,参与签名的参数有:appId、timeStamp、nonceStr、package、signType.
* 主意上面参数名称的大小写.
* 该签名用于前端js中WeixinJSBridge.invoke中的paySign的参数值
*/
String paySign = createSign("UTF-8", params);
params.put("paySign", paySign);
/** 预支付单号,前端ajax回调获取。由于js中package为关键字,所以,这里使用packageValue作为key。 */
params.put("packageValue", "prepay_id=" + map.get("prepay_id"));
/** 付款成功后,微信会同步请求我们自定义的成功通知页面,通知用户支付成功 */
// params.put("sendUrl", basePath + "pay/paysuccess?totalPrice=" + totalPrice);
// params.put("sendUrl", url);
/** 获取用户的微信客户端版本号,用于前端支付之前进行版本判断,微信版本低于5.0无法使用微信支付 */
String userAgent = request.getHeader("user-agent");
char agent = userAgent.charAt(userAgent.indexOf("MicroMessenger") + 15);
params.put("agent", new String(new char[] { agent }));
params.put("results", "");
params.put("commodityName", "商品");
params.put("out_trade_no", strTime + strRandom);
params.put("total_fee", total);
return "{\"status\": 200,\"data\": "+json+"}";
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
这里要注意生成随机字符串WXUtil.getTimeStamp() ios要求10位
生成预支付订单的方法
public static String getResult(SortedMap<Object, Object> parameters) throws Exception{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// InputStream instream = WeixinJSBridgeController.class.getResourceAsStream("D://apiclient_cert.p12");//指定证书文件
FileInputStream instream = new FileInputStream(new File("C:/apache-tomcat-7.0.91/webapps/1242390902_20181018_cert.p12"));
try {
keyStore.load(instream, MCH_ID.toCharArray());//密码默认是商户号
} finally {
instream.close();
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, MCH_ID.toCharArray()).build();
// 只允许TLSv1协议
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
HttpPost httpost = new HttpPost(UNI_URL);
httpost.addHeader("Connection", "keep-alive");
httpost.addHeader("Accept", "*/*");
httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
httpost.addHeader("Host", "api.mch.weixin.qq.com");
httpost.addHeader("X-Requested-With", "XMLHttpRequest");
httpost.addHeader("Cache-Control", "max-age=0");
httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
httpost.setEntity(new StringEntity(getRequestXml(parameters), "UTF-8"));
CloseableHttpResponse response = httpclient.execute(httpost);
HttpEntity entity = response.getEntity();
// String jsonStr = EntityUtils .toString(response.getEntity(), "UTF-8");
StringBuffer result = new StringBuffer();
if (entity != null) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));
String text;
while ((text = bufferedReader.readLine()) != null) {
result.append(text);
}
}
log.info("请求统一支付接口的返回结果:");
log.info(result.toString());
EntityUtils.consume(entity);
return result.toString();
}
生成签名的方法(使用MD5的加密方式)
讲参数转换成xmL形式
/**
* 将请求参数转换为xml格式的string
*
* 作者: zhoubang 日期:2015年6月10日 上午9:25:51
*
* @param parameters
* @return
*/
public static String getRequestXml(SortedMap<Object, Object> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set<Entry<Object, Object>> es = parameters.entrySet();
Iterator<Entry<Object, Object>> it = es.iterator();
while (it.hasNext()) {
Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) it.next();
String k = (String) entry.getKey();
String v = entry.getValue() + "";
if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
} else {
sb.append("<" + k + ">" + v + "</" + k + ">");
}
}
sb.append("</xml>");
return sb.toString();
}
服务器解析xml文件
/**
* 发送xml格式数据到微信服务器 告知微信服务器回调信息已经收到。
*
* 作者: zhoubang 日期:2015年6月10日 上午9:27:33
*
* @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>";
}
另外微信支付成功会回调,会返回商户自己生成的订单号和微信生成的订单号(这个订单号可用于微信退款等操作)
这里的支付回调方法由支付生成预支付订单的参数决定
/***
* 付款成功回调处理
*
* 作者: zhoubang 日期:2015年6月10日 上午9:25:29
*
* @param request
* @param response
* @throws IOException
* @throws JDOMException
*/
@SuppressWarnings("unchecked")
@RequestMapping(value = "pay")
public @ResponseBody void notify_success(HttpServletRequest request,
HttpServletResponse response) throws IOException, JDOMException {
// SetReqHander.setHander(response);
log.info("微信支付成功调用回调URL");
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
log.info("~~~~~~~~~~~~~~~~付款成功~~~~~~~~~");
outSteam.close();
inStream.close();
/** 支付成功后,微信回调返回的信息 */
String result = new String(outSteam.toByteArray(), "utf-8");
log.info("微信返回的订单支付信息:" + result);
Map<Object, Object> map = XMLUtil.doXMLParse(result);
// 用于验签
SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
String transaction_id=(String) map.get("transaction_id");
String out_trade_no=(String) map.get("out_trade_no");
for (Object keyValue : map.keySet()) {
/** 输出返回的订单支付信息 */
log.info(keyValue + "=" + map.get(keyValue));
if (!"sign".equals(keyValue)) {
parameters.put(keyValue, map.get(keyValue));
}
}
if (map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) {
// 先进行校验,是否是微信服务器返回的信息
String checkSign = createSign("UTF-8", parameters);
log.info("对服务器返回的结果进行签名:" + checkSign);
log.info("服务器返回的结果签名:" + map.get("sign"));
if (checkSign.equals(map.get("sign"))) {// 如果签名和服务器返回的签名一致,说明数据没有被篡改过
log.info("签名校验成功,信息合法,未被篡改过");
//告诉微信服务器,我收到信息了,不要再调用回调方法了
/**如果不返回SUCCESS的信息给微信服务器,则微信服务器会在一定时间内,多次调用该回调方法,如果最终还未收到回馈,微信默认该订单支付失败*/
/** 微信默认会调用8次该回调地址 */
/*
* 【需要注意】:
* 后期很多朋友都反应说,微信会一直请求这个回调地址,也都是使用的是 response.getWriter().write(setXML("SUCCESS", ""));
* 百思不得其解,最终发现处理办法。其实不能直接使用response.getWriter()返回结果,这样微信是接收不到的。
* 只能使用OutputStream流的方式返回结果给微信。
* 切记!!!!
* */
OutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
outputStream.flush();
outputStream.write(setXML("SUCCESS", "").getBytes());
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//response.getWriter().write(setXML("SUCCESS", ""));
log.info("-------------" + setXML("SUCCESS", ""));
}
}
}
至此微信的h5支付服务器端已经结束了。
在贴出微信支付用到的工具类
public class WXUtil {
/**
* 获取32位随机字符串
*
* 作者: zhoubang
* 日期:2015年6月26日 下午3:51:44
* @return
*/
public static String getNonceStr() {
Random random = new Random();
return MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)), "UTF-8");
}
public static String getNonceStr(int length) {
String base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
/**
* 时间戳
*
* 作者: zhoubang
* 日期:2015年6月26日 下午3:52:08
* @return
*/
public static String getTimeStamp() {
return String.valueOf(System.currentTimeMillis() / 1000);
}
}
public class XMLUtil {
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Map doXMLParse(String strxml) throws JDOMException, IOException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if(null == strxml || "".equals(strxml)) {
return null;
}
Map m = new HashMap();
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 k = e.getName();
String v = "";
List children = e.getChildren();
if(children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = XMLUtil.getChildrenText(children);
}
m.put(k, v);
}
//关闭流
in.close();
return m;
}
/**
* 获取子结点的xml
* @param children
* @return String
*/
@SuppressWarnings("rawtypes")
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();
}
}
public class TenpayUtil {
/**
* 把对象转换成字符串
*
* @param obj
* @return String 转换成字符串,若对象为null,则返回空字符串.
*/
public static String toString(Object obj) {
if (obj == null)
return "";
return obj.toString();
}
/**
* 把对象转换为int数值.
*
* @param obj
* 包含数字的对象.
* @return int 转换后的数值,对不能转换的对象返回0。
*/
public static int toInt(Object obj) {
int a = 0;
try {
if (obj != null)
a = Integer.parseInt(obj.toString());
} catch (Exception e) {
}
return a;
}
/**
* 获取当前时间 yyyyMMddHHmmss
*
* @return String
*/
public static String getCurrTime() {
Date now = new Date();
SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String s = outFormat.format(now);
return s;
}
/**
* 获取当前日期 yyyyMMdd
*
* @param date
* @return String
*/
public static String formatDate(Date date) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
String strDate = formatter.format(date);
return strDate;
}
/**
* 取出一个指定长度大小的随机正整数.
*
* @param length
* int 设定所取出随机数的长度。length小于11
* @return int 返回生成的随机数。
*/
public static int buildRandom(int length) {
int num = 1;
double random = Math.random();
if (random < 0.1) {
random = random + 0.1;
}
for (int i = 0; i < length; i++) {
num = num * 10;
}
return (int) ((random * num));
}
/**
* 获取编码字符集
*
* @param request
* @param response
* @return String
*/
public static String getCharacterEncoding(HttpServletRequest request,
HttpServletResponse response) {
if (null == request || null == response) {
return "gbk";
}
String enc = request.getCharacterEncoding();
if (null == enc || "".equals(enc)) {
enc = response.getCharacterEncoding();
}
if (null == enc || "".equals(enc)) {
enc = "gbk";
}
return enc;
}
/**
* 获取unix时间,从1970-01-01 00:00:00开始的秒数
*
* @param date
* @return long
*/
public static long getUnixTime(Date date) {
if (null == date) {
return 0;
}
return date.getTime() / 1000;
}
/**
* 时间转换成字符串
*
* @param date
* 时间
* @param formatType
* 格式化类型
* @return String
*/
public static String date2String(Date date, String formatType) {
SimpleDateFormat sdf = new SimpleDateFormat(formatType);
return sdf.format(date);
}
}