1. 程式人生 > >微信支付之原路退款

微信支付之原路退款

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渠道獲取微信的appidappSecret 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)); }

簽名方法:

/**
 * @Descriptionsign簽名
* @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;需要原始碼的夥伴,請私信博主