当前位置:   article > 正文

java微信支付v3系列——5.微信支付成功回调_java微信支付回调

java微信支付回调

目录

java微信支付v3系列——1.微信支付准备工作
java微信支付v3系列——2.微信支付基本配置
java微信支付v3系列——3.订单创建准备操作
java微信支付v3系列——4.创建订单的封装及使用
java微信支付v3系列——5.微信支付成功回调
java微信支付v3系列——6.微信支付查询订单API
java微信支付v3系列——7.微信支付之申请退款
java微信支付v3系列——8.微信支付之退款成功回调
java微信支付v3系列——9.微信支付之商家转账API

正文

同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。

如果在所有通知频率后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。

特别提醒:商户系统对于开启结果通知的内容一定要做签名验证,并校验通知的信息是否与商户侧的信息一致,防止数据泄露导致出现“假通知”,造成资金损失。

该链接是通过基础下单接口中的请求参数“notify_url”来设置的,要求必须为https地址。请确保回调URL是外部可正常访问的,且不能携带后缀参数,否则可能导致商户无法接收到微信的回调通知信息。

微信验签工具类

啥也不说了,直接复制即可,也没什么注意事项。

import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;

import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;

public class WechatPayValidatorForRequest {

    protected static final Logger log = LoggerFactory.getLogger(WechatPayValidatorForRequest.class);
    /**
     * 应答超时时间,单位为分钟
     */
    protected static final long RESPONSE_EXPIRED_MINUTES = 5;
    protected final Verifier verifier;
    protected final String body;
    protected final String requestId;

    public WechatPayValidatorForRequest(Verifier verifier, String body, String requestId) {
        this.verifier = verifier;
        this.body = body;
        this.requestId = requestId;
    }

    protected static IllegalArgumentException parameterError(String message, Object... args) {
        message = String.format(message, args);
        return new IllegalArgumentException("parameter error: " + message);
    }

    protected static IllegalArgumentException verifyFail(String message, Object... args) {
        message = String.format(message, args);
        return new IllegalArgumentException("signature verify fail: " + message);
    }

    public final boolean validate(HttpServletRequest request) throws IOException {
        try {
            validateParameters(request);

            String message = buildMessage(request);
            String serial = request.getHeader(WECHAT_PAY_SERIAL);
            String signature = request.getHeader(WECHAT_PAY_SIGNATURE);

            if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
                throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
                        serial, message, signature, request.getHeader(REQUEST_ID));
            }
        } catch (IllegalArgumentException e) {
            log.warn(e.getMessage());
            return false;
        }

        return true;
    }

    protected final void validateParameters(HttpServletRequest request) {

        // NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
        String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};

        String header = null;
        for (String headerName : headers) {
            header = request.getHeader(headerName);
            if (header == null) {
                throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
            }
        }

        String timestampStr = header;
        try {
            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
            // 拒绝过期应答
            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
                throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
            }
        } catch (DateTimeException | NumberFormatException e) {
            throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
        }
    }

    protected final String buildMessage(HttpServletRequest request) throws IOException {
        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
        String nonce = request.getHeader(WECHAT_PAY_NONCE);
        return timestamp + "\n"
                + nonce + "\n"
                + body + "\n";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

对称解密方法

创建 WxPayCallbackUtil 微信支付成功回调类,decryptFromResource用于解密微信

import com.card.config.WxPayConfig;
import com.card.exception.DefaultException;
import com.card.pay.domain.WxchatCallbackRefundData;
import com.card.pay.domain.WxchatCallbackSuccessData;
import com.card.utils.HttpUtils;
import com.card.utils.WechatPayValidatorForRequest;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;


@Slf4j
public class WxPayCallbackUtil {

    /**
     * 对称解密
     */
    private static String decryptFromResource(HashMap<String, Object> bodyMap, WxPayConfig wxPayConfig) {
        // 通知数据
        Map<String, String> resourceMap = (Map) bodyMap.get("resource");
        // 数据密文
        String ciphertext = resourceMap.get("ciphertext");
        // 随机串
        String nonce = resourceMap.get("nonce");
        // 附加数据
        String associateData = resourceMap.get("associated_data");
        AesUtil aesUtil = new AesUtil(wxPayConfig.getKey().getBytes(StandardCharsets.UTF_8));
        try {
            return aesUtil.decryptToString(associateData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
            throw new DefaultException("解密失败");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

封装微信返回的数据对象

微信返回给我们的数据是json格式的,我们需要将其转换成java对象,方便我们调用。

import cn.hutool.core.date.DateUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.util.Date;


/**
 * @author cv大魔王
 * @version 1.0
 * @description 微信支付成功回调返回的数据
 * @date 2022/8/4
 */
@Data
@Slf4j
public class WxchatCallbackSuccessData {

    /**
     * 商户订单号
     */
    private String orderId;

    /**
     * 微信支付系统生成的订单号
     */
    private String transactionId;

    /**
     * 交易状态
     * SUCCESS:支付成功
     * REFUND:转入退款
     * NOTPAY:未支付
     * CLOSED:已关闭
     * REVOKED:已撤销(付款码支付)
     * USERPAYING:用户支付中(付款码支付)
     * PAYERROR:支付失败(其他原因,如银行返回失败)
     */
    private String tradestate;

    /**
     * 支付完成时间
     */
    private Date successTime;

    /**
     * 交易类型
     * JSAPI:公众号支付
     * NATIVE:扫码支付
     * APP:APP支付
     * MICROPAY:付款码支付
     * MWEB:H5支付
     * FACEPAY:刷脸支付
     */
    private String 	tradetype;

    /**
     * 订单总金额
     */
    private BigDecimal totalMoney;


    public Date getSuccessTime() {
        return successTime;
    }

    public void setSuccessTime(String successTime) {
        // Hutool工具包的方法,自动识别一些常用格式的日期字符串
        this.successTime = DateUtil.parse(successTime);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

支付成功回调使用方法

我们先不看回调是如何封装的,先来看使用方法。

@Autowired
private WxPayConfig wxPayConfig;

@Autowired
private Verifier verifier;

@ApiOperation("微信支付回调接口")
@PostMapping("/wx/callback")
public String courseNative(HttpServletRequest request, HttpServletResponse response) {
    return WxPayCallbackUtil.wxPaySuccessCallback(request, response, verifier, wxPayConfig, callbackData -> {
        // TODO 处理你的业务逻辑,下面说一下一般业务逻辑处理方法
        log.info("微信支付返回的信息:{}", callbackData);
        // 1.根据订单id获取订单信息

        // 2.判断金额是否相符,如果不相符则调用退款接口,并取消该订单,通知客户支付金额不符

        // 3.查询订单状态是否是未支付,如果是未支付则改为已支付,填充其他逻辑,

        // 4.如果是其他状态综合你的业务逻辑来处理

        // 5.如果是虚拟物品,则对应充值,等等其他逻辑
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

封装后就这么一句话,就获取到了微信返回给我们的数据,其中callbackData就是上面封装的WxchatCallbackSuccessData对象,我们只需要在回调方法中完成我们的业务逻辑即可。

支付成功回调方法封装

import java.util.function.Consumer;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.card.pay.domain.WxchatCallbackSuccessData;
import com.card.utils.HttpUtils;
import com.card.utils.WechatPayValidatorForRequest;

@Slf4j
public class WxPayCallbackUtil {


   /**
     * 微信支付创建订单回调方法
     * @param verifier 证书
     * @param wxPayConfig 微信配置
     * @param businessCallback 回调方法,用于处理业务逻辑
     * @return json格式的string数据,直接返回给微信
     */
    public static String wxPaySuccessCallback(HttpServletRequest request, HttpServletResponse response, Verifier verifier, WxPayConfig wxPayConfig, Consumer<WxchatCallbackSuccessData> businessCallback) {
        Gson gson = new Gson();

        // 1.处理通知参数
        final String body = HttpUtils.readData(request);
        HashMap<String, Object> bodyMap = gson.fromJson(body, HashMap.class);

        // 2.签名验证
        WechatPayValidatorForRequest wechatForRequest = new WechatPayValidatorForRequest(verifier, body, (String) bodyMap.get("id"));
        try {
            if (!wechatForRequest.validate(request)) {
                // 通知验签失败
                response.setStatus(500);
                final HashMap<String, Object> map = new HashMap<>();
                map.put("code", "ERROR");
                map.put("message", "通知验签失败");
                return gson.toJson(map);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }


        // 3.获取明文数据
        String plainText = decryptFromResource(bodyMap,wxPayConfig);
        HashMap<String,Object> plainTextMap = gson.fromJson(plainText, HashMap.class);
        log.info("plainTextMap:{}",plainTextMap);
        // 4.封装微信返回的数据
        WxchatCallbackSuccessData callbackData = new WxchatCallbackSuccessData();
        callbackData.setSuccessTime(String.valueOf(plainTextMap.get("success_time")));
        callbackData.setOrderId(String.valueOf(plainTextMap.get("out_trade_no")));
        callbackData.setTransactionId(String.valueOf(plainTextMap.get("transaction_id")));
        callbackData.setTradestate(String.valueOf(plainTextMap.get("trade_state")));
        callbackData.setTradetype(String.valueOf(plainTextMap.get("trade_type")));
        String amount = String.valueOf(plainTextMap.get("amount"));
        HashMap<String,Object> amountMap = gson.fromJson(amount, HashMap.class);
        String total = String.valueOf(amountMap.get("total"));
        callbackData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
        log.info("callbackData:{}",callbackData);

        if ("SUCCESS".equals(callbackData.getTradestate())) {
            // 执行业务逻辑
            businessCallback.accept(callbackData);
        }

        // 5.成功应答
        response.setStatus(200);
        final HashMap<String, Object> resultMap = new HashMap<>();
        resultMap.put("code", "SUCCESS");
        resultMap.put("message", "成功");
        return gson.toJson(resultMap);
    }

    /**
     * 对称解密
     */
    private static String decryptFromResource(HashMap<String, Object> bodyMap, WxPayConfig wxPayConfig) {
        // 通知数据
        Map<String, String> resourceMap = (Map) bodyMap.get("resource");
        // 数据密文
        String ciphertext = resourceMap.get("ciphertext");
        // 随机串
        String nonce = resourceMap.get("nonce");
        // 附加数据
        String associateData = resourceMap.get("associated_data");
        AesUtil aesUtil = new AesUtil(wxPayConfig.getKey().getBytes(StandardCharsets.UTF_8));
        try {
            return aesUtil.decryptToString(associateData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
            throw new DefaultException("解密失败");
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

注意看方法的最后一个参数,Consumer<WxchatCallbackSuccessData> businessCallback,这是一个jdk自带的函数式接口,通过接口回调的方式,完善业务逻辑。函数式接口或者回调函数不理解的可以观看jdk自带函数接口以及其系列文章,当然您也可以直接使用。

麻烦点个赞再走吧~~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/467341
推荐阅读
相关标签
  

闽ICP备14008679号