查看微信官网开发接口
微信线下门店扫码支付开发
流程:生成一个预付单-》生成二维码》付款交易成功(微信端接收到钱已付款,这时需要告诉商户系统我已收到钱,发送异步通知给商户系统,一般会发送多次有时间间隔。
商户系统的异步通知方法:处理自己系统的业务,一般修改交易流水状态,发送微信客服消息等等。
最后发送个“SUCCESS”内容的xml给微信端,这时微信端就不会再异步请求了。
1.生成预付订单
2.js生成二维码
3.回调通知方法
4.微信退款
1.jsp页面的生成二维码
//*************start*******wxPayByQr******************************** function wxQrClick(){ var total_amount=$("#paymentAmount").val(); var orderId=$("#myorderId").val(); //check var paySize=$(".payMoneyC").length; var ptypeNum=$(".ptype:checked").length; var paySum=0; if(ptypeNum<=0){ layer.msg("支付方式至少选择一个!"); return; } if(ptypeNum!=1){ layer.msg("支付方式只能是微信扫码支付!"); return; } var companyId=$("#companyId").val(); var subCompanyId=$("#subCompanyId").val(); if(subCompanyId==null||subCompanyId=='null'||subCompanyId==undefined){ subCompanyId=""; } $.ajax({ url : '<%=basePath%>/payPrepareByQr.action', async : false, type : "post", dataType : "json", data:{"orderAmount":total_amount,"orderId":orderId}, success : function(result) { if(result.code=='SUCCESS'){ $("#wxQrBtn").hide(); $("#outputWXQr").show(); <span style="color:#ff0000;">jQuery('#outputWXQr').qrcode({width:200,height:200,text:result.code_url});</span> window.setInterval(finshWXQrPay, 8000); }else{ layer.msg("微信二维码生成出错!"); } } }); } function finshWXQrPay(){ var orderId=$("#myorderId").val(); $.ajax({ url : '<%=basePath%>/aliPay!notifyFinshedByWXQr.action?orderId='+orderId, async : false, type : "post", dataType : "json", data:$('#finishForm').serialize(), success : function(result) { if(result.code!='0'){ layer.msg("微信扫码支付交易成功,订单3秒后即将关闭!"); window.setTimeout(function(){ //关闭弹出窗之前,跳转到其他页面 parent.window.location.href="<%=path%>/"+result.redirectUrl; closeLayerDialog(); },3000); } } }); } //*************end*******wxPayByQr********************************
2.二维码调用的方法
/** * 微信二维码扫码支付生成预支付交易单,并返回交易会话的二维码链接code_url * @return * @throws JDOMException * @throws IOException * @throws NumberFormatException * @throws SQLException */ public String payPrepareByQr() throws JDOMException, IOException, NumberFormatException, SQLException { Order order = orderService.getOrderById(Integer.parseInt(orderId)); CompanyPay cp = orderService.getCompanyPay(order.getCompanyId()); HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); PrintWriter out = null; if(order.getDealSts()==5||order.getDealSts()==7){ out.print("0"); return null; } SortedMap<Object, Object> parameters = new TreeMap<Object, Object>(); userwxId = (userwxId==null||"".equals(userwxId))?order.getOrderPersonWXId():userwxId; if (!StringUtils.isNotBlank(userwxId)) { userwxId=""; } if (!StringUtils.isNotBlank(ticketId)) { ticketId=""; } parameters.put("appid", cp.getAppId()); parameters.put("mch_id", cp.getMchId()); parameters.put("nonce_str", PayCommonUtil.CreateNoncestr());// 随机字符串,不长于32位 parameters.put("body", order.getIsTakeOut()==0?"堂食订单":"外卖订单");//商品描述 String tradeNo = getTradeNo(); parameters.put("out_trade_no", tradeNo);//商户系统内部的订单号,32个字符内、可包含字母 float amount_f = Float.parseFloat(orderAmount); Long amount = (long)(amount_f*100); parameters.put("total_fee", amount.toString());//订单总金额,单位为分,不能带小数点 parameters.put("spbill_create_ip", request.getRemoteAddr());//订单生成的机器IP,第一个参数订单编号,第二个参数交易金额,第四个参数优惠劵编号 parameters.put("notify_url", ConfigUtil.WXQR_NOTIFY_URL+"?orderId="+orderId+","+orderAmount+","+tradeNo+","+ticketId);//接收微信支付成功通知 parameters.put("trade_type", "NATIVE");//JSAPI、NATIVE、APP String sign = PayCommonUtil.createSign("UTF-8", parameters,cp.getApiKey()); parameters.put("sign", sign); String requestXML = PayCommonUtil.getRequestXml(parameters); System.out.println("requestXML_------------------->>>>>:"+requestXML); String result = CommonUtil.httpsRequest(ConfigUtil.UNIFIED_ORDER_URL,"POST", requestXML); Map<String, String> map = XMLUtil.doXMLParse(result); SortedMap<Object, Object> params = new TreeMap<Object, Object>(); String return_code=(String)map.get("return_code"); String result_code=(String)map.get("result_code"); String json = ""; if("SUCCESS".equals(return_code)&&"SUCCESS".equals(result_code)){ params.put("code", map.get("return_code")); params.put("appId", cp.getAppId()); params.put("prepay_id", map.get("prepay_id")); params.put("trade_type", map.get("trade_type")); params.put("code_url", map.get("code_url"));//trade_type为NATIVE是有返回,可将该参数值生成二维码展示出来进行扫码支付 json = JSONObject.fromObject(params).toString(); //生成交易流水,等回调后再改变状态 WXPayLog vo = new WXPayLog(); vo.setOutTradeNo(tradeNo); vo.setOrderId(orderId); vo.setTotalFee(amount_f); vo.setPayOpenId(order.getOrderPersonWXId()); vo.setAppId(parameters.get("appid").toString()); vo.setStatus(0); vo.setTradeDate(new Date()); vo.setTradeType("NATIVE"); orderService.insertWXPaylog(vo); }else{ params.put("code", map.get("return_code")); params.put("msg", map.get("return_msg")); json = JSONObject.fromObject(params).toString(); } System.out.println("微信扫码支付返回信息:="+json); ResponseWriteUtil.writeHTML(json); return null; }
3.回调的方法
/** * 微信扫码支付异步通知回调方法 * @return * @throws Exception */ public String paySuccessByWXQr() throws Exception { HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } outSteam.close(); inStream.close(); System.out.println("~~~~~~~~~~~~~~~~付款成功~~~~~~~~~~~~~~~~~~`"); List<String> tktIds = new ArrayList<String>(); //tktIds.add(ticketId); //orderService.payNotify(orderId,tktIds,orderAmount); String result = new String(outSteam.toByteArray(), "utf-8");// 获取微信调用我们notify_url的返回信息 Map<Object, Object> map = XMLUtil.doXMLParse(result); String paras = ""; String userwxId=""; if (map != null) { for (Object keyValue : map.keySet()) { // System.out.println("[" + keyValue + "=" + map.get(keyValue) // + "]"); if ("orderId".equals(keyValue)) { paras = map.get(keyValue).toString(); } if("openid".equals(keyValue)){ userwxId=map.get(keyValue).toString(); } } } String[] para = paras.split(","); String orderId=para[0]; String orderAmount=para[1]; String tradeNo=para[2]; String ticketId=""; if(para.length>3){ ticketId=para[3]; if(ticketId!=null && !"".equals(ticketId)){ tktIds.add(ticketId); } } System.out.println("params="+orderId+","+orderAmount+","+tradeNo+","+userwxId +","+ticketId); //判断付款是否成功,已成功则不再记录付款信息 if(orderService.ifOrderPaid(Integer.parseInt(orderId))>0){ response.getWriter().write(PayCommonUtil.setXML("SUCCESS", "")); return null; } //更新交易流水状态 orderService.updateWXPayLogStatus(tradeNo); // 增加付款信息 Order o = orderService.getOrderById(Integer.parseInt(orderId)); List<SysUser> us = sysUserService.fetchSysUserByOpenId(userwxId, o.getCompanyId()); SysUser u = (us!=null && us.size()>0)?us.get(0):null; if(o.getIsTakeOut()==0){//堂吃 orderService.payNotify(orderId,tktIds,orderAmount,0); }else if(o.getIsTakeOut()==1){//外卖 o.setPaymentAmount(Float.parseFloat(orderAmount)); o.setPaymentType(EnumUtil.PAYMENT_TYPE.weixin.getCode());//配送员结束订单,赞为支付方式为现金或刷卡两种方式 o.setPaymentTime(new Date()); o.setDealSts(EnumUtil.ORDER_dealSts.paid.getCode()); o.setFinishTime(new Date()); o.setOrderRemark("用户微信支付"); List<OrderPlus> ops = new ArrayList<OrderPlus>(); OrderPlus op = new OrderPlus(); op.setOrderId(o.getId()); op.setOrderAmount(o.getAmount()); op.setPaymentAmount(o.getPaymentAmount()); //op.setPaymentTime(new Date()); op.setActUser(u==null?"":u.getId()); op.setPaymentType(EnumUtil.PAYMENT_TYPE.weixin.getCode()); op.setNotes("用户微信支付:支付金额为"+o.getPaymentAmount()); ops.add(op); orderService.taeoutOrderPaymet4admin(o, ops,tktIds); } orderService.updateOrderItemActualPrice(o.getId()); System.out.println("~~~~~~~~~~~~~~~~业务处理完成~~~~~~~~~~~~~~~~~~`"); //--------------------------消息发送成功-----------------------------------------end if (map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) { response.getWriter().write(PayCommonUtil.setXML("SUCCESS", "")); // 告诉微信服务器,我收到信息了,不要在调用回调action了 System.out.println("-------------"+ PayCommonUtil.setXML("SUCCESS", "")); } return null; }
import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; public class XMLUtil { /** * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 * @param strxml * @return * @throws JDOMException * @throws IOException */ public static Map doXMLParse(String strxml) throws JDOMException, IOException { strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\""); if(null == strxml || "".equals(strxml)) { return null; } Map m = new HashMap(); InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8")); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if(children.isEmpty()) { v = e.getTextNormalize(); } else { v = XMLUtil.getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } /** * 获取子结点的xml * @param children * @return String */ public static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if(!children.isEmpty()) { Iterator it = children.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if(!list.isEmpty()) { sb.append(XMLUtil.getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } }
4.微信退款:首先需要有微信退款的证书,放到商户系统的目录下,然后调用微信退款接口
/** * 微信--申请退款 * * @param orderId * 订单编号 * @param refundMoney * 退款金额 * @param totalFee * 订单总金额 * @param outRefundNo * 商户退款单号 * @return */ public Map<String, Object> payRefund(String orderId, float refundMoney, float totalFee, String outRefundNo) { Map<String, Object> resultMap = new HashMap<String, Object>(); try { Order order = orderService.getOrderById(Integer.parseInt(orderId)); CompanyPay cp = orderService.getCompanyPay(order.getCompanyId()); String tradeNo = orderRefundService .selectOutTradeNoByOrderId(Integer.parseInt(orderId)); SortedMap<Object, Object> parameters = new TreeMap<Object, Object>(); parameters.put("appid", cp.getAppId());// 微信分配的公众账号ID parameters.put("mch_id", cp.getMchId());// 微信支付分配的商户号 parameters.put("nonce_str", PayCommonUtil.CreateNoncestr());// 随机字符串,不长于32位 // CHSGOFBZJ520150918162021614 parameters.put("out_trade_no", tradeNo);// 商户系统内部的订单号,32个字符内、可包含字母 parameters.put("out_refund_no", outRefundNo);// 商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔 Long refundMoney_f = (long) (refundMoney * 100); Long totalFee_f = (long) (totalFee * 100); parameters.put("total_fee", totalFee_f.toString());// 订单总金额,单位为分,不能带小数点 parameters.put("refund_fee", refundMoney_f.toString());// 退款总金额,单位为分,不能带小数点 parameters.put("op_user_id", cp.getMchId());// 操作员帐号, 默认为商户号 String sign = PayCommonUtil.createSign("UTF-8", parameters, cp.getApiKey()); parameters.put("sign", sign); String requestXML = PayCommonUtil.getRequestXml(parameters); System.out.println("requestXML_------------------->>>>>:" + requestXML); // String result = CommonUtil.httpsRequest(ConfigUtil.REFUND_URL, // "POST", requestXML); Map<String, String> map = clientCustomSSLCall( ConfigUtil.REFUND_URL, order.getCompanyId(), cp.getMchId(), requestXML); // log.debug("result:=" + result); // Map<String, String> map = XMLUtil.doXMLParse(result); if ("FAIL".equals(map.get("return_code"))) { resultMap.put("result_flag", "fail"); resultMap.put("return_code", map.get("return_code")); resultMap.put("return_msg", map.get("return_msg")); } else if ("SUCCESS".equals(map.get("return_code"))) { if ("SUCCESS".equals(map.get("result_code"))) { resultMap.put("result_flag", "success");// SUCCESS退款申请接收成功,结果通过退款查询接口查询 resultMap.put("return_code", map.get("return_code")); resultMap.put("return_msg", map.get("return_msg")); resultMap.put("err_code", map.get("err_code")); resultMap.put("err_code_des", map.get("err_code_des")); } else if ("FAIL".equals(map.get("result_code"))) { resultMap.put("result_flag", "fail");// FAIL 提交业务失败 resultMap.put("err_code", map.get("err_code")); resultMap.put("err_code_des", map.get("err_code_des")); } } } catch (Exception e) { System.out.println("payRefund Exception:" + e.getMessage()); } return resultMap; } /** * 自定义SSL双向证书验证 * * @param url * @param mchId * @param arrayToXml * @return * @throws Exception */ public Map<String, String> clientCustomSSLCall(String url, String companyId, String mchId, String arrayToXml) throws Exception { Map<String, String> doXMLtoMap = new HashMap<String, String>(); KeyStore keyStore = KeyStore.getInstance("PKCS12"); String cAPath = ServletActionContext.getServletContext().getRealPath( "/WEB-INF/ca/" + companyId + "/apiclient_cert.p12"); // System.out.println("capath:=" + cAPath); FileInputStream instream = new FileInputStream(new File(cAPath)); try { keyStore.load(instream, mchId.toCharArray()); } finally { instream.close(); } // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore, mchId.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(url); httpPost.setEntity(new StringEntity(arrayToXml, "UTF-8")); CloseableHttpResponse response = httpclient.execute(httpPost); String jsonStr = EntityUtils .toString(response.getEntity(), "UTF-8"); doXMLtoMap = XMLUtil.doXMLParse(jsonStr); log.debug("result jsonStr:=" + jsonStr); response.close(); } finally { httpclient.close(); } return doXMLtoMap; } /** * 微信退款证书是否存在 * * @return */ public String isWxCAExist() { String companyId = getCompanyInfo().getCompanyId(); String cAPath = ServletActionContext.getServletContext().getRealPath( "/WEB-INF/ca/" + companyId + "/apiclient_cert.p12"); File f = new File(cAPath); if (f.exists()) { ResponseWriteUtil.writeHTML("{\"isExist\":\"1\"}");// 存在 } else { ResponseWriteUtil.writeHTML("{\"isExist\":\"0\"}");// 不存在 } return null; }
PayCommonUtil.java
import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.SortedMap; import org.apache.log4j.Logger; public class PayCommonUtil { private static Logger log = Logger.getLogger(PayCommonUtil.class); public static String CreateNoncestr(int length) { String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; String res = ""; for (int i = 0; i < length; i++) { Random rd = new Random(); res += chars.indexOf(rd.nextInt(chars.length() - 1)); } return res; } public static String CreateNoncestr() { String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; String res = ""; for (int i = 0; i < 16; i++) { Random rd = new Random(); res += chars.charAt(rd.nextInt(chars.length() - 1)); } return res; } /** * @Description:sign签名 * @param characterEncoding 编码格式 * @param parameters 请求参数 * @return * @throws UnsupportedEncodingException */ public static String createSign(String characterEncoding,SortedMap<Object,Object> parameters,String apiKey) throws UnsupportedEncodingException{ StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet(); 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=" + ConfigUtil.API_KEY); sb.append("key=" + apiKey); System.out.println("createSign-----befor_md5_sign:"+sb.toString()); String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; } public static String createSign4pay(String characterEncoding,SortedMap<Object,Object> parameters,String apiKey) throws UnsupportedEncodingException{ StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet(); 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=" + apiKey); //sb.append("key=" + ConfigUtil.APP_SECRECT); System.out.println("befor_md5_sign:"+sb.toString()); String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; } /** * @Description:将请求参数转换为xml格式的string * @param parameters 请求参数 * @return * @throws UnsupportedEncodingException */ public static String getRequestXml(SortedMap<Object,Object> parameters) throws UnsupportedEncodingException{ StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set es = parameters.entrySet(); Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String k = (String)entry.getKey(); String v = (String)entry.getValue(); if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) { sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">"); }else { sb.append("<"+k+">"+v+"</"+k+">"); } } sb.append("</xml>"); return sb.toString(); } /** * @Description:返回给微信的参数 * @param return_code 返回编码 * @param return_msg 返回信息 * @return */ public static String setXML(String return_code, String return_msg) { return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>"; } }