java实现微信退款及退款回调(v3)

一:微信退款的实现

1.需要的jar包

        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java</artifactId>
            <version>0.2.7</version>
        </dependency>

        <!--微信支付sdk-->
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>3.0.9</version>
        </dependency>

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.71</version>
        </dependency>

2.退款的实现

package com.example.activity.controller;

import com.alibaba.fastjson.JSONObject;
import com.github.wxpay.sdk.WXPayUtil;
import com.wechat.pay.java.core.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;


@Slf4j
@RestController
@RequestMapping("/wxPayRefund")
public class wxPayRefundController {

    @GetMapping("/refundOrder")
    public Map<String, Object> refundOrder() throws Exception {
        //构造请求参数
    /*
    transaction_id   //二选一 【微信支付订单号】 原支付交易对应的微信订单号,与out_trade_no
    out_trade_no     //二选一 【户订单号】 原支付交易对应的商户订单号,与transaction_id
    out_refund_no    //必填  【商户退款单号】 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
    amount           //必填   【金额信息】 订单金额信息   AmountReq
         refund          //必填   【退款金额】 退款金额,单位为分,只能为整数,不能超过原订单支付金额。
         total           //必填    【原订单金额】 原支付交易的订单总金额,单位为分,只能为整数。
         currency        //必填     【退款币种】 符合ISO 4217标准的三位字母代码,目前只支持人民币:CNY。
         from            //选填    【退款出资账户及金额】
    reason           //选填 【退款原因】 若商户传入,会在下发给用户的退款消息中体现退款原因
    notify_url       //选填 【退款结果回调url】 异步接收微信支付退款结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的这个地址。
    funds_account    //选填 【退款资金来源】 若传递此参数则使用对应的资金账户退款,否则默认使用未结算资金退款(仅对老资金流商户适用)
    goods_detail     //选填 【退款商品】 指定商品退款需要传此参数,其他场景无需传递
    */
        Map data = new HashMap();
        data.put("out_trade_no", "你要退款的订单号");
        data.put("out_refund_no", "退款订单号(随机生成一个就行,保证不重复)");//这里需要的是字符串
        Map amount = new HashMap();
        amount.put("refund", "退款的金额(以分为单位)");//退款金额可以最多分50次进行退款
        amount.put("total", "订单的总金额(以分为单位)");
        amount.put("currency", "CNY");
        data.put("amount", amount);
        //退款的回调是否有必要,这个不确定是否有必要(很多文章都不介绍),个人认为为了安全还是加上比较好(注意:回调不能自定义参数传递,没有attach 参数)
//    data.put("notify_url", "回调地址");


        HttpUrl httpurl = HttpUrl.parse("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
        // 设置请求链接
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
        //设置请求头信息(需要生成token进行退款身份的验证)
        httpPost.setHeader("Authorization", getToken("POST", httpurl, JSONObject.toJSONString(data)));
        httpPost.setHeader("Accept", "application/json");
        httpPost.setHeader("Content-Type", "application/json");

        //设置请求参数
        httpPost.setEntity(new StringEntity(JSONObject.toJSONString(data)));

        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        CloseableHttpResponse response = httpClient.execute(httpPost);

        // 获取响应状态码
        int statusCode = response.getStatusLine().getStatusCode();
        // 获取响应内容
        String responseBody = EntityUtils.toString(response.getEntity());
        // 关闭响应对象
        response.close();


        Map<String, Object> responseMap = JSONObject.parseObject(responseBody, Map.class);
        responseMap.put("code", statusCode);
        responseMap.put("data", responseBody);
        return responseMap;
    }

    //生成强求头需要的token
    public String getToken(String method, HttpUrl url, String body) throws UnsupportedEncodingException, SignatureException, NoSuchAlgorithmException, InvalidKeyException {
        String nonceStr = WXPayUtil.generateNonceStr(); //WXPayUtil是微信支付自带的sdk
        long timestamp = System.currentTimeMillis() / 1000; //生成时间戳

        //需要加密的参数
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }
        String parameter = method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";

        //对参数进行加密
        byte[] bytes = parameter.getBytes("utf-8");
        Signature sign = Signature.getInstance("SHA256withRSA");
        PrivateKey privateKey = PemUtil.loadPrivateKeyFromPath(privateKeyPath);  //privateKeyPath是商户证书密钥的位置apiclient_key.pem
        sign.initSign(privateKey);   //商户密钥文件路径
        sign.update(bytes);
        String signature = Base64.getEncoder().encodeToString(sign.sign());

        //获取token
        String token = "mchid=\"" + merchantId + "\","      //商户号
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + merchantSerialNumber + "\"," //merchantSerialNumber是微信支付中申请的证书序列号
                + "signature=\"" + signature + "\"";


        String schema = "WECHATPAY2-SHA256-RSA2048 "; //注意有一个空格
        return schema + token;
    }
}

二:退款回调的实现(需要的话添加到前面的controller中)

 注意:要使用回调,不要忘了在退款的参数中加入回调地址

//退款回调
@PostMapping("/notifyUrl")
@Transactional
public Object refundNotifyResult(@RequestBody String jsonData) throws Exception {
    //转为map格式
    Map<String, String> jsonMap = JSONObject.parseObject(jsonData, Map.class);

    //退款成功后返回一个加密字段resource,以下为解密
    /**
     * 解密需要从resource参数中,获取到ciphertext,nonce,associated_data这三个参数进行解密
     */
    String resource = JSONObject.toJSONString(jsonMap.get("resource"));
    JSONObject object = JSONObject.parseObject(resource);

    String ciphertext = String.valueOf(object.get("ciphertext"));
    String nonce = String.valueOf(object.get("nonce"));
    String associated_data = String.valueOf(object.get("associated_data"));

    String resultStr = decryptToString(associated_data.getBytes("UTF-8"), nonce.getBytes("UTF-8"), ciphertext);
    Map<String, String> reqInfo = JSONObject.parseObject(resultStr, Map.class);

    String refund_status = reqInfo.get("refund_status");//退款状态
    String out_trade_no = reqInfo.get("out_trade_no"); //订单号

    Map<String, Object> parm = new HashMap<>();
    if (!StringUtils.isEmpty(refund_status) && "SUCCESS".equals(refund_status))  {

        //你自己的业务

        parm.put("code", "SUCCESS");
        parm.put("message", "成功");
    } else {
        parm.put("code", "FAIL");
        parm.put("message", "失败");
        throw new RuntimeException("退款失败");
    }
    return parm;  //返回给前端的参数
}


//退款回调  解密数据
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException {
    try {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");


        SecretKeySpec key = new SecretKeySpec(apiV3key.getBytes(), "AES");//todo 这里的apiV3key是你的商户APIV3密钥
        GCMParameterSpec spec = new GCMParameterSpec(128, nonce);//规定为128

        cipher.init(Cipher.DECRYPT_MODE, key, spec);
        cipher.updateAAD(associatedData);

        return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
        throw new IllegalStateException(e);
    } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
        throw new IllegalArgumentException(e);
    }
}

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java微信退款V3是指使用Java语言开发的微信支付接口版本V3中的退款功能。微信支付是一种在线支付平台,用户可以通过微信支付完成各种消费,包括购买商品、预定服务等。在某些情况下,用户可能需要退款,例如购买商品后发现有质量问题或者服务未提供等原因。 Java微信退款V3提供了一种方便快捷的方式来实现退款操作。具体流程如下: 1. 创建退款请求:通过Java代码构建一个退款请求对象,包括订单号、退款金额、退款原因等信息。 2. 生成签名:使用微信支付提供的签名算法对退款请求进行签名,确保数据的安全性。 3. 发送退款请求:使用Java的Http请求库向微信支付接口发送退款请求,并将签名、订单信息等参数传递给接口。 4. 处理应答:接收微信接口返回的响应结果,包括退款是否成功、返回的错误信息等。 5. 处理结果:根据接口返回的结果进行相应的处理,如果退款成功,则更新订单状态并通知用户退款完成;如果退款失败,则根据返回的错误信息进行处理。 Java微信退款V3具有以下特点: 1. 安全可靠:使用微信支付提供的签名算法对退款请求进行签名,确保数据传输的安全性。 2. 简单易用:通过Java代码构建退款请求对象,方便快捷地实现退款操作。 3. 实时性好:通过Http请求将退款请求发送给微信支付接口,实时地获取退款结果。 4. 丰富的功能:除了基本的退款功能外,Java微信退款V3还支持退款查询、退款通知等附加功能。 总之,Java微信退款V3是一种方便快捷、安全可靠的退款解决方案,可以方便地实现退款操作,并提供了丰富的功能以满足不同业务需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值