前段时间给了了需求对公司网站添加微信支付,由于之前没接触过,简单的东西愣是写了好几天。话不多少,直接开始。
首先你需要先看微信的官方文档https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_1,这里面几乎介绍了全部流程了。等你了解大概流程之后,需要在微信公众平台和微信商户平台拿到或者配置一下参数
appid:公众平台的appid
商户号:公众平台的商户号
appsecrut:商户平台里的签名密钥(17年8月开始移到商户平台了,这个要保存好,现在不支持查看,忘了就得重新配置了)
最后在商户平台上图这个位置配置h5支付的域名(需要通过备案的域名,外网能直接访问)
拿到以上数据后,就开始写代码吧。首先下单
@ResponseBody
@RequestMapping(value = "/pay" ,produces = { "application/json;charset=UTF-8" })
public String weixinPayWap(HttpServletRequest request, HttpServletResponse response, ModelMap model) {
String APPID = "你的apid";
String MERID = "你的商户号";
String SIGNKEY = "你的商户密钥";
String spbill_create_ip = getIpAddr(request);//生产
System.out.println("spbill_create_ip="+spbill_create_ip);
//String spbill_create_ip = "";//测试地址,也就是本地真是ip,用于本地测试用
String scene_info = "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \"这里写在h5支付配置的那个域名\",\"wap_name\": \"信息认证\"}}";//我这里是网页入口,app入口参考文档的安卓和ios写法
String tradeType = "MWEB";//H5支付标记
String MD5 = "MD5";//虽然官方文档不是必须参数,但是不送有时候会验签失败
JSONObject result = new JSONObject();
String subject = request.getParameter("subject");//前端上送的支付主题
String total_amount = request.getParameter("totalAmount");//前端上送的支付金额
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
//金额转化为分为单位 微信支付以分为单位
String finalmoney = StringUtils.getMoney(total_amount);
int randomNum = (int) (Math.random() * 1999+5000);
String out_trade_no = TimeUtils.getSysTime("yyyyMMddHHmmss") + randomNum;
//随机数
String nonce_str= MD5Utils.getMessageDigest(String.valueOf(new Random().nextInt(10000)).getBytes());
//签名数据
StringBuilder sb = new StringBuilder();
sb.append("appid="+APPID);
sb.append("&body="+subject);
sb.append("&mch_id="+MERID);
sb.append("&nonce_str="+nonce_str);
sb.append("¬ify_url="+"这里写回调地址");
sb.append("&out_trade_no="+out_trade_no);
sb.append("&scene_info="+scene_info);
sb.append("&sign_type="+"MD5");
sb.append("&spbill_create_ip="+spbill_create_ip);
sb.append("&total_fee="+finalmoney);
sb.append("&trade_type="+tradeType);
sb.append("&key="+SIGNKEY);
System.out.println("sb="+sb);
//签名MD5加密
String sign = "把sb.toString()做MD5操作并且toUpperCase()一下,至于怎么MD5,百度一下或者看官方文档";
System.out.println("sign="+sign);
log.info("签名数据:"+sign);
//封装xml报文
String xml="<xml>"+
"<appid>"+ APPID+"</appid>"+
"<mch_id>"+ MERID+"</mch_id>"+
"<nonce_str>"+nonce_str+"</nonce_str>"+
"<sign>"+sign+"</sign>"+
"<body>"+subject+"</body>"+//
"<out_trade_no>"+out_trade_no+"</out_trade_no>"+
"<total_fee>"+finalmoney+"</total_fee>"+//
"<trade_type>"+tradeType+"</trade_type>"+
"<notify_url>"+"这里写回调地址"+"</notify_url>"+
"<sign_type>MD5</sign_type>"+
"<scene_info>"+scene_info+"</scene_info>"+
"<spbill_create_ip>"+spbill_create_ip+"</spbill_create_ip>"+
"</xml>";
String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";//微信统一下单接口
String mweb_url = "";
Map map = new HashMap();
try {
//预下单 获取接口地址
map = WebUtils.getMwebUrl(createOrderURL, xml);
String return_code = (String) map.get("return_code");
String return_msg = (String) map.get("return_msg");
if("SUCCESS".equals(return_code) && "OK".equals(return_msg)){
mweb_url = (String) map.get("mweb_url");//调微信支付接口地址
System.out.println("mweb_url="+mweb_url);
}else{
System.out.println("统一支付接口获取预支付订单出错");
result.put("msg", "支付错误");
return result.toString();
}
} catch (Exception e) {
System.out.println("统一支付接口获取预支付订单出错");
result.put("msg", "支付错误");
return result.toString();
}
result.put("mwebUrl",mweb_url);
//添加微信支付记录日志等操作
result.put("msg", "success");
return result.toString();
}
/**
* 获取用户实际ip
* @param request
* @return
*/
public String getIpAddr(HttpServletRequest request){
String ipAddress = request.getHeader("x-forwarded-for");
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
//根据网卡取本机配置的IP
InetAddress inet=null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress= inet.getHostAddress();
}
}
//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
if(ipAddress.indexOf(",")>0){
ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
}
}
return ipAddress;
}
如果签名验证失败就在https://pay.weixin.qq.com/wiki/tools/signverify/调试下签名 记住上送xml里的全都要进行签名,并且排列顺序要按ASCLL码的顺序从小到大(好像是这样,可以看官方文档的签名方式)
接下来是回调
@RequestMapping(value = "/notify")
public void weixinPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
BufferedReader reader = request.getReader();
String line = "";
Map map = new HashMap();
String xml = "<xml><return_code><![CDATA[FAIL]]></xml>";;
JSONObject dataInfo = new JSONObject();
StringBuffer inputString = new StringBuffer();
while ((line = reader.readLine()) != null) {
inputString.append(line);
}
request.getReader().close();
System.out.println("----接收到的报文---"+inputString.toString());
if(inputString.toString().length()>0){
map = XMLUtils.parseXmlToList(inputString.toString());
}else{
System.out.println("接受微信报文为空");
}
System.out.println("map="+map);
if(map!=null && "SUCCESS".equals(map.get("result_code"))){
//成功的业务。。。
xml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}else{
//失败的业务。。。
}
//告诉微信端已经确认支付成功
response.getWriter().write(xml);
}
接下来是工具类
public class WebUtils {
public static Map getMwebUrl(String url,String xmlParam){
String jsonStr = null;
HttpClient httpClient = new HttpClient();
Map map = new HashMap();
try {
PostMethod method = null;
RequestEntity reqEntity = new StringRequestEntity(xmlParam,"text/json","UTF-8");
method = new PostMethod(url);
method.setRequestEntity(reqEntity);
method.addRequestHeader("Content-Type","application/json;charset=utf-8");
httpClient.executeMethod(method);
StringBuffer resBodyBuf = new StringBuffer();
byte[] responseBody = new byte[1024];
int readCount = 0;
BufferedInputStream is = new BufferedInputStream(method.getResponseBodyAsStream());
while((readCount = is.read(responseBody,0,responseBody.length))!=-1){
resBodyBuf.append(new String(responseBody,0,readCount,"utf-8"));
}
jsonStr = resBodyBuf.toString();
System.out.println(jsonStr);
map = XMLUtils.parseXmlToList(jsonStr);
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
public class XMLUtils {
/**
* description: 解析微信通知xml
*
* @param xml
* @return
* @author ex_yangxiaoyi
* @see
*/
@SuppressWarnings({ "unused", "rawtypes", "unchecked" })
public static Map parseXmlToList(String xml) {
Map retMap = new HashMap();
try {
StringReader read = new StringReader(xml);
// 创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入
InputSource source = new InputSource(read);
// 创建一个新的SAXBuilder
SAXBuilder sb = new SAXBuilder();
// 通过输入源构造一个Document
Document doc = (Document) sb.build(source);
Element root = doc.getRootElement();// 指向根节点
List<Element> es = root.getChildren();
if (es != null && es.size() != 0) {
for (Element element : es) {
retMap.put(element.getName(), element.getValue());
}
}
} catch (Exception e) {
e.printStackTrace();
}
return retMap;
}
}
public class TimeUtils {
/**
* 取得系统时间
* @param pattern eg:yyyy-MM-dd HH:mm:ss,SSS
* @return
*/
public static String getSysTime(String pattern) {
return formatSysTime(new SimpleDateFormat(pattern));
}
/**
* 格式化系统时间
* @param format
* @return
*/
private static String formatSysTime(SimpleDateFormat format) {
String str = format.format(Calendar.getInstance().getTime());
return str;
}
}
public class StringUtils {
/**
* 元转换成分
* @param money
* @return
*/
public static String getMoney(String amount) {
if(amount==null){
return "";
}
// 金额转化为分为单位
String currency = amount.replaceAll("\\$|\\¥|\\,", ""); //处理包含, ¥ 或者$的金额
int index = currency.indexOf(".");
int length = currency.length();
Long amLong = 0l;
if(index == -1){
amLong = Long.valueOf(currency+"00");
}else if(length - index >= 3){
amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", ""));
}else if(length - index == 2){
amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0);
}else{
amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00");
}
return amLong.toString();
}
}
public class MD5Utils {
public final static String getMessageDigest(byte[] buffer) {
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
try {
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(buffer);
byte[] md = mdTemp.digest();
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
return null;
}
}
}
前端页面调用返回的地址就行了
location.href = data.mwebUrl;//这应该看的懂吧 ajax发微信下单请求,返回一个地址直接调用,注意要在手机里调用,不然会报商户格式错误。
整个微信h5支付就这样了,应该没有遗漏了吧。如果会的话代码其实很简单,亲测可以。本地测试最多能到下单成功返回mwebUrl这步,支付和回调必须线上测试,如果怕出问题可以写一个模拟页面(只有你知道地址)打到线上真是环境测试。
公众号支付和h5支付大体相同,就是多了获取用户openid和前端需要调用微信js接口。