微信支付之原路退款
阿新 • • 發佈:2019-01-10
1.場景還原
最近專案要求上線微信支付原路退款功能,今天筆者就微信支付原路退款的流程梳理下,方便有需要的夥伴借閱
2.準備工作
①獲取微信支付的相關配置
WECHATPAY_PARTNER = "150xxxxxxx"; //商戶號 WECHATPAY_PARTNERKEY = "Yunjunxxxxxxxxxxxxxyyyyyyyy"; //商戶祕鑰
②獲取微信支付API證書
微信支付後臺管理--》API安全--》下載證書
③閱讀微信官方申請退款文件,確保請求引數完整
④將證書.p12檔案放置工程的resources下
InputStream instream = PropertiesUtil.class.getResourceAsStream("/yiwei/apiclient_cert.p12")
3.實現方案
程式碼如下:
@Override public String doRefoundByWX(Map<String,Object> map) throws Exception { if (ObjectUtil.isNull(map.get("refund_money"),map.get("transaction_id"),map.get("channel"),map.get("sum_money"))) { throw new RequestException(); } String refund_money = map.get("refund_money").toString(); //退款金額 String out_trade_no = map.get("transaction_id").toString();//微信訂單號 String channel = map.get("channel").toString(); String sumMoney = map.get("sum_money").toString(); //訂單總價 String result = ""; //根據app渠道獲取微信的appid及appSecret WxpayUtil.loadWxAppIdAndSecret(Integer.valueOf(channel)); String mch_id = WxpayUtil.WECHATPAY_PARTNER; String appid = WxpayUtil.WECHATPAY_APPID; String partnerkey = WxpayUtil.WECHATPAY_PARTNERKEY; String nonce_str = WeiXinUtil.CreateNoncestr();//隨機字串 String out_refund_no = WeiXinUtil.generatePayNO();//商戶退款單號 Double total_fee = 0d; try { total_fee = StringUtil.getDouble(sumMoney); } catch (NumberFormatException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } //總金額以分為單位 long totalAmount = new BigDecimal(total_fee * 100d).longValue(); Double refund_fee = Double.parseDouble(refund_money); //退款金額以分為單位 long Amount = new BigDecimal(refund_fee * 100d).longValue(); //簽名演算法 SortedMap<Object, Object> SortedMap = new TreeMap<Object, Object>(); SortedMap.put("appid", appid); SortedMap.put("mch_id", mch_id); SortedMap.put("nonce_str", nonce_str); SortedMap.put("out_trade_no", out_trade_no); SortedMap.put("out_refund_no", out_refund_no); SortedMap.put("total_fee", String.valueOf(totalAmount)); SortedMap.put("refund_fee", String.valueOf(Amount)); String sign = WeiXinUtil.createSign("UTF-8",partnerkey,SortedMap); //獲取最終待發送的資料 String requestXml = "<xml>" + "<appid>" + appid + "</appid>" + "<mch_id>" + mch_id + "</mch_id>" + "<nonce_str>" + nonce_str + "</nonce_str>" + "<out_trade_no>" + out_trade_no + "</out_trade_no>" + "<out_refund_no>" + out_refund_no + "</out_refund_no>" + "<total_fee>" + String.valueOf(totalAmount) + "</total_fee>" + "<refund_fee>" + String.valueOf(Amount) + "</refund_fee>" + "<sign>" + sign + "</sign>" + "</xml>"; //建立連線併發送資料 HashMap<String, Object> resultMap = null; try { result = WeiXinUtil.WeixinSendPost(requestXml,mch_id,channel); //解析返回的xml resultMap = new HashMap<String, Object>(XMLParse.parseXmlToList2(result)); }catch (Exception e){ e.printStackTrace(); } //退款返回標誌碼 String return_code = resultMap.get("return_code").toString(); String result_code = resultMap.get("result_code").toString(); String msg = ""; if(return_code.equals("SUCCESS") && result_code.equals("SUCCESS")){ String userId = payorderMapper.getUserIdByOutTradeOrder(out_trade_no); //減少使用者餘額 Integer updateAmount = this.accountMapper.subtractAmount(userId, refund_money); // 生成我的錢包流水 Double remainAmount = this.accountMapper.findAmountByUserId(userId); AccountFlow flow = AccountFlow.newBuilder().setUserId(userId).setOrderCode(out_trade_no).setBody("微信原路退款扣除金額") .setIsInflow("0").setTotal_amount(refund_money).setRemainAmount(remainAmount.toString()).build(); Integer insertAccountFlow = this.accountFlowMapper.insert(flow.toMap()); msg = "微信原路返款成功!"; if(updateAmount != 1 || insertAccountFlow != 1) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return JsonUtil.toJson(new ResultBean(true, "微信原路返款失敗", null)); } }else if(return_code.equals("SUCCESS") && result_code.equals("FAIL")){ msg = "微信原路返款失敗!"; }else{ msg = "微信原路返款未知錯誤!"; } return JsonUtil.toJson(new ResultBean(true, msg, null)); }
簽名方法:
/** * @Description:sign簽名 * @param characterEncoding * 編碼格式 * @param parameters * @param secretKey * 請求引數 * @return */ public static String createSign(String characterEncoding,String secretKey, SortedMap<Object, Object> parameters) { StringBuffer sb = new StringBuffer(); Set<Map.Entry<Object, Object>> es = parameters.entrySet(); Iterator<Map.Entry<Object, Object>> it = es.iterator(); while (it.hasNext()) { Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) 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=" + secretKey); String sign = MD5Utils4WX.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); parameters.put("sign", sign); return sign; }
這裡的secretKey指的是微信商戶祕鑰
執行退款邏輯
public static String WeixinSendPost(Object xmlObj,String mch_id,String channel) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException { String result = ""; InputStream instream = PropertiesUtil.class.getResourceAsStream("/yiwei/apiclient_cert.p12"); KeyStore keyStore = KeyStore.getInstance("PKCS12"); try { keyStore.load(instream, mch_id.toCharArray()); } catch (CertificateException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } finally { instream.close(); } SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mch_id.toCharArray()).build(); 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"); @SuppressWarnings("deprecation") HttpEntity xmlData = new StringEntity((String) xmlObj, "text/xml", "iso-8859-1"); httpPost.setEntity(xmlData); System.out.println("executing request" + httpPost.getRequestLine()); CloseableHttpResponse response = httpclient.execute(httpPost); try { HttpEntity entity = response.getEntity(); result = EntityUtils.toString(entity, "UTF-8"); System.out.println(response.getStatusLine()); EntityUtils.consume(entity); } finally { response.close(); } } finally { httpclient.close(); } //去除空格 return result.replaceAll(" ", ""); }
執行完之後再進行是否成功判斷
result = WeiXinUtil.WeixinSendPost(requestXml,mch_id,channel); //解析返回的xml resultMap = new HashMap<String, Object>(XMLParse.parseXmlToList2(result));
將xml字串解析map方法
public static Map parseXmlToList2(String xml) { Map retMap = new HashMap(); try { StringReader read = new StringReader(xml); // 建立新的輸入源SAX 解析器將使用 InputSource 物件來確定如何讀取 XML 輸入 InputSource source = new InputSource(read); // 建立一個新的SAXBuilder SAXBuilder sb = new SAXBuilder(); // 通過輸入源構造一個Document Document doc = sb.build(source); Element root = (Element) doc.getRootElement();// 指向根節點 List<Element> es = root.getChildren(); if (es != null && es.size() != 0) { for (Element element : es) { retMap.put(element.getName(), element.getValue()); } } } catch (Exception e) { e.printStackTrace(); } return retMap; }
好了,我是張星,歡迎加入博主技術交流群,群號:313145288;需要原始碼的夥伴,請私信博主