V3微信支付开发笔录
真是坑爹啊,微信支付到处都是坑,一不小心就栽里面了, 文档也不怎么全,经过一周的奋斗终于把微信支付功能搞定,在此写下自己当时走入的误区和一些需要注意的地方,希望后边开发的朋友们可以少走弯路,少被微信坑~~~~~
基本上微信支付需要根据流程图走:

基本步骤如下:
1.先调用统一下单接口,获取到perpay_id,在调用接口的时候需要自己写签名,签名前拼接的字符串是有顺序的,实在不清楚顺序可以使用官网上的签名在线工具进行校验,
根据校验结果进行填写保证OK,地址https://pay.weixin.qq.com/wiki/tools/signverify/,调用统一下单接口代码如下:
public Map<String, Object> doGetWXUnifiedOrder(HttpServletRequest request,HttpServletResponse response){
		String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
		String appId = "";
		String mchId = ""; //商户ID,这个是需要商户进行申请的,只有服务号可以
		String key = ""; //登录商户端可以自己进行设置,32位的
		String nonceStr = UUID.randomUUID().toString().replaceAll("-", "");
		String timeStamp = Long.toString(System.currentTimeMillis()/1000);
		String outTradeNo = "";//订单号,确保在商户系统唯一
		String body = ""; //商品名称或者描述都可以
		String totalFee = "";//订单总金额
		String spbillCreateIp = getIp(request); //机器IP
		String notifyUrl = ""; //接收微信支付成功通知
		String tradeType = "JSAPI";//交易类型 JSAPI、NATIVE、APP
		String openid =""; //用户的唯一标识,trade_type=JSAPI时必填
List<KV> xmlList = new ArrayList<KV>();
		xmlList.add(new KV("appid", appId));
		xmlList.add(new KV("body", body));
		xmlList.add(new KV("mch_id", mchId));
		xmlList.add(new KV("nonce_str", nonceStr.replaceAll("-", "")));	
		xmlList.add(new KV("notify_url", notifyUrl));
		xmlList.add(new KV("openid", openId));
		xmlList.add(new KV("out_trade_no", outTradeNo));
		xmlList.add(new KV("spbill_create_ip", spbillCreateIp));
		xmlList.add(new KV("total_fee", totalFee));
		xmlList.add(new KV("trade_type", tradeType));
//生成签名
		StringBuffer sb = new StringBuffer();
        for (int i = 0; i < xmlList.size(); i++) {
			KV kv = xmlList.get(i);
			String k = kv.getKey();
            String v = kv.getValue();
            if(null != v && !"".equals(v)
                    && !"sign".equals(k) && !"key".equals(k)) {
            	if("total_fee".equals(k)){
            		Double dd = Double.parseDouble(v)*100;
           		 	int y = dd.intValue();
           		 sb.append(k + "=" + y + "&");
            	}else {
            		sb.append(k + "=" + v + "&");
				}
}
		}
        sb.append("key=" + key); //自己的API密钥      
        System.out.println("密钥---------"+sb);
        String sign1 = MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();
		xmlList.add(new KV("sign", sign1));
//转换成xml
		 StringBuffer sb1 = new StringBuffer();
         sb1.append("<xml>");
         for (int i = 0; i < xmlList.size(); i++) {
        	 KV kv = xmlList.get(i);
 			String k = kv.getKey();
             String v = kv.getValue();
             if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
                 sb1.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");
             }else {
            	 if("total_fee".equals(k)){
            		 Double dd = Double.parseDouble(v)*100;
            		 int y = dd.intValue();
            		 sb1.append("<"+k+">"+y+"</"+k+">");
            	 }else {
            		 sb1.append("<"+k+">"+v+"</"+k+">");
				}            
             }
         }
         sb1.append("</xml>");
		System.out.println("传递参数----------"+sb1);
URL url2;
		try {
			url2 = new URL(url);
			HttpURLConnection conn = (HttpURLConnection) url2.openConnection();
conn.setConnectTimeout(30000); // 设置连接主机超时(单位:毫秒)
conn.setReadTimeout(30000); // 设置从主机读取数据超时(单位:毫秒)
conn.setDoOutput(true); // post请求参数要放在http正文内,顾设置成true,默认是false
conn.setDoInput(true); // 设置是否从httpUrlConnection读入,默认情况下是true
conn.setUseCaches(false); // Post 请求不能使用缓存
// 设定传送的内容类型是可序列化的java对象(如果不设此项,在传送序列化对象时,当WEB服务默认的不是这种类型时可能抛java.io.EOFException)
conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
conn.setRequestMethod("POST");// 设定请求的方法为"POST",默认是GET
conn.setRequestProperty("Content-Length",sb1.length()+"");
String encode = "utf-8";
OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream(), encode);
out.write(sb1.toString());
out.flush();
out.close();
// 获取响应内容体
BufferedReader in = new BufferedReader(new InputStreamReader(
conn.getInputStream(), "UTF-8"));
String line = "";
StringBuffer strBuf = new StringBuffer();
while ((line = in.readLine()) != null) {
strBuf.append(line).append("\n");
}
in.close();
			System.out.println("统一支付返回数据-------"+strBuf);
			map = WeixinMessageUtil.parseXml(strBuf.toString());
map.put("timeStamp", timeStamp);
			map.put("nonceStr", nonceStr);
if(map.get("result_code").equals("SUCCESS") && map.get("return_code").equals("SUCCESS")){
//统一下单成功后可以进入
//重新生成的签名主要是用于H5调用API支付的时候使用的,这个和统一下单返回的签名是不一样的
				String paySignTemp = "appId="+appId+"&nonceStr="+nonceStr+"&package=prepay_id="+map.get("prepay_id")+"&signType=MD5&timeStamp="+timeStamp+"&key="+key;
				System.out.println("paySignTemp--------"+paySignTemp);
				String paySign = MD5Util.MD5Encode(paySignTemp, "UTF-8");
				map.put("paySign", paySign);
				System.out.println("paySign---------"+paySign);
				//将订单预支付信息保存数据库
				BasePrepayOrderInfo prepayOrder = (BasePrepayOrderInfo) basedao.findUniqueResult("from BasePrepayOrderInfo where orderCode='"+outTradeNo+"'");
				if(prepayOrder == null){
					prepayOrder = new BasePrepayOrderInfo();
					prepayOrder.setOrderCode(outTradeNo);
					prepayOrder.setPrepayId(map.get("prepay_id").toString());
					prepayOrder.setPaySign(paySign);
					basedao.save(prepayOrder);
				}
			}else {
				System.out.println("统一下单失败");
			}
//map.put("paySign", sign1);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
//map.clear();
		return map;
	}
统一下单需要用到的一下接口
/**
	 * 获取ip
	 * @param request
	 * @return
	 */
	public static String getIp(HttpServletRequest request) {
		if (request == null)
			return "";
		String ip = request.getHeader("X-Requested-For");
		if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("X-Forwarded-For");
		}
		if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("HTTP_CLIENT_IP");
		}
		if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("HTTP_X_FORWARDED_FOR");
		}
		if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		return ip;
	}
package com.ioif.util;
import java.security.MessageDigest;
public class MD5Util {
	private 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;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }
public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString;
    }
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
        "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
}
XML进行解析接口
public static Map<String, Object> parseXml(String xmlStr) throws Exception {
			// 将解析结果存储在HashMap中
			Map<String, Object> map = new HashMap<String, Object>();
			InputStream is = new ByteArrayInputStream(xmlStr.getBytes("UTF-8"));
			// 读取输入流
			SAXBuilder builder = new SAXBuilder();
			org.jdom.Document document = builder.build(is);
			// 得到xml根元素
			org.jdom.Element root = document.getRootElement();
			// 得到根元素的所有子节点
			List<org.jdom.Element> elementList = root.getChildren();
			Iterator it = elementList.iterator();
			while (it.hasNext()) {
				org.jdom.Element e = (org.jdom.Element)it.next();
				String k  = e.getName();
				String v = null;
				List children =  e.getChildren();
				if(children.isEmpty()){
					v = e.getTextNormalize();
				}else{
					v = getChildrenText(children);
				}
				map.put(k, v);
			}
			return map;
		}
		 public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if(!children.isEmpty()) {
Iterator it = children.iterator();
while(it.hasNext()) {
org.jdom.Element e = (org.jdom.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();
}
2.统一下单成功后可以使用H5去调用支付API,js代码如下
function onBridgeReady(orderCode,goodsName,orderPrice){
	var orderQueryUrl = localUrl + "wx/orderQuery";
	$.post(orderQueryUrl,{orderCode:orderCode,goodsName:goodsName,orderPrice:orderPrice},function(res){
		if(res.return_code=="SUCCESS"&&res.result_code=="SUCCESS"){
			pay(orderCode, goodsName, orderPrice);
		}
	},"json");
}
function pay(orderCode,goodsName,orderPrice){
	var url = localUrl+"wx/unifiedorder";
	$.post(url,{orderCode:orderCode,goodsName:goodsName,orderPrice:orderPrice},function(res){
		if(res.result_code=="SUCCESS" && res.return_code=="SUCCESS"){
			var appId = res.appid;
			var mchId = res.mch_id;
			var nonceStr = res.nonceStr;
			var body = goodsName;
			var outTradeNo = orderCode;
			var totalFee = orderPrice;
			alert("appid--"+appId+"---prepay_id--"+res.prepay_id+"----paySign"+res.sign);
			WeixinJSBridge.invoke(
					'getBrandWCPayRequest',{
						"appId":appId,
						"timeStamp":res.timeStamp,
						"nonceStr":nonceStr,
						"package":"prepay_id="+res.prepay_id,
						"signType":"MD5",
						"paySign":res.paySign //这一块使用的签名是统一下单完成后重新生成的
					},function(data){
						alert(data.err_msg)
						//if(res.err_msg=="get_brand_wcpay_request:ok"){}
					})
		}else{
			alert(res.return_msg)
		}				
	},"json");
}
function callPay(orderCode,goodsName,orderPrice){
	if(typeof(WeixinJSBridge) =="undefined"){
		if(document.addEventListener){
			document.addEventListener('WeixinJSBridgeReady',onBridgeReady(orderCode,goodsName,orderPrice),false);
		}else if(document.attachEvent){
			document.attachEvent("WeixinJSBridgeReady",onBridgeReady(orderCode,goodsName,orderPrice));
			document.attachEvent("onWeixinJSBridgeReady",onBridgeReady(orderCode,goodsName,orderPrice));
		}
	}else{
		onBridgeReady(orderCode,goodsName,orderPrice);
	}
}
H5调用支付API有时候一闪而过,一方面就是因为签名失败,但是在JS端它只会报“get_brand_wcpay_request:fail”,
另一方面就是支付授权目录的问题,这个是需要配置到最后一级目录的,要不然验证不过,这就是微信最坑爹的部分
调用支付API中途放弃支付,再重新支付调用统一下单会在微信端报错订单号重复,这时就需要换个新的订单号进行统一下单
3.支付完成后微信会发生通支付自己后端写的通知接口,统一下单中notify_url写的路径是什么就会通知到哪里
通知接口代码如下:
public Map<String, Object> doUpdateOrderStatus(HttpServletRequest request,HttpServletResponse response) {
		try {
			// 获取微信支付完成后返回的参数
			BufferedReader in = new BufferedReader(new InputStreamReader(
					request.getInputStream(), "UTF-8"));
			String line = "";
			StringBuffer strBuf = new StringBuffer();
			while ((line = in.readLine()) != null) {
				strBuf.append(line).append("\n");
			}
			in.close();
			map = WeixinMessageUtil.parseXml(strBuf.toString());
			System.out.println("微信通知参数-------"+map);
			Object returnCode = map.get("return_code");// 返回状态码
			if ("SUCCESS".equals(returnCode)) {
String openId =  map.get("openid").toString();
				String ordercode = map.get("out_trade_no").toString();
///////////////////////////////////
成功后可以自行修改数据库数据
/////////////////////////////
//修改完成后返回成功参数给微信,未接收到成功信息微信会隔一段时间发送一次通知
				StringBuffer sb1 = new StringBuffer();
		         sb1.append("<xml>");
		         sb1.append("<return_code><![CDATA[SUCCESS]]></return_code>");
		         sb1.append("<return_msg><![CDATA[OK]]></return_msg>");
		         sb1.append("</xml>");
		         try {
		 			response.setContentType("application/json; charset=utf-8");
		 			PrintWriter out = response.getWriter();
		 			// 将流传递给微信端
		 			out.print(sb1);
		 			// 关闭流
		 			out.flush();
		 			out.close();
} catch (IOException ex) {
		 			ex.printStackTrace();
		 		}
			} else {
				map.put("statue", "0");
				map.put("error", map.get("return_msg"));
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
return map;
	}
至此微信支付整个流程就开发完成了~~~~~希望以后别再碰到这玩意了
V3微信支付开发笔录的更多相关文章
- PHP微信支付开发实例
		这篇文章主要为大家详细介绍了PHP微信支付开发过程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 PHP微信支付开发过程,分享给大家,供大家参考,具体内容如下 1.开发环境 Thinkphp 3. ... 
- 微信支付开发(11) Native支付
		关键字:微信公众平台 微信支付 Native原生支付作者:方倍工作室原文:http://www.cnblogs.com/txw1958/p/wxpay-native.html 由于微信支付接口更新,本 ... 
- Android开发 --微信支付开发(转载!)(开发工具:Eclipse)
		Android_APP 微信支付接口开发 日期:2015-10-06 12:47:33 作者: 来源: 人气:3549 1.首先说一下我们在开发微信支付接口的时候遇到最多和最疑惑的问题,那就是明明 a ... 
- 微信支付开发+{ping++}微信支付托管
		------------------------微信支付接口------------------------------- 微信支付开发并没有想象中的那么难,主要是微信提供了sdk. 微信公众号必须是 ... 
- PHP微信支付开发之扫描支付(模式二)后如何回调
		其实在写这篇文章的时候感觉自己已经落伍了,不过笔者在百度上搜索"微信支付开发之扫描支付(模式二)后如何回调"寻找答案时,发现依旧有很多朋友没有解决这个问题,所以就把自己的解决思路分 ... 
- PHP微信支付开发
		此链接https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_2,是微信官方的示例,无效,报错. 1.申请微信支付的开通条件?什么样的账号可以 ... 
- 微信支付开发出现redirect_uri参数错误的解决方法
		我们在进行微信支付开发的时候会遇到出现“redirect_uri参数错误”这种情况,怎么办呢?下面就是我总结出现这种“redirect_uri参数错误”的七种可能情况,以及解决方式. 1.可能原因①: ... 
- 微信支付开发 c# SDK JSAPI支付开发的流程和微信大坑
		微信支付开发流程 1. 开通微信支付功能 省略 2. 下载微信的C#版的微信SDK 下载连接:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chap ... 
- 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_1-1.SpringBoot整合微信支付开发在线教育视频站点介绍
		笔记 第一章项目介绍和前期准备 1.SpringBoot整合微信支付开发在线教育视频站点介绍 简介: 课程介绍,和小D课堂在线教育项目搭建开发 1.课程大纲介绍 2.微信支付项 ... 
随机推荐
- CF1166E The LCMs Must be Large
			CF1166E The LCMs Must be Large 构造趣题 正着推其实很不好推 不妨大力猜结论 如果两两集合都有交,那么一定可以 证明: 1.显然如果两个集合没有交,一定不可以 2.否则给 ... 
- Python 基础课程大纲
			c0102_变量及数据类型.ipynb 1.数据类型概述 Python标准数据类型:Numbers数字,String字符串,List列表,Tuple元祖,Dici字典.布尔类型 # Numbers ... 
- Codeforces Round #196 (Div. 1 + Div. 2)
			A. Puzzles 对\(f[]\)排序,取连续的\(m\)个. B. Routine Problem 考虑\(\frac{a}{b}\)和\(\frac{c}{d}\)的大小关系,适配后就是分数的 ... 
- spring security BCryptPasswordEncoder加密解密,不错的随机盐,不错的加密解密方法
			项目中用这个加密感觉不错啊,推荐: 1.先大体看看,了解一下 浅谈使用springsecurity中的BCryptPasswordEncoder方法对密码进行加密(encode)与密码匹配(match ... 
- C# 单例类
			单例类 有时候我们不要在一个程序中创建太多的实例.只想用一个全局的实例和一个可以访问点.那么我们需要一个单例类. 因为是单例类啦,所以构造函数肯定是私有的. 需要了解的术语 懒汉式 顾名思义.什么时候 ... 
- 被孟加拉题吊打的ACM考试
			https://codeforces.com/gym/101864 题目并不难 B 考虑新加入的线段和之前线段有交的个数 总数-不交的,不交的:右端点在[l,r]左边,左端点在[l,r]右边的. 维护 ... 
- vue移动端图片上传压缩
			上传压缩方法 import {api} from '../../api/api.js'; import axios from 'axios'; export function imgPreview ( ... 
- Vue学习笔记-使用ElementUI
			ElementUI官方地址:https://element.eleme.cn/2.11/#/zh-CN 1.初期准备 首先我们准备几个基本的样式文件:normalize.css 和 base.css ... 
- U8 EAI实现XML的生成
			/*************************************************************************************************** ... 
- (转载)window安装mysql
			一.MYSQL的安装 1.打开下载的mysql安装文件mysql-5.5.27-win32.zip,双击解压缩,运行“setup.exe”. 2.选择安装类型,有“Typical(默认)”.“Comp ... 
