公众号微信支付开发手记
作者:wallimn 时间:2017-02-27
本人原创,欢迎转载,转载请保留本文链接。本文地址:http://wallimn.iteye.com/blog/2359379
花了两天时间,琢磨了一下微信支付。感觉坑并不像传言中那么多,主要有两个:
一、app的key与商户的key是两个不一样的key。我用了错误的key,花了很长时间解决支付时签名错误。
二、最后页面调起支付时,一直报错:get_brand_wcpay_request:fail。花了很长时间排除。
由于页面在手机上,很难调试。后来想到一个办法,在页面上建一个div元素,设置其ID为actionResult,然后使用jQuery提供的方法显示调用结果:
$("#actionResult").text(JSON.stringify(res))
可以很容易地看到出错原因。
常见的原因有三个:
1、参数数量不足或名称错误。查看开发文档,提供足够的参数,名称、大小写要正确,注意timeStamp的单位为秒。
2、签名不正确。注意查看签名的要求。
3、地址没有授权、或填写错误。“微信支付”->“开发配置”中,设置授权目录。授权要较长时间才能生效。如果地址为:http://wallimn.iteye.com/weixin/paytest,那么微信平台中的授权地址应为:http://wallimn.iteye.com/weixin/paytest
还有两个要注意的小问题:
一、定单号(out_trade_no)如果重复会导致支付失败。
二、测试用户要加入白名单。
微信的流程图有一点点儿不太直观,我画了一个简单的,应该一看就会明白。
[align=center]
[img]http://dl2.iteye.com/upload/attachment/0123/3788/17b646bb-f76f-3306-bd7b-770674a27932.png[/img]
[/align]
程序的主体代码如下。
一、几个基本的辅助类
1.统一定单类
package com.wallimn.weixin;/** * 统一定单,用于向微信提供信息,进行支付 * @author wallimn * */public class UnifiedOrder {
private String appid; private String mch_id; private String nonce_str; private String sign_type;//="MD5" private String body;//商品描述 private String device_info="WEB";//设备号
private String detail; private String out_trade_no;//商户订单号 private String fee_type;//,非必需="CNY" private String total_fee;//分为单位
private String spbill_create_ip; private String notify_url; private String trade_type;//JSAPI、NATIVE、APP三种 private String product_id="something";//商品ID,非必需 private String openid;//JSAPI时,必传 public String getAppid() { return appid; } public void setAppid(String appid) { this.appid = appid; } public String getMch_id() { return mch_id; } public void setMch_id(String mch_id) { this.mch_id = mch_id; } public String getNonce_str() { return nonce_str; } public void setNonce_str(String nonce_str) { this.nonce_str = nonce_str; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public String getDetail() { return detail; } public void setDetail(String detail) { this.detail = detail; } public String getOut_trade_no() { return out_trade_no; } public void setOut_trade_no(String out_trade_no) { this.out_trade_no = out_trade_no; } public String getFee_type() { return fee_type; } public void setFee_type(String fee_type) { this.fee_type = fee_type; } public String getSpbill_create_ip() { return spbill_create_ip; } public void setSpbill_create_ip(String spbill_create_ip) { this.spbill_create_ip = spbill_create_ip; } public String getNotify_url() { return notify_url; } public void setNotify_url(String notify_url) { this.notify_url = notify_url; } public String getTrade_type() { return trade_type; } public void setTrade_type(String trade_type) { this.trade_type = trade_type; } public String getProduct_id() { return product_id; } public void setProduct_id(String product_id) { this.product_id = product_id; } public String getOpenid() { return openid; } public void setOpenid(String openid) { this.openid = openid; } public String getDevice_info() { return device_info; } public void setDevice_info(String device_info) { this.device_info = device_info; } public String getTotal_fee() { return total_fee; } public void setTotal_fee(String total_fee) { this.total_fee = total_fee; } public String getSign_type() { return sign_type; } public void setSign_type(String sign_type) { this.sign_type = sign_type; }
}
2.统一定单结果类
package com.wallimn.weixin;
/** * 微信定单返回信息 * @author wallimn * */public class UnifiedOrderResult {
private String return_code; private String return_msg; private String appid; private String mch_id; private String nonce_str; private String sign; private String result_code; private String prepay_id; private String trade_type;
public String getReturn_msg() { return return_msg; }
public void setReturn_msg(String return_msg) { this.return_msg = return_msg; }
public String getReturn_code() { return return_code; }
public void setReturn_code(String return_code) { this.return_code = return_code; }
public String getNonce_str() { return nonce_str; }
public void setNonce_str(String nonce_str) { this.nonce_str = nonce_str; }
public String getAppid() { return appid; }
public void setAppid(String appid) { this.appid = appid; }
public String getMch_id() { return mch_id; }
public void setMch_id(String mch_id) { this.mch_id = mch_id; }
public String getSign() { return sign; }
public void setSign(String sign) { this.sign = sign; }
public String getResult_code() { return result_code; }
public void setResult_code(String result_code) { this.result_code = result_code; }
public String getPrepay_id() { return prepay_id; }
public void setPrepay_id(String prepay_id) { this.prepay_id = prepay_id; }
public String getTrade_type() { return trade_type; }
public void setTrade_type(String trade_type) { this.trade_type = trade_type; }}
3.与微信相关的常量类
package com.wallimn.weixin;
import java.net.URLEncoder;
/** * 与微信相关的常量 * @author Administrator * */public class Config { public static final String app_id=""; public static final String app_sercet=""; /** * 微信登录授权时使用的state参数 */ public static final String state="wallimn"; /** * 微信登录授权时使用的scope参数 */ public static final String scope="snsapi_userinfo";
/** * 获取code的Servlet地址,微信平台提供 */ public static final String code_url = "http://www.day2up.com/bzslql/sso/weixin"; /** * 默认的登录后跳转地址 */ public static final String default_back_url = "/index.jsp";
/** * 微信支付的商户号,微信商户注册时获得 */ public static final String mch_id=""; /** * 商户的api的key,在微信的商户平台中设置。与app的key不一样.这个是个坑! */ public static final String mch_secret=""; /** * 微信统一下单接口 */ public static final String unifiedorder_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/** * 用户支付成功后,微信平台的通知地址 */ public static final String pay_notify_url="";
/** * 返回微信的获取code的地址。将backurl编码到地址中。授权成功后,自动返回backurl对应的地址。 * <br> * 时间:2017年2月27日,作者:http://wallimn.iteye.com * @param backUrl * @return */ public static String getCodeUrl(String backUrl){ try { String url = code_url; //这样可以灵活返回到指定的地址,不必在Servlet中硬编码 if(backUrl!=null){ url += "?backurl="+backUrl; } String ru = URLEncoder.encode(url,"utf-8"); return "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+app_id+"&redirect_uri="+ru+"&response_type=code&scope="+scope+"&state="+state+"#wechat_redirect"; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 获取access_token的链接 * <br> * 时间:2017年2月27日,作者:http://wallimn.iteye.com * @param code * @return */ public static String getTokenUrl(String code){ return "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+app_id+"&secret="+app_sercet+"&code="+code+"&grant_type=authorization_code"; }
/** * 刷新access_token的链接 * @param token * @return * 作者:wallimn<br/> * 时间:2017年1月22日<br/> * 联系:54871876@qq.com<br/> */ public static String getFreshTokenUrl(String token){ return "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid="+app_id+"&grant_type=refresh_token&refresh_token="+token; }
/** * 使用access_token获取用户信息的链接 * @param token * @param openid * @return * 作者:wallimn<br/> * 时间:2017年1月22日<br/> * 联系:54871876@qq.com<br/> */ public static String getUserInfoUrl(String token,String openid){ return "https://api.weixin.qq.com/sns/userinfo?access_token="+token+"&openid="+openid+"&lang=zh_CN"; }}
二、支付工具类
package com.wallimn.weixin;
import java.io.InputStream;import java.lang.reflect.InvocationTargetException;import java.security.MessageDigest;import java.util.Arrays;import java.util.HashMap;import java.util.LinkedList;import java.util.List;import java.util.Map;import java.util.Map.Entry;import java.util.Set;import java.util.UUID;
import org.apache.commons.beanutils.BeanUtils;import org.apache.commons.lang3.StringUtils;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.dom4j.Document;import org.dom4j.DocumentException;import org.dom4j.DocumentHelper;import org.dom4j.Element;import org.dom4j.io.SAXReader;
import com.fasterxml.jackson.databind.ObjectMapper;
/** * 微信支付处理辅助类 * @author http://wallimn.iteye.com * */public class PayUtils { private static final Logger log = LogManager.getLogger(PayUtils.class);
/** * 解析统一定单结果数据。 * * @param xml * @return * <br> * 时间:2017年2月27日<br> * 作者:http://wallimn.iteye.com<br> * 联系:54871876@qq.com */ public static UnifiedOrderResult parseUnifiedOrderResult(String xml) { UnifiedOrderResult result = new UnifiedOrderResult(); //AXReader reader = new SAXReader(); Document doc=null; try { //doc = reader.read(xml); doc = DocumentHelper.parseText(xml); result.setReturn_code(((Element) doc.selectSingleNode("/xml/return_code")).getText()); result.setReturn_msg(((Element) doc.selectSingleNode("/xml/return_msg")).getText()); if("SUCCESS".equals(result.getReturn_code())){ result.setAppid(((Element) doc.selectSingleNode("/xml/appid")).getText()); result.setMch_id(((Element) doc.selectSingleNode("/xml/mch_id")).getText()); result.setNonce_str(((Element) doc.selectSingleNode("/xml/nonce_str")).getText()); //result.setOpenid(((Element) doc.selectSingleNode("/xml/openid")).getText()); result.setSign(((Element) doc.selectSingleNode("/xml/sign")).getText()); result.setResult_code(((Element) doc.selectSingleNode("/xml/result_code")).getText()); result.setPrepay_id(((Element) doc.selectSingleNode("/xml/prepay_id")).getText()); result.setTrade_type(((Element) doc.selectSingleNode("/xml/trade_type")).getText());
} doc = null; //reader = null; } catch (DocumentException e) { log.error("解析统一定单返回结果错误:{}",e.getMessage()); e.printStackTrace(); } return result; }
/** * 使用必须用户提供的参数,生成统计定单对象 * * @param body * @param detail * @param openid * @param out_trade_no * @param spbill_create_ip * @param total_fee * @param trade_type * @return * <br> * 时间:2017年2月27日<br> * 作者:http://wallimn.iteye.com<br> * 联系:54871876@qq.com */ public static UnifiedOrder getUnifiedOrder(String body, String detail, String openid, String out_trade_no, String spbill_create_ip, String total_fee, String trade_type) { UnifiedOrder order = new UnifiedOrder(); /* * String wx_order = Config.order_url;// 获取统一下单接口地址 String mchappid = * Config.appid;// 商户appid String mchid = Config.mch_id;// 商户ID String * wx_callback = Config.paycallback_url;// 获取微信支付回调接口 String wx_key = * Config.appsercet;// 微信商户后台设置的key String app_mchid = Config.appid;// * APP调起微信支付的商户ID String app_mchappid = Config.mch_id;// APP调起微信的APPID */ order.setAppid(Config.app_id); order.setBody(body); order.setMch_id(Config.mch_id); order.setNonce_str(UUID.randomUUID().toString().replace("-", "")); order.setNotify_url(Config.pay_notify_url); order.setOpenid(openid); order.setOut_trade_no(out_trade_no); order.setSpbill_create_ip(spbill_create_ip); order.setTotal_fee(total_fee); order.setTrade_type(trade_type); order.setDetail(detail);
return order; }
/** * 将bean转化为Map,方便下一步签名、转化xml使用 * * @param bean * @return * <br> * 时间:2017年2月27日<br> * 作者:http://wallimn.iteye.com<br> * 联系:54871876@qq.com */ public static Map<String, String> BeanToMap(Object bean) { Map<String, String> map = null; try { map = BeanUtils.describe(bean); map.remove("class"); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } return map; }
/** * 将Map数据转化为统一定单要求的xml数据。 * * @param map * @return * <br> * 时间:2017年2月27日<br> * 作者:http://wallimn.iteye.com<br> * 联系:54871876@qq.com */ public static String MapToXML(Map<String, String> map) { if (map == null) return null; StringBuilder sb = new StringBuilder(); sb.append("<xml>"); String value = null; Set<String> keySet = map.keySet(); String [] keyArray = keySet.toArray(new String[0]); Arrays.sort(keyArray);
for (String key :keyArray) { value = map.get(key); if (value == null) { value = ""; } else if (value.length() > 60) { // 简单处理一下,如果过长,就加上XML的转义 value = "<![CDATA[" + value + "]]"; } sb.append("<").append(key).append(">").append(value).append("</").append(key) .append(">"); } sb.append("</xml>"); return sb.toString(); }
/** * 读取流中的xml数据,转化为Map<String,String> * * @param is * @return * <br> * 时间:2017年2月27日<br> * 作者:http://wallimn.iteye.com<br> * 联系:54871876@qq.com */ public static Map<String,String> XmlToMap(InputStream is){ HashMap<String, String> map = new HashMap<String, String>(); log.info("------------微信回调函数----------------"); // 1、读取传入信息并转换为map SAXReader reader = new SAXReader(); Document document = null; try { document = reader.read(is); log.info("支付返回结果:/n/r"+document.asXML()); Element root = document.getRootElement(); List<Element> list = root.elements(); for (Element e : list) { map.put(e.getName().trim(), e.getText().trim()); } document = null; reader = null; } catch(Exception e){ log.error("XML转化为bean错误,原因:{}",e.getMessage()); e.printStackTrace(); } return map;
}
/** * 提交统一定单,仅对应于JSAPI类型 * * @param orderXml * @return 返回结果为适合公众号页面上使用的JSON数据。格式与微信要求一致。 * <br> * 时间:2017年2月27日<br> * 作者:http://wallimn.iteye.com<br> * 联系:54871876@qq.com */ public static String postUnifiedOrder(String orderXml) { String response = ""; try {// 注意,此处的httputil一定发送请求的时候一定要注意中文乱码问题,中文乱码问题会导致在客户端加密是正确的,可是微信端返回的是加密错误 log.info("统一定单发送:"+Config.unifiedorder_url); response = HttpUtils.post(Config.unifiedorder_url, orderXml); log.info("统一定单结果:"+response); UnifiedOrderResult orderResult = PayUtils.parseUnifiedOrderResult(response);
// 6、处理请求结果 log.info("return_code={}, result_code={}",orderResult.getReturn_code(),orderResult.getResult_code()); if ("SUCCESS".equals(orderResult.getReturn_code()) && "SUCCESS".equals(orderResult.getResult_code())) { log.error("微信支付统一下单请求成功:" + orderResult.getPrepay_id()); } else { log.error("微信支付统一下单请求错误:{}" + orderResult.getReturn_msg()); return null; }
HashMap<String, String> back = new HashMap<String, String>(); ObjectMapper mapper = new ObjectMapper();
// 生成客户端调时需要的信息对象 // 网页调起的时候 //时戳,要求转化为以秒为单位 String time = Long.toString(System.currentTimeMillis() / 1000); back.put("appId", Config.app_id); back.put("timeStamp", time); String uuid = UUID.randomUUID().toString().replace("-",""); back.put("nonceStr", uuid); back.put("package", "prepay_id=" + orderResult.getPrepay_id()); back.put("signType", "MD5");
//根据以上参数,计算签名。 String sign = signature(back, Config.mch_secret); back.put("paySign", sign);
String json = mapper.writeValueAsString(back); //log.info("返回也面的JSON:/n/r{}"+json); return json;
} catch (Exception e) { log.error("微信支付统一下单失败,http请求失败:{}", e.getMessage()); e.printStackTrace(); }
return null;
}
/** * 查检定单数据 * * @param order * @return * <br> * 时间:2017年2月27日<br> * 作者:http://wallimn.iteye.com<br> * 联系:54871876@qq.com */ public static boolean checkUnifiedOrder(UnifiedOrder order) {
// if (StringUtils.isBlank(order.getDetail()) ) {// log.error("微信支付统一下单请求错误:detail为空!");// return false;// }
if ( StringUtils.isBlank(order.getBody())) { log.error("微信支付统一下单请求错误:body为空!"); return false; }
if ( StringUtils.isBlank(order.getSpbill_create_ip())) { log.error("微信支付统一下单请求错误:spbill_create_ip为空!"); return false; }
if (StringUtils.isBlank(order.getOut_trade_no()) ) { log.error("微信支付统一下单请求错误:out_trade_no为空!"); return false; }
if (StringUtils.isBlank(order.getTotal_fee())) { log.error("微信支付统一下单请求错误:total_fee为空!"); return false; }
if (StringUtils.isBlank(order.getTrade_type())) { log.error("微信支付统一下单请求错误:trade_type为空!"); return false; }
try {// 进行格式转换异常获取,保证数目正确 int fee = Integer.parseInt(order.getTotal_fee()); if (fee == 0 || !order.getTotal_fee().equals(String.valueOf(fee))) {// 微信支付的支付金额必须为大于0的int类型,单位为分 log.error("微信支付统一下单请求错误:请求金额不能为0,不能为小数"); return false; } } catch (Exception e) { log.error("微信支付统一下单请求错误:请求金额格式错误,值:{}", order.getTotal_fee()); return false; }
if (!("JSAPI".equalsIgnoreCase(order.getTrade_type()) || "NATIVE".equalsIgnoreCase(order.getTrade_type()) || "APP".equalsIgnoreCase(order.getTrade_type()))) { log.error("微信支付统一下单请求错误:支付类型不正确,{}", order.getTrade_type()); return false; }
/* 公众号调起微信支付的时候,必须要有openID */ if ("JSAPI".equalsIgnoreCase(order.getTrade_type()) && StringUtils.isBlank(order.getOpenid())) { log.error("微信支付统一下单请求错误:请求参数不足,当类型为JSAPI时,必须提供openid"); return false; }
if (StringUtils.isBlank(order.getAppid()) || StringUtils.isBlank(order.getMch_id()) || StringUtils.isBlank(order.getNotify_url())) { log.error("微信支付统一下单请求错误:系统配置信息缺失,appid, mch_id, notify_url"); return false; }
return true; }
/** * 签名生成的通用步骤如下: * 第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。 * 特别注意以下重要规则: ◆ 参数名ASCII码从小到大排序(字典序); ◆ 如果参数的值为空不参与签名; ◆ 参数名区分大小写; ◆ * 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。 ◆ * 微信接口可能增加字段,验证签名时必须支持增加的扩展字段 * 第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。 * key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 * */
/** * 为微信支付发送数据进行签名的类。 * * @param map * @param app_secret * @return * <br> * 时间:2017年2月27日<br> * 作者:http://wallimn.iteye.com<br> * 联系:54871876@qq.com */ public static String signature(Map<String, String> map, String app_secret) { List<String> keyList = new LinkedList<String>(); // 如果参数不为空,加入签名的字符串 for (Entry<String, String> entry : map.entrySet()) { if (StringUtils.isNotEmpty(entry.getValue())) { keyList.add(entry.getKey()); } }
String[] keyArray = keyList.toArray(new String[0]); Arrays.sort(keyArray); StringBuilder sbInfo = new StringBuilder(); // 进行字典排序 for (String str : keyArray) { sbInfo.append(str).append("=").append(map.get(str)).append("&"); } if (StringUtils.isNotEmpty(app_secret)) { sbInfo.append("key=" + app_secret); } String tosend = sbInfo.toString(); MessageDigest md = null; byte[] bytes = null; try {
md = MessageDigest.getInstance("MD5"); bytes = md.digest(tosend.getBytes("utf-8")); } catch (Exception e) { e.printStackTrace(); }
String sign = byteToStr(bytes); System.out.println("签名字符串:"+tosend); sign = sign.toUpperCase(); System.out.println("签名字结果:"+sign); return sign;
}
/** * 字节数组转换为字符串 * * @param byteArray * @return */ private static String byteToStr(byte[] byteArray) { String strDigest = ""; for (int i = 0; i < byteArray.length; i++) { strDigest += byteToHexStr(byteArray[i]); } return strDigest; }
/** * 字节转换为字符串 * * @param mByte * @return */ private static String byteToHexStr(byte mByte) { char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; char[] tempArr = new char[2]; tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr); return s; } /** * SHA-1签名,这个在实际中并未使用 * <br> * 时间:2017年2月27日,作者:http://wallimn.iteye.com * @param map * @return */ public static String signatureSHA1(Map<String, String> map) { Set<String> keySet = map.keySet(); String[] str = new String[map.size()]; StringBuilder tmp = new StringBuilder(); // 进行字典排序 str = keySet.toArray(str); Arrays.sort(str); for (int i = 0; i < str.length; i++) { String t = str[i] + "=" + map.get(str[i]) + "&"; tmp.append(t); }
String tosend = tmp.toString().substring(0, tmp.length() - 1); MessageDigest md = null; byte[] bytes = null; try {
md = MessageDigest.getInstance("SHA-1"); bytes = md.digest(tosend.getBytes("utf-8")); } catch (Exception e) { e.printStackTrace(); }
String singe = byteToStr(bytes); return singe.toLowerCase();
}}
三、HttpClient辅助类
package com.wallimn.weixin;
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.UnsupportedEncodingException;import java.net.URI;import java.net.URISyntaxException;import java.util.ArrayList;import java.util.List;import java.util.Map;
import org.apache.http.HttpResponse;import org.apache.http.NameValuePair;import org.apache.http.client.ClientProtocolException;import org.apache.http.client.HttpClient;import org.apache.http.client.entity.UrlEncodedFormEntity;import org.apache.http.client.methods.HttpGet;import org.apache.http.client.methods.HttpPost;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.HttpClientBuilder;import org.apache.http.message.BasicNameValuePair;
public class HttpUtils { private static final String Charset = "utf-8"; public static HttpClient client; static { client = HttpClientBuilder.create().build();
}
/** * 发送请求,如果失败,会返回null * @param url * @param map * @return */ public static String post(String url, Map<String, String> map) { // 处理请求地址 try { URI uri = new URI(url); HttpPost post = new HttpPost(uri);
// 添加参数 List<NameValuePair> params = new ArrayList<NameValuePair>(); for (String str : map.keySet()) { params.add(new BasicNameValuePair(str, map.get(str))); } post.setEntity(new UrlEncodedFormEntity(params, Charset)); // 执行请求 HttpResponse response = client.execute(post);
if (response.getStatusLine().getStatusCode() == 200) { // 处理请求结果 StringBuffer buffer = new StringBuffer(); InputStream in = null; try { in = response.getEntity().getContent(); BufferedReader reader = new BufferedReader(new InputStreamReader(in,Charset)); String line = null; while ((line = reader.readLine()) != null) { buffer.append(line); }
} catch (UnsupportedOperationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { // 关闭流 if (in != null) try { in.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
return buffer.toString(); } else { return null; } } catch (URISyntaxException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnsupportedEncodingException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (ClientProtocolException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } return null;
}
/** * 发送请求,如果失败会返回null * @param url * @param str * @return */ public static String post(String url, String str) { // 处理请求地址 try { URI uri = new URI(url); HttpPost post = new HttpPost(uri); post.setEntity(new StringEntity(str, Charset)); // 执行请求 HttpResponse response = client.execute(post);
if (response.getStatusLine().getStatusCode() == 200) { // 处理请求结果 StringBuffer buffer = new StringBuffer(); InputStream in = null; try { in = response.getEntity().getContent(); BufferedReader reader = new BufferedReader(new InputStreamReader(in,"utf-8")); String line = null; while ((line = reader.readLine()) != null) { buffer.append(line); }
} finally { // 关闭流 if (in != null) in.close(); }
return buffer.toString(); } else { return null; } } catch (URISyntaxException | IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null;
}
/** * 发送GET方式的请求,并返回结果字符串。 * <br> * 时间:2017年2月27日,作者:http://wallimn.iteye.com * @param url * @return 如果失败,返回为null */ public static String get(String url) { try { URI uri = new URI(url); HttpGet get = new HttpGet(uri); HttpResponse response = client.execute(get); if (response.getStatusLine().getStatusCode() == 200) { StringBuffer buffer = new StringBuffer(); InputStream in = null; try { in = response.getEntity().getContent(); BufferedReader reader = new BufferedReader(new InputStreamReader(in,Charset)); String line = null; while ((line = reader.readLine()) != null) { buffer.append(line); }
} finally { if (in != null) in.close(); }
return buffer.toString(); } else { return null; } } catch (URISyntaxException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClientProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null;
}
}
四、Controller中相关的代码
package com.wallimn.bzslql.controller;
import java.io.IOException;import java.io.InputStream;import java.io.PrintWriter;import java.util.Map;
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.h2.util.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.ResponseBody;
import com.wallimn.weixin.Config;import com.wallimn.weixin.HttpUtils;import com.wallimn.weixin.PayUtils;import com.wallimn.weixin.UnifiedOrder;import com.wallimn.weixin.User;
/** * 与微信相关的处理Controller * * @author wallimn * */@Controller@RequestMapping("/weixin")public class WeixinController extends CommonController {
private static Logger log = LogManager.getLogger(WeixinController.class); @Autowired private HttpServletRequest request; @Autowired protected HttpServletResponse response;
/** * 接受用户提供的信息,生成标准的统一订单,提交到微信平台进行处理,提交成功后,返回JSON数据, * 公众号网页依据此JSON内容,调用微信平台的接口、输入密码,完成支付 * * @param body * @param detail * @param out_trade_no * @param total_fee * @return * @throws Exception * <br> * 时间:2017年2月27日<br> * 作者:http://wallimn.iteye.com<br> * 联系:54871876@qq.com */ @RequestMapping("/unifiedorder") @ResponseBody public String unifiedorder(String body, String detail, String out_trade_no, String total_fee) throws Exception { String spbill_create_ip = request.getRemoteAddr(); String openid = this.getLoginUserId(); log.info("创建定单:body:{}, detail:{},out_trade_no:{},total_fee:{}", body, detail, out_trade_no, total_fee); UnifiedOrder order = PayUtils.getUnifiedOrder(body, "", openid, out_trade_no, spbill_create_ip, total_fee, "JSAPI"); if (PayUtils.checkUnifiedOrder(order) == true) { Map<String, String> map = PayUtils.BeanToMap(order); String sign = PayUtils.signature(map, Config.mch_secret); log.info("签名:" + sign); map.put("sign", sign);//加入签名 String orderXml = PayUtils.MapToXML(map);//生成xml定单数据 log.info("统一定单:" + orderXml); String result = PayUtils.postUnifiedOrder(orderXml); log.info("统一定单结果:" + result); // String result = WechatOrderUtils.createOrder(detail, body, // openid, ip, goodSn, orderSn, total_fee, "JSAPI"); return result; } else { return this.getFailJSON("FAIL", "定单参数错误!"); } }
/** * 微信支付回调函数。 当用户支付成功后,通过此地址通知操作结果 * 该地址在向微信发送统一定单时,告知微信平台。 * * @param request * @param response * @throws IOException * <br> * 时间:2017年2月27日<br> * 作者:http://wallimn.iteye.com<br> * 联系:54871876@qq.com */ @RequestMapping(value = "/paycallback", method = RequestMethod.POST) public void payCallBack(HttpServletRequest request, HttpServletResponse response) throws IOException { InputStream is = request.getInputStream(); Map<String, String> map = PayUtils.XmlToMap(is); String result_code = map.get("result_code"); // 支付成功 if ("SUCCESS".equals(result_code)) { String sign = map.get("sign"); // System.out.println(map.toString()); // 2、克隆传入的信息并进行验签 map.remove("sign"); String sign2 = PayUtils.signature(map, Config.mch_secret); if (StringUtils.equals(sign, sign2)) { log.info("微信支付回调函数,验答正确。处理业务逻辑。"); // TODO:添加业务处理逻辑 } else { log.error("微信支付回调函数:验签错误!");
} } else if ("FAIL".equals(result_code)) { log.error("微信支付回调函数:失败!失败代码为:{}", map.get("return_code"));
}
// 返回信息,防止微信重复发送报文 // 由于微信后台会同时回调多次,所以需要做防止重复提交操作的判断 // 此处放防止重复提交操作 String result = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml>"; PrintWriter out = new PrintWriter(response.getOutputStream()); out.print(result); out.flush(); out.close();
}
/** * 支付操作准备界面(测试用)。 * 注意映射地址为:/weinxin/paytest/170228,那么在微信设置界面中,填写的授权地址应为:/weinxin/paytest/ * 这是一个坑 * * @return * <br> * 时间:2017年2月27日<br> * 作者:http://wallimn.iteye.com<br> * 联系:54871876@qq.com */ @RequestMapping("/paytest/170228") public String paytest() { return "paytest"; }
/** * 支付操作准备界面。 * 注意映射地址为:/weinxin/paytest/170228,那么在微信设置界面中,填写的授权地址应为:/weinxin/payreal/ * * @return * <br> * 时间:2017年2月27日<br> * 作者:http://wallimn.iteye.com<br> * 联系:54871876@qq.com */ @RequestMapping("/payreal/170228") public String payreal() { return "payreal"; }}
五、公众号页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %><!DOCTYPE html><!-- 作者: wallimn, http://wallimn.iteye.com 时间:2017年1月26日 功能:--><html lang="zh-cn"><head> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>JSAPI方式支付调起页面</title> <c:set var="ctx" value="${pageContext.request.contextPath}"></c:set> <script type="text/javascript" src="${ctx }/js/jquery-1.11.1.min.js"></script> <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script> <script type="text/javascript"> function submit(){ $("#actionResult").text(""); $.ajax({ type: 'POST', url: '${ctx }/weixin/unifiedorder?dt='+(new Date()).getTime(), data: {'detail':'测试detail','body':'商户名-商品名','out_trade_no':(new Date()).getTime().toString(16),'total_fee':$("input[name=total_fee]").val()}, success: function(json){ if(!json || !json.appId){ $("#actionResult").text("参数错误!"); return ; } wxpay(json); }, dataType: "json"});
} function wxpay(json){ function onBridgeReady(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId" :json.appId, //公众号名称,由商户传入 "timeStamp":json.timeStamp, //时间戳,自1970年以来的秒数 "nonceStr" :json.nonceStr, //随机串 "package" : json.package, "signType" :json.signType, //微信签名方式: "paySign" :json.paySign //微信签名 }, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) { $("#actionResult").text("支付成功!"); } // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 else{ $("#actionResult").text("支付失败:"+JSON.stringify(res)); } } ); } 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(); } } $(function(){ $("#sub").click(function(){ submit(); }); $("#actionResult").text("WeixinJSBridge"); });
</script></head><body><div style="text-align:center"> <div id="actionResult">注意单位为(分)</div> <input type="text" placeholder="请输入金额(分)" name="total_fee" value="1"><br> <input type="button" id="sub" value="支付"></div></body></html>
公众号微信支付开发手记相关推荐
- 微信服务号 微信支付开发
微信支付,须要通过微信支付验证 眼下.支付仅限服务号, 做微信支付开发,主要看开发文档 统一下单. 订单查询 退款等 1.发起支付.都是通过h5发起的,首先获取prepay_id 发起支付,须要统一下 ...
- vue开发项目微信公众号授权支付开发
一.注册微信公众号服务号并填写企业信息(个人订阅号没有开发微信支付的权限) 链接: https://mp.weixin.qq.com/ 二.在微信公众号内进行微信认证(3-5个工作日) 三.在微信公众 ...
- weixin-java-pay实现公众号微信支付与退款
内容来自:https://www.jianshu.com/p/0a0ccc15cb80 pom.xml 文件 需要在 pom.xml 加入以下依赖! <dependency><gro ...
- 微信sdk 公众号 微信支付 NFC 坑笔记
微信sdk--微信支付--NFC项目 坑&笔记 微信公众号开发的环境配置 准备工作 & 微信公众平台配置 & 环境配置 微信的登录流程(即授权机制)遵循OAuth2.0标准,实 ...
- uniapp公众号微信支付
1.安装jssdk npm install jweixin-module --save 2.调用 <template><viewstyle="width: 100%;hei ...
- Vue公众号微信支付
需要获取的支付信息: openid.attach(备注).body(支付标题).out_trade_no(订单号).total_fee(订单价格,单位分,100是一元).goods_tag(订单类型) ...
- 公众号微信支付ios和android,uni-app微信公众号支付和分享,特别是ios下的配置,完美解决...
一.支付 由于在ios中uni-app发布的应用是单应用,不管访问哪个页面,始终记录的是首次进来的那个页面. 这样的话,在微信支付签名时会报签名不对的错误.怎么解决? 老王的解决方案是在main.js ...
- php 公众号微信支付流程,微信公众号支付完整流程案例
简介 微信公众号支付,顾名思义就是必须在微信中实现支付,并且需要公众号配合. 教程 由于我们使用的是第三方封装好的接口,这里省去了我们自己配置公众号.为什么用第三方?因为个人没有申请权限. 交互细节: ...
- 公众号微信支付ios和android,【微信支付】
跳转到还款小程序 var extraData = { appid:wxcbda96de0b165486, sub_appid:wxcbda96de0b165482, mch_id:1900009231 ...
最新文章
- 计算机图形学——三角形网格
- node学习笔记--模块加载
- 回归与梯度下降法及实现原理
- linux下Vim和Terminal配色
- eclipse中Android模拟器常见的问题解决方法汇总
- python/Django(增、删、改、查)操作
- Flask最强攻略 - 跟DragonFire学Flask - 第四篇 Flask 中的模板语言 Jinja2 及 render_template 的深度用法
- Adsense加入黑名单的预防办法
- CSS解决无空格太长的字母,数字不会自动换行的问题
- 重点推荐:HP大中华区总裁孙振耀退休感言
- 大学生数学建模赛题解析及优秀论文-2021电工杯A题高铁牵引供电系统运行数据分析及等值建模(附Python代码)
- ccccccccccc
- curl encode
- Android事件传递
- [HAOI2007]理想的正方形(单调队列)
- Win10安装Apache和PHP
- 湖南对口升学计算机专科学院,湖南省计算机对口升学的大学有哪些
- 技术团队里什么样的人会被清除?抢老板的工作干合适吗?
- 知道2020年计算机二级考试成绩,2020年9月计算机二级考试成绩可以查了 多少分及格...
- Nuxt中全局路由守卫的写法