天天看点

微信和支付宝支付实战

最近的项目中要用到移动支付。在此总结下

1、微信支付 :因为需求是扫码支付即时到账,用的是native方式。按照官方文档的说法,调用“https://api.mch.weixin.qq.com/pay/unifiedorder” 这个接口生成一个预支付的订单,微信会返回给你一个订单的二维码的地址,然后把二维码解析展示出来用户扫码就可以支付了。

    //微信支付

String random = RandomStringGenerator.getRandomStringByLength(32);
  Map<String,Object> params = new HashMap<String,Object>();
  params.put("appid", TenConfig.getAppid());
  params.put("mch_id", TenConfig.getMchid());
  params.put("nonce_str", random);
  params.put("body", body.toString().trim());
  params.put("out_trade_no", order.getOrderNumber());
  params.put("total_fee", sump);
  params.put("spbill_create_ip", TenConfig.SPBILL_CREATE_IP);
  params.put("notify_url", TenConfig.getNotify_url());
  params.put("trade_type", "NATIVE");
  params.put("product_id", productId);
  String sign = TenUtils.getSign(params);
  //拼接xml
  StringBuilder xml = new StringBuilder("<xml>");
  xml.append("<appid>").append("<![CDATA["+TenConfig.getAppid()+"]]>").append("</appid>");
  xml.append("<mch_id>").append("<![CDATA["+TenConfig.getMchid()+"]]>").append("</mch_id>");
  xml.append("<nonce_str>").append("<![CDATA["+random+"]]>").append("</nonce_str>");
  xml.append("<body>").append("<![CDATA["+body.toString().trim()+"]]>").append("</body>");
  xml.append("<out_trade_no>").append("<![CDATA["+order.getOrderNumber()+"]]>").append("</out_trade_no>");
  xml.append("<total_fee>").append("<![CDATA["+sump+"]]>").append("</total_fee>");
  xml.append("<spbill_create_ip>").append("<![CDATA["+TenConfig.SPBILL_CREATE_IP+"]]>").append("</spbill_create_ip>");
  xml.append("<notify_url>").append("<![CDATA["+TenConfig.getNotify_url()+"]]>").append("</notify_url>");
  xml.append("<trade_type>").append("<![CDATA[NATIVE]]>").append("</trade_type>");
  xml.append("<product_id>").append("<![CDATA["+productId+"]]>").append("</product_id>");
  xml.append("<sign>").append("<![CDATA["+sign+"]]>").append("</sign>");
  xml.append("</xml>");<pre name="code" class="java">  logger.info("XML:\n"+xml.toString());
  String resStr = TenUtils.postData(TenConfig.UNIFIED_ORDER, xml.toString(), "text/html;charset=utf-8");
           

这一步主要是拼接预下单接口的参数,要注意的是请求的要用utf-8编码下,不然可能会出现签名错误之类的返回。

把MD5签名算法也贴上 在这里被编码坑了。

ArrayList<String> list = new ArrayList<String>();
        for(Map.Entry<String,Object> entry:map.entrySet()){
            if(entry.getValue()!=""){
                list.add(entry.getKey() + "=" + entry.getValue() + "&");
            }
        }
        int size = list.size();
        String [] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < size; i ++) {
            sb.append(arrayToSort[i]);
        }
        String result = sb.toString();
        result += "key=" + TenConfig.getKey();  	    
        result = MD5Encrypt.MD5Encode(result, "utf-8").toUpperCase();
        return result;
           

MD5签名的时候切记要用“utf-8”编码,不然会出现签名错误,这是个深坑,接口文档是没有说明的,

贴上MD5算法

public static String md5(String s)
  {
    byte[] input = s.getBytes();
    String output = null;
    
    char[] hexChar = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 
      'a', 'b', 'c', 'd', 'e', 'f' };
    try
    {
      MessageDigest md = MessageDigest.getInstance("MD5");
      md.update(input);
      


      byte[] tmp = md.digest();
      char[] str = new char[32];
      byte b = 0;
      for (int i = 0; i < 16; i++)
      {
        b = tmp[i];
        str[(2 * i)] = hexChar[(b >>> 4 & 0xF)];
        str[(2 * i + 1)] = hexChar[(b & 0xF)];
      }
      output = new String(str);
    }
    catch (NoSuchAlgorithmException e)
    {
      e.printStackTrace();
    }
    return output;
  }
  
  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 localException) {}
    return resultString;
  }
  
  private static final String[] hexDigits = { "0", "1", "2", "3", "4", "5", 
    "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
           

然后创建连接去请求微信接口

BufferedReader reader = null;
        try {
            URL url = new URL(urlStr);
            URLConnection conn = url.openConnection();
            conn.setDoOutput(true);
            conn.setConnectTimeout(CONNECT_TIMEOUT);
            conn.setReadTimeout(CONNECT_TIMEOUT);
            if(contentType!=null && !"".equals(contentType))
                conn.setRequestProperty("content-type", contentType);
            OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);
            if(data == null)
                data = "";
            writer.write(data); 
            writer.flush();
            writer.close();  
 
            reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
                sb.append("\r\n");
            }
            return sb.toString();
        } catch (IOException e) {
        	logger.error("Error connecting to " + urlStr + ": " + e.getMessage());
        } finally {
            try {
                if (reader != null)
                    reader.close();
            } catch (IOException e) {
            }
        }
        return null;
           

调用之后会拿到一个xml 里面是返回值。解析xml

Map resMap = XMLParser.getMapFromXML(resStr);
						
  if(resMap == null){
     logger.info("统一下单接口调用失败");
  }else{
     String returnCode = (String) resMap.get("return_code");
     if(returnCode.equals("SUCCESS")){
     logger.info("统一下单接口调用成功");
     String codeUrl = (String) resMap.get("code_url");
     //把二维码连接返回给页面
     result.put("msg", codeUrl);
								
     //更新订单的支付方式,金额等
     order.setPayAmount(Float.parseFloat(sumprice));
     order.setPayType(payType);
     order.setWxPayNonceStr(random);
     order.setBody(body.toString());
     if(orderService.updateWhenPay(order) > 0){
	 result.put("code", "ok");
     }else{
	 result.put("code", "err");
     }
  }
           

解析xml把生成的二维码给页面 通过jQuery的qrcode.js把地址生成二维码。

<span style="white-space:pre">	</span>$.post("/xxx/xxxxx.jspx",{"orderId":orderId
	,"payType":payType,"sumPrice":price},
	function(data){
		if(data&&data.code=="ok"){
			if(payType==1){
				//微信支付
				$("#model_content").css("display","block");
				jQuery('#qrcodeCanvas').qrcode({
					render   : "canvas",//设置渲染方式  
					width       : 210,     //设置宽度  
					height      : 210,     //设置高度  
					typeNumber  : -1,      //计算模式  
					correctLevel    : QRErrorCorrectLevel.H,//纠错等级  
					background      : "#ffffff",//背景颜色  
					foreground      : "#000000", //前景颜色
					text	: data.msg
				});
				intel =setInterval(function(){
					//请求查询订单状态
					queryOrderStatus(orderId,intel);
				},2000); 
			}else if(payType==2){
				//支付宝
				$("#ali_pay").html(data.msg);
			}
		}else{
			window.wxc.xcConfirm("支付失败!", window.wxc.xcConfirm.typeEnum.error);
		}
	});
           

用qrcode.js的生成二维码的时候 render   建议用canvas方式 虽然它还提供了table方式,但是我用的时候table方式无法生成二维码的但是canvas是可以的 ,没弄懂为什么。如果顺利的话 二维码就生成了,扫码就可以支付了。解释下这段js代码,请求成功后用一个遮罩层,把二维码放遮罩层上,然后用一个函数去轮询这个订单的状态,因为调用时异步的吗,如果查询到订单已经支付了的话,清除掉轮询并就跳转到支付完成页面。

下面贴微信回调的代码

try {
		//获取微信回调的流
		BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream)request.getInputStream()));
<span style="white-space:pre">	</span>        String line = null;
        <span style="white-space:pre">	</span>StringBuilder sb = new StringBuilder();
<span style="white-space:pre">	</span>        while((line = br.readLine())!=null){
	            sb.append(line);
	        }
		//把微信返回的xml转换成map
	        Map result = XMLParser.getMapFromXML(sb.toString());
	        
		//返回给微信的xml
	        StringBuilder reXML = new StringBuilder("<xml>");
	        
	        //return_code 字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
	        if(result.get("return_code").toString().equals("SUCCESS")){
	        	//回调成功
	        	logger.info("微信支付成功,返回的XML:\n"+sb.toString());
	        	
		        //获取微信回调的参数
		        String orderNum = (String) result.get("out_trade_no");
		        //业务结果
		        String resultCode = (String) result.get("result_code");
		        
		        String transactionId = result.get("transaction_id").toString();
		        
		        String sign = result.get("sign").toString();
		        if(result.get("sign")!=null){
		        	//去除Sign
		        	result.remove("sign");
		        }
		        if(sign.equals(TenUtils.getSign(result))){
	        		//签名通过,判断交易状态
	        		if(resultCode.equals("SUCCESS")){
	        			if(orderNum !=null && !"".equals(orderNum)){
	    		        	Order order = orderService.findByOrderNum(orderNum);
	    		        	if(order != null){
	    	        			//支付成功
	    	        			if(order.getStatus() == 0){
	    						//订单如果是待支付状态,更改订单状态为确认中状态(1),更新支付完成时间
	    						orderService.updateStatus(order.getId(), 1);
	    						orderService.updatePayCompleteTime(order.getId());
	    						
	    						reXML.append("<return_code><![CDATA[SUCCESS]]></return_code>");
	    						reXML.append("<return_msg><![CDATA[OK]]></return_msg>");
	    							
	    						//插入支付日志
	    						Map<String,String> map = new HashMap<String,String>();
	    						map.put("pro_type", "销售");
	    						map.put("pay_type", "微信");
	    						map.put("pro_info", order.getBody());
	    						map.put("user_name", order.getBuyMember().getUsername());
	    						map.put("trade_no", orderNum);
	    						map.put("pay_amount", order.getPayAmount()+"");
	    						map.put("status", "支付完成");
	    						map.put("buyer_email", transactionId);
	    						orderService.insertPayLog(map);
	    	        			}else{
	    						reXML.append("<return_code><![CDATA[FAIL]]></return_code>");
	    						reXML.append("<return_msg><![CDATA[订单状态异常]]></return_msg>");
	    	        			}
			        		}else{
							reXML.append("<return_code><![CDATA[FAIL]]></return_code>");
							reXML.append("<return_msg><![CDATA[参数[out_trade_no]错误]]></return_msg>");
			        		}
				        }else{
						reXML.append("<return_code><![CDATA[FAIL]]></return_code>");
						reXML.append("<return_msg><![CDATA[参数[out_trade_no]为空]]></return_msg>");
				        }
		        	}else{
		        	 <span style="white-space:pre">	</span>logger.info("交易状态异常:"+resultCode);
		        	}
		        }else{
				reXML.append("<return_code><![CDATA[FAIL]]></return_code>");
				reXML.append("<return_msg><![CDATA[签名失败]]></return_msg>");
		        }
	        }else{
	        	logger.info("微信支付回调失败,失败原因:"+result.get("return_msg").toString());
	        }
	        <span style="white-space:pre">	</span>reXML.append("</xml>");
	        <span style="white-space:pre">	</span>logger.info("返回给微信的XML:\n"+reXML);
	        <span style="white-space:pre">	</span>response.getWriter().write(new String(reXML.toString().getBytes("UTF-8"),"ISO-8859-1"));
		} catch (Exception e) {
		<span style="white-space:pre">	</span>logger.info(e.getMessage(), e);
		}
           

支付成功后微信会调用这个方法,这个方法主要用于修改订单状态。当订单状态改变了,js的queryOrderStatus方法检测到订单支付完成了,就会跳转到支付完成页面。就完成了。把js轮训方法也贴上

//查询订单状态
function queryOrderStatus(orderId,intel){
	$.post("/xxx/xxxx.jspx",{"orderId":orderId},
	function(data){
		if(data && data.orderId == orderId){
			//查询成功
			if(data.code=="ok"){
				//订单查询成功
				if(data.status==1){
					//支付成功,清除轮训并刷新页面
					clearInterval(intel);
					window.location.href = "/xxxx/xxxx.jspx?orderId="+orderId+"&guid="+guid();			
				}
			}
		}
	});
}
           

酱紫微信支付就完成了。

微信和支付宝支付实战

下面说说支付宝支付,因为支付官方提供的有demo 拿过来改改就能直接用了,这个比微信方便多了。直接贴代码

//支付宝支付
//把请求参数打包成数组
Map<String, String> params = new HashMap<String, String>();
params.put("service", AlipayConfig.service);
params.put("partner", AlipayConfig.partner);
params.put("seller_id", AlipayConfig.seller_id);
params.put("_input_charset", AlipayConfig.input_charset);
//params.put("qr_pay_mode", AlipayConfig.qr_pay_mode);
//异步通知支付结果连接
//params.put("notify_url", AlipayConfig.notify_url);
//支付宝处理完请求后,自动跳转到指定页面
params.put("return_url", AlipayConfig.return_url);
//傻逼支付宝“anti_phishing_key”同步回调不返回这个参数,请求的时候不传递
//params.put("anti_phishing_key", AlipaySubmit.query_timestamp());
//支付用户的ip
params.put("exter_invoke_ip", AlipayConfig.exter_invoke_ip);
params.put("out_trade_no", order.getOrderNumber());
params.put("subject", body.toString());
params.put("payment_type", AlipayConfig.payment_type);
params.put("it_b_pay", AlipayConfig.it_b_pay);
params.put("total_fee", sumprice);
params.put("body", body.toString());
						
//请求支付接口
html = AlipaySubmit.buildRequest(params, "POST","确认");
logger.info("支付宝请求HTML:\n"+html);
//更新订单的支付方式,金额等
order.setPayAmount(Float.parseFloat(sumprice));
order.setPayType(payType);
if(orderService.updatePay(order) > 0){
	result.put("code", "ok");
}else{
	result.put("code", "err");
}
result.put("msg", html);
           

我这里用的是同步回调方式,当然也可以用异步和微信是一样的,支付宝是拼接一个自动提交的form表单,然后把这歌表单写到页面就能跳转到支付宝收银台,代码在上面微信生成二维码那里,拿下来

//支付宝
$("#ali_pay").html(data.msg);
           

这样子表单会自动提交,页面会跳转到支付宝收银台,完成支付后会从支付宝那里跳转 需要一个处理的方法,贴代码

try {
			//获取支付宝POST过来反馈信息
			Map<String,String> params = new HashMap<String,String>();
			Map requestParams = request.getParameterMap();
			for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
				String name = (String) iter.next();
				String[] values = (String[]) requestParams.get(name);
				String valueStr = "";
				for (int i = 0; i < values.length; i++) {
					valueStr = (i == values.length - 1) ? valueStr + values[i]
							: valueStr + values[i] + ",";
				}
				//乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
				//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "UTF-8");
				params.put(name, valueStr);
			}
			
			//商户订单号
			String out_trade_no = request.getParameter("out_trade_no");

			//支付宝交易号
			String trade_no = request.getParameter("trade_no");

			//交易状态
			String trade_status = request.getParameter("trade_status");
			
			//支付价格
			String total_fee = request.getParameter("total_fee");
			
			String seller_id = request.getParameter("seller_id");
			
			//买家支付宝账号
			String buyer_email = request.getParameter("buyer_email");
			
			//产品描述
			String body = request.getParameter("body");

			Float total_pay = Float.parseFloat(total_fee);
			
			boolean verify_result = AlipayNotify.verify(params);
			
			if(verify_result){//验证成功
				Order o = orderService.findByOrderNum(out_trade_no);
				if(o != null){
					//请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
					if(o.getPayAmount() == total_pay && AlipayConfig.seller_id.equals(seller_id)){
						//请求参数和回调参数一致
						if(trade_status.equals("TRADE_FINISHED") || trade_status.equals("TRADE_SUCCESS")){
							//注意:付款完成后,支付宝系统发送该交易状态通知
							//判断该笔订单是否是待付款状态
							if(o.getStatus() == 0){
								//如果没有做过处理,更改订单状态为确认中状态(1),更新支付完成时间
								orderService.updateStatus(o.getId(), 1);
								orderService.updatePayCompleteTime(o.getId());
								
								//插入支付日志
								Map<String,String> map = new HashMap<String,String>();
								map.put("pro_type", "销售");
								map.put("pay_type", "支付宝");
								map.put("pro_info", body);
								map.put("user_name", o.getBuyMember().getUsername());
								map.put("trade_no", trade_no);
								map.put("pay_amount", o.getPayAmount()+"");
								map.put("status", "支付完成");
								map.put("buyer_email", buyer_email);
								orderService.insertPayLog(map);
								//跳转到支付成功页面
								return "redirect:/xxx/xxxx.jspx?orderId="+o.getId();
							}
						}
					}
				}
			}else{//验证失败
				response.setContentType("text/html;charset=UTF-8");
				response.setCharacterEncoding("UTF-8");
				response.getWriter().write("Sign Failure , 验签失败!");
			}
		} catch (Exception e) {
			logger.info(e.getMessage(), e);
		}
           

这个地方要注意 支付宝给的demo 是这样写的

<span style="white-space:pre">	</span>乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
<span style="white-space:pre">	</span>valueStr = new String(valueStr.getBytes("ISO-8859-1"), "UTF-8");
           

这里千万不要一上来就用“UTF-8” 不然会乱码 然后就会出现签名错误的问题。至此微信和支付宝支付都做成功了。

微信和支付宝支付实战

继续阅读