公众号支付是手机端的微信公众号H5页面支付,这种支付方式必须是在微信内置浏览器发起。
扫码支付分为模式一和模式二,模式一主要为线下服务,该模式是先扫码,再生成订单,商户先为自己的商品生成二维码连接,然后用户扫码之后决定是否购买,二维码无过期时间,比如自动售卖机大多采用这种模式;模式二主要为线上电商服务,用户选择商品后生成订单,根据订单生成二维码,然后支付,该二维码为临时二维码。
开发流程
一、授权目录
官方文档说必须是精确目录,其实是二级或三级目录就可以了,太精确的可能还会出现不识别的情况。如果是扫码支付模式一还需要设置扫码支付回调URL
二.统一下单
注意传入参数不要为null,尽量不要是空字符串,如果在没有抛出Exception的情况下支付失败,十有八九是参数导致的签名有问题,微信支付的签名规定的特别严格,必须按照微信给的规则来,建议第一次先用demo提供的签名方法,以后可以再修改。这里的appid就是支付所在的公众号的appid,openId是用户对应当前公众号的openId。
1、扫码支付模式二
扫码支付比较简单,可以直接通过url发起,传入统一下单参数,生成扫码支付的url。扫码支付的trade_type为NATIVE。
public void getCodeUrl(@PathVariable(value="tradeNo")String tradeNo, HttpServletRequest request, HttpServletResponse response) throws Exception { //根据订单号获取订单详情 OrderProduceBean order = reservationCarService.getTradebyNo(tradeNo); // 附加数据 原样返回 String attach = "attach"; // 总金额以分为单位,不带小数点 String totalFee = TenpayUtil.getMoney(order.getTotalFee().toString()); // 订单生成的机器 IP String spbill_create_ip = IpUtil.getIpAddr(request); // 这里notify_url是 支付完成后微信发给该链接信息,可以判断会员是否支付成功,改变订单状态等。 String notify_url = TenPayConfig.notifyUrl; String trade_type = "NATIVE"; // 商户号 String mch_id = TenPayConfig.partner; // 随机字符串 String nonce_str = TenpayUtil.getNonceStr(); // 商品描述根据情况修改 String body = order.getBody(); // 商户订单号 String out_trade_no = order.getOutTradeNo(); SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid", TenPayConfig.appid); packageParams.put("mch_id", mch_id); packageParams.put("nonce_str", nonce_str); packageParams.put("body", body); packageParams.put("attach", attach); packageParams.put("out_trade_no", out_trade_no); packageParams.put("total_fee", totalFee); packageParams.put("spbill_create_ip", spbill_create_ip); packageParams.put("notify_url", notify_url); packageParams.put("trade_type", trade_type); RequestHandler reqHandler = new RequestHandler(null, null); reqHandler.init(TenPayConfig.appid, TenPayConfig.appsecret, TenPayConfig.partnerkey); String sign = reqHandler.createSign(packageParams); String xml = "<xml>" + "<appid>" + TenPayConfig.appid + "</appid>" + "<mch_id>" + mch_id + "</mch_id>" + "<nonce_str>" + nonce_str + "</nonce_str>" + "<sign>" + sign + "</sign>" + "<body><![CDATA[" + body + "]]></body>" + "<out_trade_no>" + out_trade_no + "</out_trade_no>" + "<attach>" + attach + "</attach>" + "<total_fee>" + totalFee + "</total_fee>" + "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>" + "<notify_url>" + notify_url + "</notify_url>" + "<trade_type>" + trade_type + "</trade_type>" + "</xml>"; System.out.println(xml); String code_url = ""; String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; code_url = new TenPayCore().getCodeUrl(createOrderURL, xml);//调用统一下单接口 if (code_url == null || code_url.equalsIgnoreCase("")) { logger.debug("****************************trade has closed or no this trade in tencentPay"); response.sendError(404); return; } GenerateQrCodeUtil.encodeQrcode(code_url, response); }
2.公众号支付
公众号支付首先通过H5调起支付api,微信生成订单,然后开发者获取预支付id,最后由用户确认支付。
①H5调起JSAPI。
调起有两种方式,一种是WeixinJSBridge.invoke(),另一种是最新的微信JSAPI,两种方式均可,官方文档用的第一种,使用第一种方式首先要引入http://res.wx.qq.com/open/js/jweixin-1.0.0.js。
function toPay(){ $.ajax({ url : URL, type : "GET", dataType : 'json', success : function(data) { WeixinJSBridge.invoke('getBrandWCPayRequest', { "appId" : data.appId, //公众号名称,由商户传入 "timeStamp" : data.timeStamp, //时间戳,自1970年以来的秒数 "nonceStr" : data.nonceStr, //随机串 "package" : data.package, "signType" : data.signType, //微信签名方式: "paySign" : data.paySign //微信签名 }, function(res) { if (res.err_msg == "get_brand_wcpay_request:ok") { } // 使用以上方式判断前端返回:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 else{ //res.err_msg; } }); } }); }
②获取预支付id
这个api和扫码支付的api差不多,就是trade为JSAPI不一样。
public void TencentPayController(@RequestParam("orderno")String orderno,HttpServletRequest request, HttpServletResponse response) throws IOException { //根据订单号查询订单 OrderProduceBean order=reservationCarService.getTradebyNo(orderno); String appid = TenPayConfig.appid; String openId =order.getOpenId(); // 订单号 String orderId = order.getOutTradeNo(); // 附加数据 原样返回 String attach = "attach"; // 总金额以分为单位,不带小数点 String totalFee = TenpayUtil.getMoney(order.getTotalFee().toString()); // 订单生成的机器 IP String spbill_create_ip = IpUtil.getIpAddr(request);; // 这里notify_url是 支付完成后微信发给该链接信息,可以判断会员是否支付成功,改变订单状态等。 String notify_url = TenPayConfig.notifyUrl; String trade_type = "JSAPI"; // ---必须参数 // 商户号 String mch_id = TenPayConfig.partner; // 随机字符串 String nonce_str = TenpayUtil.getNonceStr(); // 商品描述根据情况修改 String body = order.getBody(); // 商户订单号 String out_trade_no = orderId; SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid", appid); packageParams.put("mch_id", mch_id); packageParams.put("nonce_str", nonce_str); packageParams.put("body", body); packageParams.put("attach", attach); packageParams.put("out_trade_no", out_trade_no); packageParams.put("total_fee", totalFee); packageParams.put("spbill_create_ip", spbill_create_ip); packageParams.put("notify_url", notify_url); packageParams.put("trade_type", trade_type); packageParams.put("openid", openId); RequestHandler reqHandler = new RequestHandler(null, null); reqHandler.init(appid, TenPayConfig.appsecret,TenPayConfig.partnerkey); String sign = reqHandler.createSign(packageParams); String xml = "<xml>" + "<appid>" + appid + "</appid>" + "<mch_id>" + mch_id + "</mch_id>" + "<nonce_str>" + nonce_str + "</nonce_str>" + "<sign>" + sign + "</sign>" + "<body><![CDATA[" + body + "]]></body>" + "<out_trade_no>" + out_trade_no + "</out_trade_no>" + "<attach>" + attach + "</attach>" + "<total_fee>" + totalFee + "</total_fee>" + "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>" + "<notify_url>" + notify_url + "</notify_url>" + "<trade_type>" + trade_type + "</trade_type>" + "<openid>" + openId + "</openid>" + "</xml>"; String prepay_id = ""; String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; prepay_id = new TenPayCore().getPayNo(createOrderURL, xml); logger.debug("********************************************"); logger.debug("prepay_id :" + prepay_id); //获取prepay_id后,拼接最后请求支付所需要的package SortedMap<String, String> finalpackage = new TreeMap<String, String>(); String timestamp = Sha1Util.getTimeStamp(); String packages = "prepay_id="+prepay_id; finalpackage.put("appId", appid); finalpackage.put("timeStamp", timestamp); finalpackage.put("nonceStr", nonce_str); finalpackage.put("package", packages); finalpackage.put("signType", "MD5"); //要签名 String finalsign = reqHandler.createSign(finalpackage); finalpackage.put("paySign", finalsign); String finaPackage = "\"appId\":\"" + appid + "\",\"timeStamp\":\"" + timestamp + "\",\"nonceStr\":\"" + nonce_str + "\",\"package\":\"" + packages + "\",\"signType\" : \"MD5" + "\",\"paySign\":\"" + finalsign + "\""; logger.debug("********************************************"); logger.debug("V3 jsApi package:" + finaPackage); if(prepay_id.length()>0){ this.callBack(response, request,this.gson.toJson(finalpackage)); return; }else{ this.callBack(response, request,"{\"error\":\"prepay_id is null\"}"); return; } }
3.支付回调
该回调方法的路径就是获取预支付id中的参数notify_url,用来处理支付完成后的业务逻辑,注意一定要检查return_code和result_code是否都为success,另外一定要给微信返回success信息.
public void notifyPay(HttpServletRequest request, HttpServletResponse response) throws IOException { // 示例报文 String inputLine; String notityXml = ""; String resXml = ""; try { while ((inputLine = request.getReader().readLine()) != null) { notityXml += inputLine; } request.getReader().close(); } catch (Exception e) { e.printStackTrace(); } Map<String, String> m = TenpayUtil.parseXmlToList(notityXml); OrderTenPayBean orderTen = new OrderTenPayBean(); orderTen.setAppid(m.get("appid").toString()); orderTen.setBankType(m.get("bank_type").toString()); orderTen.setCashFee(m.get("cash_fee").toString()); orderTen.setFeeType(m.get("fee_type").toString()); orderTen.setIsSubscribe(m.get("is_subscribe").toString()); orderTen.setMchId(m.get("mch_id").toString()); orderTen.setNonceStr(m.get("nonce_str").toString()); orderTen.setOpenid(m.get("openid").toString()); orderTen.setOutTradeNo(m.get("out_trade_no").toString()); orderTen.setResultCode(m.get("result_code").toString()); orderTen.setReturnCode(m.get("return_code").toString()); orderTen.setSign(m.get("sign").toString()); orderTen.setTimeEnd(m.get("time_end").toString()); orderTen.setTotalFee(m.get("total_fee").toString()); orderTen.setTradeType(m.get("trade_type").toString()); orderTen.setTransactionId(m.get("transaction_id").toString()); if ("SUCCESS".equals(orderTen.getResultCode()) && "SUCCESS".equals(orderTen.getReturnCode())<span style="font-family: Arial, Helvetica, sans-serif;">) { //必须判断</span> //TODO 信息入库,修改订单状态 // 支付成功,必须给微信返回此信息,否则微信会一直调用此回调方法 resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } else { resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> "; } this.callBack(response, request, resXml); }
4.退款
退款需要把商户证书apiclient_cert.p12放到指定目录下,因为有证书的情况下退款不需要密码,一定要注意业务请求的权限问题
public void refusePay(HttpServletResponse response,HttpServletRequest request,@PathVariable(value="outTradeNo")String outTradeNo) throws Exception { OrderProduceBean tradebyNo = reservationCarService.getTradebyNo(outTradeNo); SortedMap<String, String> parameters = new TreeMap<String, String>(); parameters.put("appid", TenPayConfig.appid); parameters.put("mch_id", TenPayConfig.partner); parameters.put("nonce_str", TenpayUtil.getNonceStr()); parameters.put("out_trade_no", tradebyNo.getOutTradeNo()); parameters.put("out_refund_no", tradebyNo.getOutTradeNo()); // 我们自己设定的退款申请号,约束为UK parameters.put("total_fee", TenpayUtil.getMoney(tradebyNo.getTotalFee().toString())); // 单位为分 parameters.put("refund_fee", TenpayUtil.getMoney(tradebyNo.getTotalFee().toString())); // 单位为分 parameters.put("op_user_id", TenPayConfig.partner); RequestHandler reqHandler = new RequestHandler(null, null); reqHandler.init(TenPayConfig.appid, TenPayConfig.appsecret, TenPayConfig.partnerkey); String sign = reqHandler.createSign(parameters); parameters.put("sign", sign); String reuqestXml = TenpayUtil.getRequestXml(parameters); KeyStore keyStore = KeyStore.getInstance("PKCS12"); FileInputStream instream = new FileInputStream(new File(Resources.getString("cert_url"))); try { keyStore.load(instream, TenPayConfig.partner.toCharArray()); } finally { instream.close(); } // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore, TenPayConfig.partner.toCharArray()) .build(); // Allow TLSv1 protocol only SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); CloseableHttpClient httpclient = HttpClients.custom() .setSSLSocketFactory(sslsf) .build(); try { HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund"); System.out.println("executing request" + httpPost.getRequestLine()); StringEntity reqEntity = new StringEntity(reuqestXml); // 设置类型 reqEntity.setContentType("application/x-www-form-urlencoded"); httpPost.setEntity(reqEntity); CloseableHttpResponse reqs = httpclient.execute(httpPost); try { HttpEntity entity = reqs.getEntity(); if (entity != null) { System.out.println("Response content length: " + entity.getContentLength()); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent())); String returnXml = ""; String text; while ((text = bufferedReader.readLine()) != null) { returnXml += text; } //微信退款返回的参数转换为Map Map<String, String> resultMaP = TenpayUtil.parseXmlToList(returnXml); this.callBack(response, request, resultMaP.toString()); //TODO 信息入库 } EntityUtils.consume(entity); } finally { reqs.close(); } } finally { httpclient.close(); } }
PS:代码仅供参考!