1. 程式人生 > >微信線下門店二維碼掃碼支付和退款

微信線下門店二維碼掃碼支付和退款

檢視微信官網開發介面

微信線下門店掃碼支付開發

流程:生成一個預付單-》生成二維碼》付款交易成功(微信端接收到錢已付款,這時需要告訴商戶系統我已收到錢,傳送非同步通知給商戶系統,一般會發送多次有時間間隔。

商戶系統的非同步通知方法:處理自己系統的業務,一般修改交易流水狀態,傳送微信客服訊息等等。

最後傳送個“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;
	}
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("</" + 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>";
	}
}