JAVA之微信小程序支付退款(PKCS12证书设置与SSL请求封装)

版权声明:powered by 大狼狗郑锴/Moshow魔手 https://blog.csdn.net/moshowgame/article/details/84952585

问题背景

话说有小程序支付就有小程序退款,退款和支付是对应的,不能凭空退。

解决方案

解决方案有点长,我们分两个部分,一个是业务参数拼接与Sign签名,一个是https请求/ssl请求与pkcs12证书,用到的包org.apache.httpcomponents/httpclient。

参数拼接

以下是官方规定的字段,有些可以不需要,根据业务情况来即可。
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4

字段名 变量名 必填 类型 示例值 描述
小程序ID appid String(32) wx8888888888888888 微信分配的小程序ID
商户号 mch_id String(32) 1900000109 微信支付分配的商户号
随机字符串 nonce_str String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位。推荐随机数生成算法
签名 sign String(32) C380BEC2BFD727A4B6845133519F3AD6 签名,详见签名生成算法
微信订单号 transaction_id 二选一 String(32) 1217752501201407033233368018 微信生成的订单号,在支付通知中有返回
商户订单号 out_trade_no String(32) 1217752501201407033233368018 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
商户退款单号 out_refund_no String(64) 1217752501201407033233368018 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
订单金额 total_fee Int 100 订单总金额,单位为分,只能为整数,详见支付金额
退款金额 refund_fee Int 100 退款总金额,订单总金额,单位为分,只能为整数,详见支付金额

退款请求报文

以下是真实的业务场景所需要的参数,数据做了处理,可供参考。
=======================退款XML数据:

<xml>
    <appid>wxe09a8f4******</appid>
    <mch_id>150074*****</mch_id>
    <nonce_str>aqw596hsfxs9f0kposs64pzw8xiwd692</nonce_str>
    <out_trade_no>20181210024229*****</out_trade_no>
    <out_refund_no>2018121002422*****</out_refund_no>
    <total_fee>1</total_fee>
    <refund_fee>1</refund_fee>
    <refund_desc>用户退票f906d8ae70434430ace5671651bde693</refund_desc>
    <sign>6C2D267A54932D941C4F838D12D0C916</sign>
</xml>

PKCS12证书与SSl请求封装

用到的maven库是apache的httpclient,里面包含大量的SSL请求相关,引入即可。

<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.6</version>
</dependency>

外部Controller或者ServiceImpl调用方法

String result = "";
 // 调用退款接口,并接受返回的结果
 try{
     result = PayUtil.doRefund(mch_id,refund_url,xml);
     log.info("=======================退款RESPONSE数据:" + result);
 }catch (Exception e){
     e.printStackTrace();
 }

核心业务请求,大部分基于httpclient,需要手工设置filepath,也可以自己修改成一个变量传进来。

  • mchId=商户id用于解码秘钥
  • refund_url=请求的url,官方是https://api.mch.weixin.qq.com/secapi/pay/refund基本不会变
  • data是上文封装好的xml数据
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
/**  
 * 微信支付工具类
 * @Author blog.csdn.com/moshowgame
 */  
public class PayUtil {
    public static String doRefund(String mchId,String url, String data) throws Exception{
        /**
         * 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
         */

        KeyStore keyStore  = KeyStore.getInstance("PKCS12");
        //P12文件目录 证书路径,这里需要你自己修改,linux下还是windows下的根路径
        String filepath = "D:\\";
        System.out.println("filepath->"+filepath);
        FileInputStream instream = new FileInputStream(filepath+"apiclient_cert.p12");
        try {
            keyStore.load(instream, mchId.toCharArray());//这里写密码..默认是你的MCHID
        } finally {
            instream.close();
        }

        // Trust own CA and all self-signed certs
        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, mchId.toCharArray())//这里也是写密码的
                .build();
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        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();
        }
    }

退款返回

看到如果不是显示什么end file of server或者其他FAIL如签名错误的话,就证明成功了,剩下的可能是这些例如"基本账户余额不足,请充值后重新发起"的,账户里存些钱进去就可以退了,核心的业务逻辑已经搞定。
=======================退款RESPONSE数据:

<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wxe09a8f4******]]></appid>
<mch_id><![CDATA[15007******]]></mch_id>
<nonce_str><![CDATA[Lp9JL9qF1tvSxXBb]]></nonce_str>
<sign><![CDATA[B8075C857C9760023CB5A61D49F3138E]]></sign>
<result_code><![CDATA[FAIL]]></result_code>
<err_code><![CDATA[NOTENOUGH]]></err_code>
<err_code_des><![CDATA[基本账户余额不足,请充值后重新发起]]></err_code_des>
</xml>

猜你喜欢

转载自blog.csdn.net/moshowgame/article/details/84952585