微信支付退款,回调接口

文章介绍了如何在Java中处理微信支付退款,包括所需的Maven依赖、配置微信支付工具类以设置appid、mchId和证书,以及解码类用于解密退款回调信息。还提供了一个退款接口的示例代码,展示了退款请求参数的构建和退款状态的检查。回调接口部分提到了接收到微信回调的处理流程,包括解密req_info字段。
摘要由CSDN通过智能技术生成

前期准备

  • Maven依赖

      由于具体是哪个已经分不清了,有些是微信支付要用到的,有些是退款要用到的,有些是解码要用到的。

        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>
		<dependency>
			<groupId>com.github.binarywang</groupId>
			<artifactId>weixin-java-mp</artifactId>
		</dependency>
		<dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-common</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-pay</artifactId>
        </dependency>
        <dependency>
		    <groupId>com.alipay.sdk</groupId>
		    <artifactId>alipay-sdk-java</artifactId>
		    <version>4.9.5.ALL</version>
		</dependency>
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-pay</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!-- 微信支付sdk -->

        <!-- Apache Commons Codec库 -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.15</version>
        </dependency>
  •   微信支付工具类

       这个类是配置微信支付的基本信息的如appId,mchId这些,建议用我的,因为后面要用到这个配置类。

       注意:除了一般用到的appid这些东西外,还得去官网申请证书,如apiclient_cert.p12,pem这些。路径配置可以配置本地的地址或者服务器下的地址,按我下面那样子配置就可以。

public class PayConfig implements WXPayConfig{

    private byte[] certData;

    public PayConfig() throws Exception {
        //服务器的路径,按照你自己的来
        File file = new File("/usr/local/cert/apiclient_cert.p12");
//       本地测试的路径,按照你自己的来
//        File file = new File("D:\\*****\\*****\\*****\\apiclient_cert.p12");
        InputStream certStream = new FileInputStream(file);
        this.certData = new byte[(int) file.length()];
        certStream.read(this.certData);
        certStream.close();
    }

    public String getAppID() {
        return "****************";
    }

    public String getMchID() {
        return "********";
    }

    public String getKey() {
        return "************";
    }

    public InputStream getCertStream() {
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }

    public int getHttpConnectTimeoutMs() {
        return 8000;
    }

    public int getHttpReadTimeoutMs() {
        return 10000;
    }
}
  • 解码类

     拿来用就可以 ,是用于解密退款回调返回的信息req_info。第一个decrypt方法是解密手机号的,这里用不到,只是顺便贴一下,要用可以拿去用。

public class AesUtil {
    public static String decrypt(String encryptedData, String sessionKey, String iv) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidParameterSpecException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        // 初始化加密库
        Security.addProvider(new BouncyCastleProvider());

        byte[] encryptedDataBytes = Base64.getDecoder().decode(encryptedData);
        byte[] sessionKeyBytes = Base64.getDecoder().decode(sessionKey);
        byte[] ivBytes = Base64.getDecoder().decode(iv);


            // 构造解密器
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec keySpec = new SecretKeySpec(sessionKeyBytes, "AES");
            AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
            params.init(new IvParameterSpec(ivBytes));
            cipher.init(Cipher.DECRYPT_MODE, keySpec, params);

            byte[] decryptedDataBytes = cipher.doFinal(encryptedDataBytes);
            return new String(decryptedDataBytes, StandardCharsets.UTF_8);
    }

    public static String aesDecrypt(String secretInfo, String rawKey) throws Exception {
        try {
            SecretKeySpec key = new SecretKeySpec(DigestUtils.md5Hex(getContentBytes(rawKey, "utf-8")).toLowerCase().getBytes(), "AES");
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding", "BC");
            cipher.init(Cipher.DECRYPT_MODE, key);
            return new String(cipher.doFinal(Base64.getDecoder().decode(secretInfo)),"UTF-8");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    private static byte[] getContentBytes(String content, String charset) {
        if (charset == null || "".equals(charset)) {
            return content.getBytes();
        }
        try {
            return content.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("MD5签名过程中出现错误" + charset);
        }
    }

}

退款接口

  @注解按需添加 

  transactionId为微信支付订单号,money表示你要退款的钱,reason表示原因。

@Data
@Accessors(chain = true)
public class ConfirmCancel{
    private Long transactionId;
    private String money;
    private String reason;
}

微信退款以分为单位,不论是总额还是要退款的钱。 (有些字段和微信官方提供的不同,是因为版本不一样,用就行了

    @Log
    @ApiOperation(value = "退款")
    @PostMapping("confirmCancel")
    public Object confirmCancel(@RequestBody ConfirmCancel confirmCancel) throws Exception {//serviceOrderId
        if (poDto != null){
            String transactionId = confirmCancel.getTransactionId();
            Double stringMonry = Double.valueOf(confirmCancel.getMoney());

            //退款以分为单位,且为int类型,不能有小数点。前端输入的money也得按这个来。如0.01元就得是1分

            //totalMoney为总额,你可以从之前支付订单存的数据库里面取,也可以直接从前端传,我这里取的是数据库里的,按你自己的来
            Double totalMoney = *****Dto.get******Vo().getTotalMoney()*100;
            Integer intToalMoney = totalMoney.intValue();

            Double refundMoney = stringMonry*100;
            Integer intRefundMoney = refundMoney.intValue();

            String url = "https://(填写域名)/(填写回调接口url)";  //如果你需要回调的话
            if (stringMonry <= totalMoney) {
                PayConfig config = new PayConfig();
                WXPay wxpay = new WXPay(config);
                String refundNo = String.valueOf(UUID.randomUUID());
                Map<String, String> map = new HashMap<>();
                map.put("appid", config.getAppID());
                map.put("mch_id", config.getMchID());
                map.put("refund_desc", confirmCancel.getReason()); //退款原因
                map.put("nonce_str", WXPayUtil.generateNonceStr());
                map.put("transaction_id", transactionId);
                map.put("out_refund_no", refundNo);
                map.put("total_fee", intToalMoney + "");         //总价格
                map.put("refund_fee", intRefundMoney + "");      //退款价格
                map.put("notify_url",url);  //回调地址
                String sign = WXPayUtil.generateSignature(map, config.getKey());
                map.put("sign", sign);

                Map<String, String> refund = wxpay.refund(map);
                System.out.println(refund);    //返回的详情,可以根据这个来看成没成功
                String return_code = refund.get("return_code");
                String return_msg = refund.get("return_msg");

            } else {
                return buildFailure("超出原有金额");
            }
        }
        return buildFailure("失败");
    }

实现效果        

                                                                 请求参数

 回调接口

    注意:如果使用回调,得先在退款的接口中,设置你的notify_url,这个url为你的回调接口地址

再次提醒,@注解按需求添加。

    @Log
    @PostMapping("refundNotifyResult")
    public Object refundNotifyResult(@RequestBody String xmlData) throws Exception {
        System.out.println("进入回调");
        //TODO 具体业务处理
        logger.info(xmlData);    //回调接收到的是xml格式的数据

        PayConfig config = new PayConfig();
        Map<String, String> xmlMap =  WXPayUtil.xmlToMap(xmlData); //转为map格式
        System.out.println("xmlMap==>" + xmlMap);

        //退款成功后返回一个加密字段req_info,以下为解密
        //解密的方法在上述准备工作的AesUtil类里
        String req_info = xmlMap.get("req_info");
        String resultStr = AesUtil.aesDecrypt(req_info,config.getKey());

        Map<String, String> reqInfo = WXPayUtil.xmlToMap(resultStr);
        System.out.println("reqInfo===>" + reqInfo);

        String out_trade_no = reqInfo.get("out_trade_no");
        String return_code = xmlMap.get("return_code");

        Map<String,Object> parm = new HashMap<>();
        if (StringUtils.isNotBlank(return_code) && StringUtils.equals(return_code, SUCCESS)) {

            //你自己的业务操作
            *********************************

            parm.put("return_code",SUCCESS);
            parm.put("req_info",reqInfo);
        }

        return parm;  //返回给前端的参数
    }

以下为打印的数据,因为涉及隐私,所以大部分都打码了。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值