小程序微信JSAPI支付进行退款操作

小程序使用微信支付进行退款操作

微信支付-退款操作的特殊性

  1. 在微信支付中,有生成预订单接口、查询订单状态接口、关闭订单接口、申请退款接口和退款查询接口。
  2. 之前我已经写过一片文章介绍如何使用微信支付拉起收银台支付,完整的介绍了从调用微信接口,到将微信接口返回的数据,处理后给前端拉起收银台完成用户付款。
  3. 除了申请退款接口、其它功能接口的调用使用,类似,除了参数的不一样
  4. 其实申请退款接口的使用只是多了一个商户的证书,这个证书用来校验身份之类的信息,将该证书加载到发送请求的httpclient中即可。

微信官方指引

  1. 微信支付证书的使用,可以参考官方说明:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
  2. 可以按照以下路径下载微信支付证书:微信商户平台(pay.weixin.qq.com)–>账户中心–>账户设置–>API安全;注需要管理员权限才能下载
  3. 下载下来是个压缩包,内有两种格式的证书,分别适用于不同的开发环境,我们只需要pkcs12格式的即可;注:证书的密码默认是商户ID。

自己开发

  1. 提前下载好指定商户的,证书,存放到自己指定的目录中,部署到服务器的话,建议对证书进行安全设置或者保护,以防其他人获取到该证书,最好放到web容器以外的路径下
  2. 证书准备完毕后,根据官方文档进行编码调试。https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4

开发步骤

  1. 加载证书
  2. 构建请求的客户端信息
  3. 填充请求参数
  4. 对参数进行签名,并转换成xml形式
  5. 发送请求到微信
  6. 参数处理

具体编码

  1. 将退款需要的商户配置参数通过配置文件注入
 	//证书地址
    @Value("${wechat.cert}")
    private String certLocation;
    
    //小程序appid
    @Value("${wechat.appid}")
    private String appid;
	
	//商户号
    @Value("${wechat.partner}")
    private String partner;
    
    //商户密钥
    @Value("${wechat.partnerkey}")
    private String partnerkey;
  1. 具体的实现(仅供参考,小程序支付可以直接使用下面代码,也可根据自己业务进行删减,最好自己编写一边):
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import org.apache.http.impl.client.CloseableHttpClient;
/**
     * 退款接口
     *
     * @param orderNo    原订单号
     * @param refoundNo  退款单号
     * @param totalFee   订单总金额
     * @param refoundFee 要退款金额
     * @return
     */
    @Override
    public Map refound(String orderNo, String refoundNo, String totalFee, String refoundFee) {
        logger.info("申请退款接口,前端传参:orderNo: "+orderNo+",refoundNo: "+refoundNo+",totalFee: "+totalFee+",refoundFee: "+refoundFee);
        Map<String, String> map = null;
        HashMap<String, String> resMap = new HashMap<>();
        try {
            KeyStore clientStore = KeyStore.getInstance("PKCS12");
            // 读取本机存放的PKCS12证书文件
            FileInputStream instream = new FileInputStream(certLocation);
            try {
                // 指定PKCS12的密码(商户ID)
                clientStore.load(instream, partner.toCharArray());
            } finally {
                instream.close();
            }
            SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(clientStore, partner.toCharArray()).build();
            // 指定TLS版本
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null,
                    SSLConnectionSocketFactory.getDefaultHostnameVerifier());
            // 设置httpclient的SSLSocketFactory
            CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
            try {
                HttpPost httpost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund");

                //封装参数
                Map param = new HashMap();
                param.put("appid", appid);
                param.put("mch_id", partner);
                param.put("out_trade_no", orderNo);
                param.put("nonce_str", WXPayUtil.generateNonceStr());
                //退款单号
                param.put("out_refund_no", refoundNo);
                //订单总金额
                param.put("total_fee", totalFee);
                //退款金额
                param.put("refund_fee", refoundFee);

                String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);
                System.out.println("申请退款请求参数" + xmlParam);

                logger.info("申请退款请求参数" + xmlParam);
                httpost.setEntity(new StringEntity(xmlParam, "UTF-8"));
                CloseableHttpResponse response = httpclient.execute(httpost);
                try {
                    HttpEntity entity = response.getEntity();
                    String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                    EntityUtils.consume(entity);
                    System.out.println("申请退款微信返回参数:" + jsonStr);
                    logger.info("申请退款微信返回参数:" + jsonStr);
                    map = WXPayUtil.xmlToMap(jsonStr);

                    RefoundMent refoundMent = new RefoundMent();
                    if (map.get("return_code").equals("SUCCESS")) {
                    	//退款成功
                        resMap.put("resultCode", "SUCCESS");
                        resMap.put("mchId", map.get("mch_id"));
                       // resMap.put("sign", map.get("sign"));
                       // resMap.put("transactionId", map.get("transaction_id"));
                        resMap.put("orderNo", orderNo);
                        resMap.put("refoundNo", refoundNo);
                       // resMap.put("refundId", map.get("refund_id"));
                        resMap.put("refundFee", map.get("refund_fee"));
                        }else {
                            resMap.put("resultCode", "FAIL");
                            resMap.put("errCode", map.get("err_code"));
                            resMap.put("errCodeDes", map.get("err_code_des"));
                            logger.info("申请退款失败:"+orderNo);
                            logger.info("失败原因:"+map.get("err_code")+" 原因:"+map.get("err_code_des"));
                            //退款失败,将失败信息存库
                            /*
                            CompletableFuture.runAsync(() -> {
                                        RefoundMent failReFound=new RefoundMent();
                                        failReFound.setMachRefoundNo(refoundNo);
                                  
                                        failReFound.setRefoundTime(new Date());
                                        failReFound.setRefoundRes(resMap.get("err_code_des"));

                                        int insert = refoundMentDao.insertFail(refoundMent);
                                        logger.info("申请退款失败,存库成功");
                                    },
                                    threadPoolTaskExecutor
                            );
                            */
                        }
                    }

                } finally {
                    response.close();
                }
            } finally {
                httpclient.close();
            }
        } catch (Exception e) {
            logger.error("发起退款异常,订单号:" + orderNo);
            e.printStackTrace();
            resMap.put("resultCode", "FAL");
            resMap.put("mesg", "请求异常");

        }
        logger.info("申请退款返回前端数据:"+resMap);
        return resMap;
    }

  1. 由此结束

注意事项

  1. 传参,原订单号是之前发起预订单的时候商户自定义的商户订单号,必须成功支付才能申请退款,退款单号,也是商家自定义的不重复编号,推荐可以使用雪花算法,uuid也可以
  2. 申请退款,接口返回成功,也不代表会退款成功,会有很多情况导致退款失败,例如商户账户里面没钱,所以退款成功与否还需要调用查询退款接口查询。
  3. 同一个与预付单支持部分退款和分批次退款,退款金额应该小于等于之前支付的金额,小于支付金额即部分退款,分批退款,需要发起多次申请退款,且加起来金额也应该小于等于原支付金额,每批次的退款单号应不重复且唯一.
  4. 上述代码,我使用了连接池以及lambda表达式,进行写库操作,读者可以自行定义成功与失败后的处理方式,注:不管怎样保持记录日志是个好习惯.
  5. 上述代码中退款的传参只是必要的一部分,关于退款操作可传的参数还有很多,读者可以自行选择,对自己业务扩展等.

最后

之前说要把微信支付的退款写完的,但苦于一直没时间来写博客,所以一直没写,现在业务已经偏移其他方向,仅将我自己做的写出来给读者以参考,如有不解可以评论,我看到后会回复.

你可能感兴趣的:(小程序支付)