微信小程序-微信自動退款(Java後臺) 微信小程序-微信自動退款

微信小程序-微信自動退款

1、首先分享

      微信自動退款接口: 

        https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4

      微信付款 代碼案例 (很多共同的代碼 都在付款邏輯裏面)

        https://www.cnblogs.com/yi1036943655/p/7211275.html

2、小程序端代碼 栗子

    //獲取openId
    wx.request({
      url: 'http://192.168.1.183:8081/order/refund',
      data: {
        'amount':1,
        'incrementId': outTradeNo,
        'orderId': orderId,
        'productId': productId,
        'amount': amount,
        'sku': sku,
        'name': name

      },
      method: 'POST',
      header: { 'content-type': 'application/x-www-form-urlencoded' },
      success: function (result) {
        
      }

2、接口端代碼 栗子

    @Transactional(rollbackFor=MyException.class)
    @Override
    public JSONObject refundOrder(HttpServletRequest request) {
        //設置最終返回對象
        JSONObject resultJson = new JSONObject();
        //接受參數(金額)
        String amount = request.getParameter("amount");
        //接受參數(訂單Id)
        String orderId = request.getParameter("orderId");
        //接受參數(商品ID)
        String productId = request.getParameter("productId");
        //接受參數(商品sku)
        String sku = request.getParameter("sku");
        //接受參數(商品name)
        String name = request.getParameter("name");
        //接受參數(商品訂單號)
        String incrementId = request.getParameter("incrementId");
        
        //創建hashmap(用戶獲得簽名)
        SortedMap<String, String> paraMap = new TreeMap<String, String>();
        //設置隨機字符串
        String nonceStr = Utils.getUUIDString().replaceAll("-", "");
        //設置商戶退款單號
        Integer randomNumber = new Random().nextInt(900)+ 100;
        String orderIncrementId = DateUtil.formatDate(new Date(), DateUtil.DATE_FMT_FOR_ORDER_NUMBER)+randomNumber;
        
        //設置請求參數(小程序ID)
        paraMap.put("appid", Configuration.APPLYID);
        //設置請求參數(商戶號)
        paraMap.put("mch_id", Configuration.MCHID);
        //設置請求參數(隨機字符串)
        paraMap.put("nonce_str", nonceStr);
        //設置請求參數(商戶訂單號)
        paraMap.put("out_trade_no", incrementId);
        //設置請求參數(商戶退款單號)
        paraMap.put("out_refund_no", orderIncrementId);
        //設置請求參數(訂單金額)
        paraMap.put("total_fee", amount);
        //設置請求參數(退款金額)
        paraMap.put("refund_fee", amount);
        //TODO (這個回調地址 沒有具體進行測試 需要寫好邏輯 打版在測試)設置請求參數(通知地址)
        paraMap.put("notify_url", "http://abcdefg.nat123.cc:443/order/refundCallback");
        //調用邏輯傳入參數按照字段名的 ASCII 碼從小到大排序(字典序)
        String stringA = formatUrlMap(paraMap, false, false);
        //第二步,在stringA最後拼接上key得到stringSignTemp字符串,並對stringSignTemp進行MD5運算,再將得到的字符串所有字符轉換爲大寫,得到sign值signValue。(簽名)
        String sign = MD5Util.MD5(stringA+"&key="+Configuration.KEY).toUpperCase();
        //將參數 編寫XML格式
        StringBuffer paramBuffer = new StringBuffer();
        paramBuffer.append("<xml>");
        paramBuffer.append("<appid>"+Configuration.APPLYID+"</appid>");
        paramBuffer.append("<mch_id>"+Configuration.MCHID+"</mch_id>");
        paramBuffer.append("<nonce_str>"+paraMap.get("nonce_str")+"</nonce_str>");
        paramBuffer.append("<sign>"+sign+"</sign>");
        paramBuffer.append("<out_refund_no>"+paraMap.get("out_refund_no")+"</out_refund_no>");
        paramBuffer.append("<out_trade_no>"+paraMap.get("out_trade_no")+"</out_trade_no>");
        paramBuffer.append("<refund_fee>"+paraMap.get("refund_fee")+"</refund_fee>");
        paramBuffer.append("<total_fee>"+paraMap.get("total_fee")+"</total_fee>");
        paramBuffer.append("<notify_url>"+paraMap.get("notify_url")+"</notify_url>");
        paramBuffer.append("</xml>");
        
        try {
            //發送請求(POST)(獲得數據包ID)(這有個注意的地方 如果不轉碼成ISO8859-1則會告訴你body不是UTF8編碼 就算你改成UTF8編碼也一樣不好使 所以修改成ISO8859-1)
            Map<String,String> map = doXMLParse(doRefund(request,Configuration.REFUND_URL, new String(paramBuffer.toString().getBytes(), "ISO8859-1")));
            //應該創建 退款表數據
            if(map!=null && (StringUtils.isNotBlank(map.get("return_code")) && "SUCCESS".equals(map.get("return_code")))){
                if(StringUtils.isBlank(map.get("err_code_des"))) {
            //接口調用成功 執行操作邏輯 返回成功狀態碼給前臺 
                }else {
                    resultJson.put("returnCode", "error");
                    resultJson.put("err_code_des", map.get("err_code_des"));
                }
            }else {
                resultJson.put("returnCode", map.get("return_code"));
                resultJson.put("err_code_des", map.get("err_code_des"));
            }
        } catch (UnsupportedEncodingException e) {
            log.info("微信 退款 異常:"+e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            log.info("微信 退款 異常:"+e.getMessage());
            e.printStackTrace();
        }
        log.info("微信 退款 失敗");
        return resultJson;

3、Http請求 代碼(這塊的代碼邏輯和付款的是不一樣的)

    private String doRefund(HttpServletRequest request,String url,String data) throws Exception{
        /**
         * 注意PKCS12證書 是從微信商戶平臺-》賬戶設置-》 API安全 中下載的
         */
        
        KeyStore keyStore  = KeyStore.getInstance("PKCS12");
        String substring = request.getSession().getServletContext().getRealPath("/").substring(0, request.getSession().getServletContext().getRealPath("/").lastIndexOf("webapp\\"));
        FileInputStream instream = new FileInputStream(substring+"resources/refund_certificate/apiclient_cert.p12");//P12文件目錄 證書路徑
        try {
            /**
             * 此處要改
             * */
            keyStore.load(instream, Configuration.MCHID.toCharArray());//這裏寫密碼..默認是你的MCHID
        } finally {
            instream.close();
        }
 
        // Trust own CA and all self-signed certs
        /**
         * 此處要改
         * */
        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, Configuration.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 httpost = new HttpPost(url); // 設置響應頭信息
            httpost.addHeader("Connection", "keep-alive");
            httpost.addHeader("Accept", "*/*");
            httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            httpost.addHeader("Host", "api.mch.weixin.qq.com");
            httpost.addHeader("X-Requested-With", "XMLHttpRequest");
            httpost.addHeader("Cache-Control", "max-age=0");
            httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
            httpost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpclient.execute(httpost);
            try {
                HttpEntity entity = response.getEntity();
 
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
               return jsonStr;
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }

4、退款結果通知 後臺代碼 栗子

AESUtil

package com.bodi.repository;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AESUtil {
    /**
     * 密鑰算法
     */
    private static final String ALGORITHM = "AES";
    /**
     * 加解密算法/工作模式/填充方式
     */
    private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";
    /**
     * 生成key
     */
    private static SecretKeySpec key = new SecretKeySpec(MD5Util.MD5Encode(Configuration.KEY, "UTF-8").toLowerCase().getBytes(), ALGORITHM);
 
    /**
     * AES加密
     * 
     * @param data
     * @return
     * @throws Exception
     */
    public static String encryptData(String data) throws Exception {
        // 創建密碼器
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
        // 初始化
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return Base64Util.encode(cipher.doFinal(data.getBytes()));
    }
 
    /**
     * AES解密
     * 
     * @param base64Data
     * @return
     * @throws Exception
     */
    public static String decryptData(String base64Data) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
        cipher.init(Cipher.DECRYPT_MODE, key);
        return new String(cipher.doFinal(Base64Util.decode(base64Data)));
    }
Base64Util 
package com.bodi.repository;

import java.io.ByteArrayOutputStream;

public class Base64Util {
    private static final char[] base64EncodeChars = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
            'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
            's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
 
    private static byte[] base64DecodeChars = new byte[] { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60,
            61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1,
            -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 };
 
    private Base64Util() {
    }
 
    /**
     * 將字節數組編碼爲字符串
     *
     * @param data
     */
    public static String encode(byte[] data) {
        StringBuffer sb = new StringBuffer();
        int len = data.length;
        int i = 0;
        int b1, b2, b3;
 
        while (i < len) {
            b1 = data[i++] & 0xff;
            if (i == len) {
                sb.append(base64EncodeChars[b1 >>> 2]);
                sb.append(base64EncodeChars[(b1 & 0x3) << 4]);
                sb.append("==");
                break;
            }
            b2 = data[i++] & 0xff;
            if (i == len) {
                sb.append(base64EncodeChars[b1 >>> 2]);
                sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
                sb.append(base64EncodeChars[(b2 & 0x0f) << 2]);
                sb.append("=");
                break;
            }
            b3 = data[i++] & 0xff;
            sb.append(base64EncodeChars[b1 >>> 2]);
            sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
            sb.append(base64EncodeChars[((b2 & 0x0f) << 2) | ((b3 & 0xc0) >>> 6)]);
            sb.append(base64EncodeChars[b3 & 0x3f]);
        }
        return sb.toString();
    }
 
    public static byte[] decode(String str) throws Exception {
        byte[] data = str.getBytes("GBK");
        int len = data.length;
        ByteArrayOutputStream buf = new ByteArrayOutputStream(len);
        int i = 0;
        int b1, b2, b3, b4;
 
        while (i < len) {
 
            /* b1 */
            do {
                b1 = base64DecodeChars[data[i++]];
            } while (i < len && b1 == -1);
            if (b1 == -1) {
                break;
            }
 
            /* b2 */
            do {
                b2 = base64DecodeChars[data[i++]];
            } while (i < len && b2 == -1);
            if (b2 == -1) {
                break;
            }
            buf.write((b1 << 2) | ((b2 & 0x30) >>> 4));
 
            /* b3 */
            do {
                b3 = data[i++];
                if (b3 == 61) {
                    return buf.toByteArray();
                }
                b3 = base64DecodeChars[b3];
            } while (i < len && b3 == -1);
            if (b3 == -1) {
                break;
            }
            buf.write(((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2));
 
            /* b4 */
            do {
                b4 = data[i++];
                if (b4 == 61) {
                    return buf.toByteArray();
                }
                b4 = base64DecodeChars[b4];
            } while (i < len && b4 == -1);
            if (b4 == -1) {
                break;
            }
            buf.write(((b3 & 0x03) << 6) | b4);
        }
        return buf.toByteArray();
    }
}

MD5

package com.bodi.repository;

import java.security.MessageDigest;

public class MD5Util {
    
    /**
     * 十六進制下數字到字符的映射數組
     */
    private final static String[] hexDigits = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"}; 
    
    /**
     * @Title: encodeByMD5
     * @Description: 對字符串進行MD5編碼
     * @author yihj
     * @param @param originString
     * @param @return    參數
     * @return String    返回類型
     * @throws
     */
    public static String MD5(String originString){ 
        if (originString!=null) { 
            try { 
                //創建具有指定算法名稱的信息摘要 
                MessageDigest md5 = MessageDigest.getInstance("MD5"); 
                //使用指定的字節數組對摘要進行最後更新,然後完成摘要計算 
                byte[] results = md5.digest(originString.getBytes()); 
                //將得到的字節數組變成字符串返回  
                String result = byteArrayToHexString(results); 
                return result; 
            } catch (Exception e) { 
                e.printStackTrace(); 
            } 
        } 
        return null; 
    } 
    
    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString;
    }


    /**
     * @Title: byteArrayToHexString
     * @Description: 輪換字節數組爲十六進制字符串 
     * @author yihj
     * @param @param b
     * @param @return    參數
     * @return String    返回類型
     * @throws
     */
    private static String byteArrayToHexString(byte[] b){ 
        StringBuffer resultSb = new StringBuffer(); 
        for(int i=0;i<b.length;i++){ 
            resultSb.append(byteToHexString(b[i])); 
        } 
        return resultSb.toString(); 
    } 
    
    /**
     * @Title: byteToHexString
     * @Description: 將一個字節轉化成十六進制形式的字符串 
     * @author yihj
     * @param @param b
     * @param @return    參數
     * @return String    返回類型
     * @throws
     */
    private static String byteToHexString(byte b){ 
        int n = b; 
        if(n<0) 
        n=256+n; 
        int d1 = n/16; 
        int d2 = n%16; 
        return hexDigits[d1] + hexDigits[d2]; 
    } 
    
    /**
     * MD5加密 byte 數據
     * 
     * @param source
     *            要加密字符串的byte數據
     * @return
     */
    public static String getMD5(byte[] source) {
        String s = null;
        char hexDigits[] = { // 用來將字節轉換成 16 進製表示的字符
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
                'e', 'f' };
        try {
            java.security.MessageDigest md = java.security.MessageDigest
                    .getInstance("MD5");
            md.update(source);
            byte tmp[] = md.digest(); // MD5 的計算結果是一個 128 位的長整數,
                                        // 用字節表示就是 16 個字節
            char str[] = new char[16 * 2]; // 每個字節用 16 進製表示的話,使用兩個字符,
                                            // 所以表示成 16 進制需要 32 個字符
            int k = 0; // 表示轉換結果中對應的字符位置
            for (int i = 0; i < 16; i++) { // 從第一個字節開始,對 MD5 的每一個字節
                                            // 轉換成 16 進制字符的轉換
                byte byte0 = tmp[i]; // 取第 i 個字節
                str[k++] = hexDigits[byte0 >>> 4 & 0xf]; // 取字節中高 4 位的數字轉換,
                                                            // >>>
                                                            // 爲邏輯右移,將符號位一起右移
                str[k++] = hexDigits[byte0 & 0xf]; // 取字節中低 4 位的數字轉換
            }
            s = new String(str); // 換後的結果轉換爲字符串

        } catch (Exception e) {
            e.printStackTrace();
        }
        return s;
    }
    
    
}
實際退款代碼 邏輯
@Override
    public void refundCallback(HttpServletRequest request, HttpServletResponse response) {
        log.info("退款  微信回調接口方法 start");
        String inputLine = "";
        String notityXml = "";
        try {
            while((inputLine = request.getReader().readLine()) != null){
                notityXml += inputLine;
            }
            //關閉流
            request.getReader().close();
            log.info("退款  微信回調內容信息:"+notityXml);
            //解析成Map
            Map<String,String> map = doXMLParse(notityXml);
            //判斷 退款是否成功
            if("SUCCESS".equals(map.get("return_code"))){
                log.info("退款  微信回調返回是否退款成功:是");
                //獲得 返回的商戶訂單號
                String passMap = AESUtil.decryptData(map.get("req_info"));
                //拿到解密信息
                map = doXMLParse(passMap);
                //拿到解密後的訂單號
                String outTradeNo = map.get("out_trade_no");
                
                log.info("退款  微信回調返回商戶訂單號:"+map.get("out_trade_no"));
                //支付成功 修改訂單狀態 通知微信成功回調
                int sqlRow = orderJpaDao.updateOrderStatus("refunded",new Timestamp(System.currentTimeMillis()), outTradeNo);
                if(sqlRow == 1) {
                    log.info("退款 微信回調 更改訂單狀態成功");
                }
            }else {
                //獲得 返回的商戶訂單號
                String passMap = AESUtil.decryptData(map.get("req_info"));
                //拿到解密信息
                map = doXMLParse(passMap);
                //拿到解密後的訂單號
                String outTradeNo = map.get("out_trade_no");
                //更改 狀態爲取消
                int sqlRow = orderJpaDao.updateOrderStatus("canceled",new Timestamp(System.currentTimeMillis()), outTradeNo);
                if(sqlRow == 1) {
                    log.info("退款 微信回調返回是否退款成功:否");
                }
            }
            
            //給微信服務器返回 成功標示 否則會一直詢問 咱們服務器 是否回調成功
            PrintWriter writer = response.getWriter();
            //封裝 返回值
            StringBuffer buffer = new StringBuffer();
            buffer.append("<xml>");
            buffer.append("<return_code><![CDATA[SUCCESS]]></return_code>");
            buffer.append("<return_msg><![CDATA[OK]]></return_msg>");
            buffer.append("</xml>");
            //返回
            writer.print(buffer.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

5、注意事項

  1、退款 調用的時候需要證書 證書需要下載

  2、退款回調 需要解密 解密代碼 在上面

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章