微信线下门店二维码扫码支付和退款


查看微信官网开发接口

微信线下门店扫码支付开发

流程:生成一个预付单-》生成二维码》付款交易成功(微信端接收到钱已付款,这时需要告诉商户系统我已收到钱,发送异步通知给商户系统,一般会发送多次有时间间隔。

商户系统的异步通知方法:处理自己系统的业务,一般修改交易流水状态,发送微信客服消息等等。

最后发送个“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();
				jQuery('#outputWXQr').qrcode({width:200,height:200,text:result.code_url});
				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 parameters = new TreeMap();
		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 map = XMLUtil.doXMLParse(result);
		
		SortedMap params = new TreeMap();
		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 tktIds = new ArrayList();
		//tktIds.add(ticketId);
		//orderService.payNotify(orderId,tktIds,orderAmount);
		String result = new String(outSteam.toByteArray(), "utf-8");// 获取微信调用我们notify_url的返回信息
		Map 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 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 ops = new ArrayList();
			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;
	}


XMLUtil.java
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("");
			}
		}
		
		return sb.toString();
	}
	
}

4.微信退款:首先需要有微信退款的证书,放到商户系统的目录下,然后调用微信退款接口

/**
	 * 微信--申请退款
	 * 
	 * @param orderId
	 *            订单编号
	 * @param refundMoney
	 *            退款金额
	 * @param totalFee
	 *            订单总金额
	 * @param outRefundNo
	 *            商户退款单号
	 * @return
	 */
	public Map payRefund(String orderId, float refundMoney,
			float totalFee, String outRefundNo) {
		Map resultMap = new HashMap();
		try {
			Order order = orderService.getOrderById(Integer.parseInt(orderId));
			CompanyPay cp = orderService.getCompanyPay(order.getCompanyId());
			String tradeNo = orderRefundService
					.selectOutTradeNoByOrderId(Integer.parseInt(orderId));
			SortedMap parameters = new TreeMap();
			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 map = clientCustomSSLCall(
					ConfigUtil.REFUND_URL, order.getCompanyId(), cp.getMchId(),
					requestXML);
			// log.debug("result:=" + result);
			// Map 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 clientCustomSSLCall(String url,
			String companyId, String mchId, String arrayToXml) throws Exception {
		Map doXMLtoMap = new HashMap();
		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 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 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 parameters) throws UnsupportedEncodingException{
		StringBuffer sb = new StringBuffer();
		sb.append("");
		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+">"+"");
			}else {
				sb.append("<"+k+">"+v+"");
			}
		}
		sb.append("");
		return sb.toString();
	}
	/**
	 * @Description:返回给微信的参数
	 * @param return_code 返回编码
	 * @param return_msg  返回信息
	 * @return
	 */
	public static String setXML(String return_code, String return_msg) {
		return "";
	}
}



你可能感兴趣的:(web开发)