java实现微信h5支付

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/github_38924695/article/details/78760164

微信h5支付需要在微信商户平台-》产品中心开通h5支付。
这里写图片描述
官网提供的开发文档中需要的参数:
这里写图片描述这里写图片描述这里写图片描述
h5支付主要是scene_info中的参数wap_url必须是可以访问到的地址。spbill_create_ip的获取必须和调起微信支付的ip一致。
代码实现如下:
action中代码:

private static DecimalFormat df = new DecimalFormat("0.00");
 @Action("weChatUnifiedorder")
   public String weChatUnifiedorder(){
	   try {
df.setRoundingMode(RoundingMode.HALF_UP);
// 获取用户ID
			String memberId = Struts2Utils.getRequest().getParameter("memberId");
			String integer = Struts2Utils.getRequest().getParameter("integer");
			String type = Struts2Utils.getRequest().getParameter("type");
//我这里的ip直接从前端页面获取部分手机不能获取
			String spbill_create_ip = Struts2Utils.getRequest().getParameter("ip");
			if (StringUtils.isNotBlank(memberId)
					&& StringUtils.isNotBlank(integer)
					&& StringUtils.isNotBlank(type)) {
				Integer score = Integer.valueOf(integer);
				// 产生订单号
				String outTradeNo = UuIdUtils.getUUID();
				// WeixinConfigUtils config = new WeixinConfigUtils();
				// 参数组
				String appid = config.appid;
				String mch_id = config.mch_id;
				String nonce_str = RandCharsUtils.getRandomString(16);
				String body = "";	
				if (type.equals("0")) {
					body = "购买" + score + "积分,支付" + df.format(score / 100)
							+ "元";
				} else if (type.equals("1")) {
					body = "购买商品支付" + String.valueOf(score / 100) + "元";
				}
				String attach = "暂时无用(这个用于区分不同版本)";
				String out_trade_no = outTradeNo;
				int total_fee = score;// 单位是分,现在按照ios传递过来的参数进行
				String notify_url = config.notify_url;
				String trade_type = "MWEB";
				// 参数:开始生成第一次签名
				parameters.put("appid", appid);
				parameters.put("mch_id", mch_id);
				parameters.put("body", body);
				parameters.put("nonce_str", nonce_str);
				parameters.put("attach", attach);
				parameters.put("out_trade_no", out_trade_no);
				parameters.put("total_fee", total_fee);
				parameters.put("notify_url", notify_url);
				parameters.put("trade_type", trade_type);
				parameters.put("spbill_create_ip", spbill_create_ip);
				parameters.put("scene_info", "'h5_info':{'type':'Wap','wap_url':'www.abc.com(必须可以直接访问,**这里不要带http://**)','wap_name': '应用名(会在微信订单页显示)'}");
				String sign = WXSignUtils.createSign("UTF-8", parameters);
				// 微信统一下单
				unifiedorder.setAppid(appid);
				unifiedorder.setMch_id(mch_id);
				unifiedorder.setNonce_str(nonce_str);
				unifiedorder.setSign(sign);
				unifiedorder.setBody(body);
				unifiedorder.setAttach(attach);
				unifiedorder.setOut_trade_no(out_trade_no);
				unifiedorder.setTotal_fee(total_fee);
				unifiedorder.setSpbill_create_ip(spbill_create_ip);
				unifiedorder.setNotify_url(notify_url);
				unifiedorder.setTrade_type(trade_type);
				unifiedorder.setScene_info("'h5_info':{'type':'Wap','wap_url':'http://www.abc.com(必须可以直接访问**这里不要带http://**)','wap_name': '应用名(会在微信订单页显示)'}");
				Map<String, String> msgMap = HttpXmlUtils.getUrl(unifiedorder);
				if (msgMap.get("return_code").equals("SUCCESS") && msgMap.get("result_code").equals("SUCCESS")) {
					aLiPay.setApp_id(appid);
					aLiPay.setSubject(body);
					aLiPay.setScore(integer);
					aLiPay.setGmt_create(simpleDateFormat.format(new Date()));
					if (null != memberId && !"".equals(memberId)) {
						aLiPay.setMember(memberService.getMemberById(Integer
								.valueOf(memberId)));
					}
					aLiPay.setOut_trade_no(out_trade_no);
					aLiPay.setPayStatus(PayStatusEnum.WAITPAY.getComment());
					aLiPay.setPayType(PayTypeEnum.WEIXIN.getComment());
					aLiPay.setState(0);
					aLiPay.setTotal_amount(String.valueOf(total_fee / 100));
					aLiPay.setSign(sign);
					aLiPay.setTotalFee(total_fee);
					aLiPayService.addALiPay(aLiPay);
					msg.put("ok", true);
	//将回调地址发回前端,支付完成后会自动跳转该页面(回调url必须带http://)
					msg.put("url", msgMap.get("mweb_url")+"&redirect_url=http://www.jabc.com/member/toMemberIntegral.do");
					Struts2Utils.renderJson(msg);
				}else {
					msg.put("ok", false);
				}
			}
			return NONE;
		} catch (Exception e) {
			log.error(e.getMessage());
			return "500";
		}
   }

微信支付签名代码:

public class WXSignUtils {
	/**
	 * 微信支付签名算法sign
	 * @param characterEncoding
	 * @param parameters
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public static String createSign(String characterEncoding,SortedMap<Object,Object> parameters){
		StringBuffer sb = new StringBuffer();
		Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
		Iterator it = es.iterator();
		while(it.hasNext()) {
			Map.Entry entry = (Map.Entry)it.next();
			String k = (String)entry.getKey();
			Object v = entry.getValue();
			if(null != v && !"".equals(v) 
					&& !"sign".equals(k) && !"key".equals(k)) {
				sb.append(k + "=" + v + "&");
			}
		}
		sb.append("key=" + weixinConstant.KEY);
		String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
		return sign;
	}
}

向微信发送post数据并解析返回的微信订单号

public class HttpXmlUtils {
/**
	 * h5支付时 解析返回的值并返回prepareid
	 * @throws IOException 
	 * @throws JDOMException 
	 */
	public static Map<String, String> getUrl(Unifiedorder unifiedorder) throws JDOMException, IOException{
	//构造xml
		String xmlInfo = HttpXmlUtils.xmlH5Info(unifiedorder);
		String wxUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
		String method = "POST";
		//获取微信返回的结果
		String weixinPost = HttpXmlUtils.httpsRequest(wxUrl, method, xmlInfo).toString();
		//解析返回的xml
		ParseXMLUtils.jdomParseXml(weixinPost);
		StringReader read = new StringReader(weixinPost);
		// 创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入
		InputSource source = new InputSource(read);
		// 创建一个新的SAXBuilder
		SAXBuilder sb = new SAXBuilder();
		// 通过输入源构造一个Document
		org.jdom.Document doc;
		doc = (org.jdom.Document) sb.build(source);
		org.jdom.Element root = doc.getRootElement();// 指向根节点
		List<org.jdom.Element> list = root.getChildren();
		String prepayId =null;
		Map<String, String> msg = new HashMap<String, String>();
		if(list!=null&&list.size()>0){
			for (org.jdom.Element element : list) {
			msg.put(element.getName(), element.getText());
			}
			}
		return msg;
	}
	/**
	 * 构造xml参数
	 * @param xml
	 * @return
	 */
	public static String xmlH5Info(Unifiedorder unifiedorder){
		if(unifiedorder!=null){
			StringBuffer bf = new StringBuffer();
			bf.append("<xml>");

			bf.append("<appid><![CDATA[");
			bf.append(unifiedorder.getAppid());
			bf.append("]]></appid>");

			bf.append("<mch_id><![CDATA[");
			bf.append(unifiedorder.getMch_id());
			bf.append("]]></mch_id>");

			bf.append("<nonce_str><![CDATA[");
			bf.append(unifiedorder.getNonce_str());
			bf.append("]]></nonce_str>");

			bf.append("<sign><![CDATA[");
			bf.append(unifiedorder.getSign());
			bf.append("]]></sign>");

			bf.append("<body><![CDATA[");
			bf.append(unifiedorder.getBody());
			bf.append("]]></body>");


			bf.append("<attach><![CDATA[");
			bf.append(unifiedorder.getAttach());
			bf.append("]]></attach>");

			bf.append("<out_trade_no><![CDATA[");
			bf.append(unifiedorder.getOut_trade_no());
			bf.append("]]></out_trade_no>");

			bf.append("<total_fee><![CDATA[");
			bf.append(unifiedorder.getTotal_fee());
			bf.append("]]></total_fee>");

			bf.append("<spbill_create_ip><![CDATA[");
			bf.append(unifiedorder.getSpbill_create_ip());
			bf.append("]]></spbill_create_ip>");

			bf.append("<notify_url><![CDATA[");
			bf.append(unifiedorder.getNotify_url());
			bf.append("]]></notify_url>");

			bf.append("<trade_type><![CDATA[");
			bf.append(unifiedorder.getTrade_type());
			bf.append("]]></trade_type>");
			
			bf.append("<scene_info><![CDATA[");
			bf.append(unifiedorder.getScene_info());
			bf.append("]]></scene_info>");
			
			bf.append("</xml>");
			return bf.toString();
		}
		return "";
	}
/**
	 * post请求并得到返回结果
	 * @param requestUrl
	 * @param requestMethod
	 * @param output
	 * @return
	 */
	public static String httpsRequest(String requestUrl, String requestMethod, String output) {
		try{
			URL url = new URL(requestUrl);
			HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
			connection.setDoOutput(true);
			connection.setDoInput(true);
			connection.setUseCaches(false);
			connection.setRequestMethod(requestMethod);
			if (null != output) {
				OutputStream outputStream = connection.getOutputStream();
				outputStream.write(output.getBytes("UTF-8"));
				outputStream.close();
			}
			// 从输入流读取返回内容
			InputStream inputStream = connection.getInputStream();
			InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			String str = null;
			StringBuffer buffer = new StringBuffer();
			while ((str = bufferedReader.readLine()) != null) {
				buffer.append(str);
			}
			bufferedReader.close();
			inputStreamReader.close();
			inputStream.close();
			inputStream = null;
			connection.disconnect();
			return buffer.toString();
		}catch(Exception ex){
			ex.printStackTrace();
		}
		return "";
	}
}

解析微信返回的xml

/**
	 * 3、JDOM解析XML
	 * 解析的时候自动去掉CDMA
	 * @param xml
	 */
	@SuppressWarnings("unchecked")
	public static void jdomParseXml(String xml){
		try { 
			StringReader read = new StringReader(xml);
			// 创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入
			InputSource source = new InputSource(read);
			// 创建一个新的SAXBuilder
			SAXBuilder sb = new SAXBuilder();
			// 通过输入源构造一个Document
			org.jdom.Document doc;
			doc = (org.jdom.Document) sb.build(source);

			org.jdom.Element root = doc.getRootElement();// 指向根节点
			List<org.jdom.Element> list = root.getChildren();

			if(list!=null&&list.size()>0){
				for (org.jdom.Element element : list) {
				}
			}

		} catch (JDOMException e) {
			e.printStackTrace();
		}  catch (IOException e) {
			e.printStackTrace();
		}catch (Exception e) {
			e.printStackTrace();
		}
	}

封装支付参数的实体类:(参数名与官网参数名一致,此类封装了h5、app支付和退款的参数)

import java.io.Serializable;

/**
 * 统一下单提交为微信的参数
 * @author 
 * @date 2017年08月11日
 */
public class Unifiedorder implements Serializable{
	private static final long serialVersionUID = 1L;
	//微信支付表id
	private Integer weixinId;
	//微信分配的公众账号ID(企业号corpid即为此appId)
	private String appid;
	//商户id
	private String mch_id;
	//终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB"
	private String device_info;
	//随机字符串:数字+大写字母的组合,32位
	private String nonce_str;
	//签名
	private String sign;
	//商品或支付单简要描述
	private String body;
	//商品名称明细列表
	private String detail;
	//附加参数(例如:用于区别本商户不同的分店)
	private String attach;
	//商户系统内部的订单号
	private String out_trade_no;
	//货币类型:符合ISO 4217标准的三位字母代码,默认人民币:CNY
	private String fee_type;
	//总金额
	private int total_fee;
	//APP和网页支付提交[用户端ip],Native支付填调用微信支付API的机器IP。
	private String spbill_create_ip;
	//订单生成时间,格式为yyyyMMddHHmmss,
	private String time_start;
	//订单失效时间,格式为yyyyMMddHHmmss,最短失效时间间隔必须大于5分钟[支付宝是30分钟,同样30分钟]
	private String time_expire;
	//商品标记,代金券或立减优惠功能的参数
	private String goods_tag;
	//接收微信支付异步通知回调地址
	private String notify_url;
	//交易类型:JSAPI,NATIVE,APP h5为 MWEB
	private String trade_type;
	//trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义。
	private String product_id;
	//no_credit--指定不能使用信用卡支付
	private String limit_pay;
	//trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识
	private String openid;
	//商户内部自己的退款单号
	private String out_refund_no;
	//退款总金额单位为分
	private int refund_fee;
	//操作员的id默认为mch_id
	private String op_user_id;
	//微信官方提供的订单号
	private String prepayid;
	//记录所对应的member
	private Member member;
	//返回给微信的状态码(用于支付回调时)
	public String return_code;
	//微信h5支付时候的场景信息官方的信息模板 {"h5_info"://h5支付固定传"h5_info" 
    //{"type":"",//场景类型 "wap_url":"",//WAP网站URL地址"wap_name": ""//WAP 网站名}}
	public String scene_info;
	public String getScene_info() {
		return scene_info;
	}
	public void setScene_info(String scene_info) {
		this.scene_info = scene_info;
	}
	public String getReturn_code() {
		return return_code;
	}
	public void setReturn_code(String return_code) {
		this.return_code = return_code;
	}
	public String getAppid() {
		return appid;
	}
	public String getMch_id() {
		return mch_id;
	}
	public String getDevice_info() {
		return device_info;
	}
	public String getNonce_str() {
		return nonce_str;
	}
	public String getSign() {
		return sign;
	}
	public String getBody() {
		return body;
	}
	public String getDetail() {
		return detail;
	}
	public String getAttach() {
		return attach;
	}
	public String getOut_trade_no() {
		return out_trade_no;
	}
	public String getFee_type() {
		return fee_type;
	}
	public int getTotal_fee() {
		return total_fee;
	}
	public String getSpbill_create_ip() {
		return spbill_create_ip;
	}
	public String getTime_start() {
		return time_start;
	}
	public String getTime_expire() {
		return time_expire;
	}
	public String getGoods_tag() {
		return goods_tag;
	}
	public String getNotify_url() {
		return notify_url;
	}
	public String getTrade_type() {
		return trade_type;
	}
	public String getProduct_id() {
		return product_id;
	}
	public String getLimit_pay() {
		return limit_pay;
	}
	public String getOpenid() {
		return openid;
	}
	public void setAppid(String appid) {
		this.appid = appid;
	}
	public void setMch_id(String mch_id) {
		this.mch_id = mch_id;
	}
	public void setDevice_info(String device_info) {
		this.device_info = device_info;
	}
	public void setNonce_str(String nonce_str) {
		this.nonce_str = nonce_str;
	}
	public void setSign(String sign) {
		this.sign = sign;
	}
	public void setBody(String body) {
		this.body = body;
	}
	public void setDetail(String detail) {
		this.detail = detail;
	}
	public void setAttach(String attach) {
		this.attach = attach;
	}
	public void setOut_trade_no(String out_trade_no) {
		this.out_trade_no = out_trade_no;
	}
	public void setFee_type(String fee_type) {
		this.fee_type = fee_type;
	}
	public void setTotal_fee(int total_fee) {
		this.total_fee = total_fee;
	}
	public void setSpbill_create_ip(String spbill_create_ip) {
		this.spbill_create_ip = spbill_create_ip;
	}
	public void setTime_start(String time_start) {
		this.time_start = time_start;
	}
	public void setTime_expire(String time_expire) {
		this.time_expire = time_expire;
	}
	public void setGoods_tag(String goods_tag) {
		this.goods_tag = goods_tag;
	}
	public void setNotify_url(String notify_url) {
		this.notify_url = notify_url;
	}
	public void setTrade_type(String trade_type) {
		this.trade_type = trade_type;
	}
	public void setProduct_id(String product_id) {
		this.product_id = product_id;
	}
	public void setLimit_pay(String limit_pay) {
		this.limit_pay = limit_pay;
	}
	public void setOpenid(String openid) {
		this.openid = openid;
	}
	public String getOut_refund_no() {
		return out_refund_no;
	}
	public void setOut_refund_no(String out_refund_no) {
		this.out_refund_no = out_refund_no;
	}
	public int getRefund_fee() {
		return refund_fee;
	}
	public void setRefund_fee(int refund_fee) {
		this.refund_fee = refund_fee;
	}
	public Integer getWeixinId() {
		return weixinId;
	}
	public void setWeixinId(Integer weixinId) {
		this.weixinId = weixinId;
	}
	public Member getMember() {
		return member;
	}
	public void setMember(Member member) {
		this.member = member;
	}
	public String getPrepayid() {
		return prepayid;
	}
	public void setPrepayid(String prepayid) {
		this.prepayid = prepayid;
	}
	public String getOp_user_id() {
		return op_user_id;
	}
	public void setOp_user_id(String op_user_id) {
		this.op_user_id = op_user_id;
	}
	

}

前端获取ip:

<script src="http://pv.sohu.com/cityjson?ie=utf-8">
var ip = returnCitySN.cip;(有些手机不行,目前发现的是魅族和三星)判断正不正确可以查自己的ip地址,微信获取的是你所处网络的ip。也可以在java后台获取
</script>

前端页面代码:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
	<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<link rel="stylesheet" href="${basePath}/css/mui.min.css">
	<%@ include file="../common/base.jsp"%>
    <title>积分充值</title>
    <script src="${basePath}/js/mui.min.js"></script>
    <link rel="stylesheet" type="text/css" href="${basePath }/css/recharge-integral.css">
    

  </head>
  
  <body>
   <header>
			<i><a href="javascript:history.go(-1)"><img src="${basePath }/images/left-ico.png"/></a></i>
			<h1>积分</h1>
		</header>
		<div class="recharge-wrap" id="pay">
			<dl>
				<dt>您当前积分为 <span>${sessionScope.member.sumIntegral}分</span></dt>

				<dd class="buy">
					本次要购买积分数
					<i class="jia"></i>

					<input id="score" type="number" value="100" readonly="readonly">

					<i class="jian"></i>
				</dd>
				<dd class="tip"> 一元可购买100积分</dd>

				<dd class="clear">
					<span>支付方式</span>

					<ul>
						<li><i class="check-active"></i> 支付宝</li>
						<li><i class="weixinPay"></i>微信</li> 
					</ul>
				</dd>

			</dl>

			<button type="button" onclick="alipay()">确认购买</button>

		</div>
  </body>
  <script src="http://pv.sohu.com/cityjson?ie=utf-8"></script>
  <script>
var ip = returnCitySN.cip;
  var num = $("input[type=number]");
			
		  $(".weixinPay").on('click',function(){
          	mui.alert('您将要进入微信页面,若需要返回本系统,请点击浏览器的返回按钮!', '提示', function() {
				$.ajax({
					type : "post",
					url : "${basePath}/weChat/weChatUnifiedorder.do ",
					data : {
					"integer":numVal,
					"type":0,
					"memberId":${sessionScope.member.memberId},
					"ip":ip
					},
					datatype : "json",
					success : function(data){
						if(data.ok){
						window.location.href=data.url;
						}else{
						window.location.href="${basePath}/weChat/500.do ";
						}
					},error:function(){
						alert("后台错误,请联系管理员!");
					}
				});
			});
        });
	</script>
</html>

在h5支付时出现的一些问题
这个
这个页面就是你传递的ip和微信获取的发起支付的ip不一致,这个时候你就要检查获取ip的代码
这里写图片描述
出现这个情况的原因有两个1.你的scene_info中的wap_url与你配置的授权域名不一致,需要去商户平台的产品中心->开发配置中修改 2. 配置的redirect_url与授权域名不一致,需要修改。
这个地方还有其他的一些错误,我没遇到,以上两个错误我遇到很多次,后来查看官方文档才看见的(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4)。

猜你喜欢

转载自blog.csdn.net/github_38924695/article/details/78760164