微信線下門店二維碼掃碼支付和退款
阿新 • • 發佈:2019-01-10
檢視微信官網開發介面
微信線下門店掃碼支付開發
流程:生成一個預付單-》生成二維碼》付款交易成功(微信端接收到錢已付款,這時需要告訴商戶系統我已收到錢,傳送非同步通知給商戶系統,一般會發送多次有時間間隔。
商戶系統的非同步通知方法:處理自己系統的業務,一般修改交易流水狀態,傳送微信客服訊息等等。
最後傳送個“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.回撥的方法
XMLUtil.java/** * 微信掃碼支付非同步通知回撥方法 * @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>";
}
}