JAVA微信退款(JSAPI支付)

上一章咱们介绍了微信支付整个流程,这章就趁热打铁地整理下微信退款(JSAPI支付)相关的知识,为这几章的微信支付画上一个句号把。

前提:从微信公众号那边获取appid,mchid,paternerKey三个参数备用。

1、微信申请退款的实现

应用场景

当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。

注意:

1、交易时间超过一年的订单无法提交退款

2、微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号

3、请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次

错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次

4、每个支付订单的部分退款次数不能超过50次

5、如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败

是否需要证书:

java开发申请退款需要要用到双向证书(apiclient_cert.p12证书)。登录微信商户平台(https://pay.weixin.qq.com/)如下图所示操作


申请退款接口调用:

@GET
@Path(value = "refundOrder")
@Produces(MediaType.APPLICATION_JSON)
public Response refundOrder(@QueryParam("outTradeNo") String outTradeNo){
    Map<String, Object> result = new HashMap<String, Object>();
    try{
        // 1、获取参数,再进行申请退款
        Map<String, String> paramMap = new HashMap<String, String>(); 
        paramMap.put("appid", appid); //公众账号ID
        paramMap.put("mch_id", mchid); //商户号
        paramMap.put("nonce_str",  WXPayUtil.generateNonceStr());//随机字符串  
        paramMap.put("out_refund_no", WXPayUtil.generateNonceStr());//商户退款单号
        paramMap.put("out_trade_no", outTradeNo);//商户订单号
        paramMap.put("total_fee", price+"");  //订单金额
        paramMap.put("refund_fee", price+"");  //退款金额 
        String sign = WXPayUtil.generateSignature(paramMap, paternerKey);
        paramMap.put("sign", sign); //签名
        String requestXmlString = WXPayUtil.mapToXml(paramMap);//转为xml字符串
        String refundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";//微信申请退款接口
        
        // 2、判断微信是否退款成功
        HttpResponse resp = WeChatUtil.httpPost(refundUrl, requestXmlString);
        WeChatOrderResponse response = null;
        if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            String entityStr = EntityUtils.toString(resp.getEntity(), "UTF-8");
            response = (WeChatOrderResponse) WeChatUtil.xmlToPojo(entityStr,WeChatOrderResponse.class);
        }
        if("FAIL".equals(response.getReturn_code())){
            throw new RuntimeException("return_code为fail, " + response.getReturn_msg());
        }
        if("FAIL".equals(response.getResult_code())){
            throw new RuntimeException("result_code为fail, " +response.getErr_code_des());
        }

         //3、微信退款成功后的操作流程
         ......

        result.put("code", SUCCESS);
    }catch(Exception e){
        result.put("code", ERROR);
        result.put("msg", e.getMessage());
    }
    return Response.ok(result).build();
}

WeChatOrderResponse类的参数与微信的申请退款接口的返回结果一致,下面是该类的具体实现:

@XmlRootElement(name = "xml")
public class WeChatOrderResponse {
    private String return_code;
    private String return_msg;
 
    // 以下字段在return_code为SUCCESS的时候有返回
    private String result_code;
    private String err_code;
    private String err_code_des;
    private String appid;
    private String mch_id;
    private String nonce_str;
    private String sign;
    private String transaction_id;
    private String out_trade_no;
    private String out_refund_no;
    private String refund_id;
    private String refund_fee;
    private String settlement_refund_fee;
    private String total_fee;
    private String settlement_total_fee;
    private String fee_type;
    private String cash_fee;
    private String cash_fee_type;
    private String cash_refund_fee;
    private String coupon_type_$n;
    private String coupon_refund_fee;
    private String coupon_refund_fee_$n;
    private String coupon_refund_count;
    private String coupon_refund_id_$n;
 
    // 以下字段在return_code 和result_code都为SUCCESS的时候有返回
    private String trade_type;
    private String prepay_id;
    private String code_url;
    private String openid;
    
    //参数的get/set方法
    ......
}

WeChatUtil工具类(亲测有效)的具体结构如下:

public class WeChatUtil {

   /**
    * Post请求+证书
    * @param url  微信申请退款接口链接
    * @param entity  微信申请退款接口的参数
    */
   public static HttpResponse httpPost(String url, String entity) {
        //注意PKCS12证书 是从微信商户平台->账号设置->API安全 中下载的
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        //加载本地的证书进行https加密传输,keystorePath是证书的绝对路径
        FileInputStream instream = new FileInputStream(new File(keystorePath)); 
        try{
            //设置证书密码,keystorePassword:下载证书时的密码,默认密码是你的mchid
            keyStore.load(instream, keystorePassword.toCharArray()); 
        } finally { 
            instream.close();
        }
        
        //java 主动信任证书
        //keystorePassword:下载证书时的密码,默认密码是你的mchid
        SSLContext sslcontext = SSLContexts.custom()
              .loadKeyMaterial(keyStore, keystorePassword.toCharArray()).build();
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
              sslcontext, new String[] { "TLSv1" }, null,
              SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        //CloseableHttpClient 加载证书来访问https网站
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        try {
           // 设置响应头信息,发送post请求
           HttpPost httpPost = new HttpPost(url);  
           RequestConfig requestConfig = RequestConfig.custom()
                     .setSocketTimeout(5000)
                     .setConnectTimeout(5000)
                     .setConnectionRequestTimeout(5000)
                     .build();
           httpPost.setConfig(requestConfig);
           httpPost.setEntity(new StringEntity(entity, "UTF-8"));
           httpPost.setHeader("Accept", "*/*");
           httpPost.setHeader("Content-type", "application/xml");
           HttpResponse resp = httpClient.execute(httpPost);
           return resp;
        } catch (Exception e) {
           e.printStackTrace();
           return null;
      }
  }
  
  /**
  * 字符串转为对象
  * @param xml  json字符串
  * @param clazz
  */
  public static Object xmlToPojo(String xml, Class<?> clazz){
        try {
            JAXBContext context = JAXBContext.newInstance(clazz);
            Unmarshaller umMarshaller = context.createUnmarshaller();
            Object pojo = umMarshaller.unmarshal(new ByteArrayInputStream(xml.getBytes("utf-8")));           
        }catch (Exception e ){
            e.printStackTrace();
        }
  }
}

2、查询退款的实现

应用场景:

提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。(退款有延迟,可通过轮询来判断是否成功退款)

注意:如果单个支付订单部分退款次数超过20次请使用退款单号查询

退款状态变化:

 

查询退款接口具体实现:

@GET
@Path(value = "queryRefund")
@Produces(MediaType.APPLICATION_JSON)
public Response queryRefund(@QueryParam("outTradeNo") String outTradeNo){
    Map<String, Object> result = new HashMap<String, Object>();
    try{
        // 1、查询退款
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("appid", appid);// 公众账号ID
        paramMap.put("mch_id", mchid);// 商户号
        paramMap.put("nonce_str", WXPayUtil.generateNonceStr());// 随机字符串
        paramMap.put("out_trade_no", outTradeNo + "");// 商户订单号
        String sign = WXPayUtil.generateSignature(paramMap, paternerKey);
        paramMap.put("sign", sign);// 签名
        WXPay wxpay = new WXPay(MyConfig.getInstance()); 
        Map<String, String> resp = wxpay.refundQuery(paramMap);
        if("FAIL".equals(resp.get("return_code"))){
            throw new RuntimeException("return_code为fail, " + resp.get("return_msg"));
        }
        if("FAIL".equals(resp.get("result_code")) && !"订单已全额退款".equals(resp.get("err_code_des"))){
            throw new RuntimeException("result_code为fail, " + resp.get("err_code_des")); 
        }
        
        // 2、成功退款后的操作流程
        ......
        
        result.put("code", SUCCESS); 
    }catch(Exception e){
        e.printStackTrace();
        result.put("code", ERROR);
        result.put("msg", e.getMessage()); 
    }
    return Response.ok(result).build();
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值