微信V3支付 订单查询 退款查询

本编在对接V3支付的时候连连撞头,希望后来人能少走点弯路,如果有bug请海涵啊,希望各位大佬也能给我点意见,话不多说上代码:

service层

package com.tiyaa.mall.pay.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.squareup.okhttp.HttpUrl;
import com.tiyaa.mall.common.enums.CommonEnum;
import com.tiyaa.mall.common.exceptions.BackendException;
import com.tiyaa.mall.pay.config.WeChatConfig;
import com.tiyaa.mall.pay.config.WeChatPayConfig;
import com.tiyaa.mall.pay.constants.WeChatConstant;
import com.tiyaa.mall.pay.dto.BuyCreateDto;
import com.tiyaa.mall.pay.dto.BuyUrlMapBo;
import com.tiyaa.mall.pay.enums.PayTypeEnum;
import com.tiyaa.mall.pay.enums.WeChatApiTypeEnum;
import com.tiyaa.mall.pay.service.WeChatPayService;
import com.tiyaa.mall.pay.utils.PayUtil;
import com.tiyaa.mall.pay.vo.QueryPayVo;
import com.tiyaa.mall.pay.vo.QueryRefundVo;
import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author xyl
 * @date 2022/8/23 10:33
 * @explain
 */
@Slf4j
@Service
public class WeChatPayServiceImpl extends WeChatConfig implements WeChatPayService {

    @Resource
    private CloseableHttpClient wxPayClient;
    @Autowired
    private ScheduledUpdateCertificatesVerifier verifier;
    @Autowired
    private WeChatPayConfig weChatPayConfig;


    /**
     * 微信下单
     *
     * @param dto
     * @return {@link {@link HashMap < String, String>}}
     * @author xyl
     * @date 2022/7/12 16:22
     * @explain
     */
    @Override
    public HashMap<String, String> buy(BuyCreateDto dto) {
        //下单map
        BuyUrlMapBo bo = getBuyUrlMapBo(dto);
        HashMap<String, Object> buyMap = bo.getBuyMap();
        //支付金额封装map
        HashMap<String, Object> amountMap = new HashMap<>();
        amountMap.put("total", dto.getTotal());
        amountMap.put("currency", "CNY");
        //加入金额
        buyMap.put("amount", amountMap);
        try {
            //生成签名
            Gson gson = new Gson();
            String json = gson.toJson(buyMap);
            HttpUrl httpurl = HttpUrl.parse(domainUrl.concat(WeChatApiTypeEnum.GET_CERTIFICATES.getType()));
            HashMap<String, String> token = getToken(WeChatConstant.GET, httpurl, json);
            HttpPost httpPost = new HttpPost(bo.getUrl());
            StringEntity entity = new StringEntity(json, "utf-8");
            entity.setContentType("application/json");
            httpPost.setEntity(entity);
            httpPost.setHeader("Accept", "application/json");
            CloseableHttpResponse response = wxPayClient.execute(httpPost);
            //响应体
            String bodyAsString = EntityUtils.toString(response.getEntity());
            JSONObject jsonObject = JSON.parseObject(bodyAsString);
            System.out.println("响应体" + bodyAsString);
            //响应状态码
            int statusCode = response.getStatusLine().getStatusCode();
            System.out.println("状态码" + statusCode);
            if (statusCode != 200) {
                throw new BackendException(bodyAsString);
            }
            //封装返回参数
            return getResMap(dto.getPayType(), buyMap, token, jsonObject);
        } catch (Exception e) {
            log.warn("微信支付失败,错误信息:{}", e.getMessage());
            System.out.println(("微信支付失败,错误信息:{}" + e.getMessage()));
            throw new BackendException(CommonEnum.PARAMETER_ERROR);
        }
    }


    private boolean verifiedSign(String serialNo, String signStr, String signature) {
        return verifier.verify(serialNo, signStr.getBytes(StandardCharsets.UTF_8), signature);
    }


    /**
     * 微信下单组装返回
     *
     * @param buyMap     支付map
     * @param token      签名
     * @param jsonObject 下单返回结果
     * @return {@link {@link HashMap< String, String>}}
     * @author xyl
     * @date 2022/7/12 16:05
     * @explain
     */
    private HashMap<String, String> getResMap(Integer payType, HashMap<String, Object> buyMap, HashMap<String, String> token, JSONObject jsonObject) throws InvalidKeyException, SignatureException {
        HashMap<String, String> resMap = new HashMap<>();
        StringBuilder stringBuilder = new StringBuilder();
        String appid = buyMap.get("appid").toString();
        resMap.put("appId", appid);
        stringBuilder.append(appid).append("\n")
                .append(token.get("timestamp")).append("\n")
                .append(token.get("nonce_str")).append("\n");

        if (payType.equals(PayTypeEnum.WX_APP_WECHAT_PAY.getCode()) || payType.equals(PayTypeEnum.WX_H5_WECHAT_PAY.getCode())) {
            resMap.put("package", "prepay_id=" + jsonObject.get("prepay_id").toString());
            stringBuilder.append(resMap.get("package")).append("\n");
        } else {
            resMap.put("package", WeChatConstant.PACKAGE);
            resMap.put("prepayId", jsonObject.get("prepay_id").toString());
            resMap.put("partnerId", token.get("mchid"));
            stringBuilder.append(jsonObject.get("prepay_id").toString()).append("\n");
        }
        resMap.put("nonceStr", token.get("nonce_str"));
        resMap.put("timeStamp", token.get("timestamp"));
        log.warn("签名字符串:" + stringBuilder);
        String sign = sign(stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
        log.warn("获取的签名:" + sign);
        resMap.put("signType", "RSA");
        resMap.put("paySign", sign);
        System.out.println(resMap);
        return resMap;
    }

    /**
     * 构建下单基础信息
     *
     * @param dto
     * @return {@link {@link HashMap< String, Object>}}
     * @author xyl
     * @date 2022/7/12 9:18
     * @explain
     */
    private BuyUrlMapBo getBuyUrlMapBo(BuyCreateDto dto) {
        BuyUrlMapBo bo = new BuyUrlMapBo();
        HashMap<String, Object> buyMap = new HashMap<>();
        //微信小程序
        if (dto.getPayType() == PayTypeEnum.WX_APP_WECHAT_PAY.getCode()) {
            //小程序appId
            buyMap.put(WeChatConstant.APP_ID, smallAppId);
            //用户openId
            HashMap<String, Object> payerMap = new HashMap<>();
            payerMap.put("openid", dto.getOpenId());
            buyMap.put("payer", payerMap);
            //微信api接口
            String url = domainUrl.concat(WeChatApiTypeEnum.JSAPI_PAY.getType());
            bo.setUrl(url);
            //微信app
        } else if (dto.getPayType() == PayTypeEnum.APP_WECHAT_PAY.getCode()) {
            buyMap.put(WeChatConstant.APP_ID, appAppId);
            //微信api接口
            String url = domainUrl.concat(WeChatApiTypeEnum.APP_PAY.getType());
            bo.setUrl(url);
            //微信H5 jsapi支付,本质上不算H5支付,只能公众号内部调用
        } else if (dto.getPayType() == PayTypeEnum.WX_H5_WECHAT_PAY.getCode()) {
            buyMap.put(WeChatConstant.APP_ID, h5AppId);
            //用户openId
            HashMap<String, Object> payerMap = new HashMap<>();
            payerMap.put("openid", dto.getOpenId());
            buyMap.put("payer", payerMap);
            //微信api接口
            String url = domainUrl.concat(WeChatApiTypeEnum.JSAPI_PAY.getType());
            bo.setUrl(url);
        } else {
            log.warn("发起支付失败,支付方式错误,请求参数:{}", dto);
            throw new BackendException(CommonEnum.PARAMETER_ERROR);
        }
        buyMap.put(WeChatConstant.MCH_ID, mchId);
        buyMap.put("description", "商品购买");
        buyMap.put("out_trade_no", dto.getOutTradeNo());
        buyMap.put("notify_url", payNotifyUrl);
        bo.setBuyMap(buyMap);
        return bo;
    }

    /**
     * 获取微信支付平台证书
     *
     * @param method 提交方式
     * @param url    接口
     * @param body   主体
     * @return {@link {@link String}}
     * @author xyl
     * @date 2022/7/12 10:45
     * @explain
     */
    public HashMap<String, String> getToken(String method, HttpUrl url, String body) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
        //随机字符串
        String nonceStr = PayUtil.createNonceStr();
        //当前时间戳
        long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMessage(method, url, timestamp, nonceStr, body);
        String signature = sign(message.getBytes(StandardCharsets.UTF_8));
        HashMap<String, String> map = new HashMap<>();
        map.put("mchid", mchId);
        map.put("nonce_str", nonceStr);
        map.put("timestamp", String.valueOf(timestamp));
        map.put("serial_no", serialNo);
        map.put("signature", signature);
        return map;
    }

    /**
     * 构建消息
     *
     * @param method    方式
     * @param url       请求连接
     * @param timestamp 时间戳
     * @param nonceStr  随机字符串
     * @param body      传递的主体
     * @return {@link {@link String}}
     * @author xyl
     * @date 2022/7/12 10:51
     * @explain
     */
    String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }
        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

    /**
     * 生成签名
     *
     * @param message
     * @return {@link {@link String}}
     * @author xyl
     * @date 2022/7/12 10:47
     * @explain
     */
    String sign(byte[] message) throws InvalidKeyException, SignatureException {
        Signature sign = null;
        try {
            PrivateKey merchantPrivateKey = weChatPayConfig.getPrivateKey();
            sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(merchantPrivateKey);
            try {
                sign.update(message);
            } catch (SignatureException e) {
                e.printStackTrace();
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return Base64.getEncoder().encodeToString(sign.sign());
    }


    /**
     * 微信支付回调
     *
     * @param request  请求体
     * @param response 响应体
     * @return {@link {@link HashMap< String, String>}}
     * @author xyl
     * @date 2022/7/12 16:39
     * @explain
     */
    @Override
    public HashMap<String, String> weChatPayNotify(HttpServletRequest request, HttpServletResponse response) {
        //获取报文
        String body = getRequestBody(request);
        log.info("微信支付回调,获得报文:{}", body);
        //随机串
        String nonceStr = request.getHeader("Wechatpay-Nonce");

        //微信传递过来的签名
        String signature = request.getHeader("Wechatpay-Signature");

        //证书序列号(微信平台)
        String serialNo = request.getHeader("Wechatpay-Serial");

        //时间戳
        String timestamp = request.getHeader("Wechatpay-Timestamp");

        //构造签名串

        //应答时间戳\n
        //应答随机串\n
        //应答报文主体\n
        String signStr = Stream.of(timestamp, nonceStr, body).collect(Collectors.joining("\n", "", "\n"));
        log.warn("微信支付回调,构建签名串:{}", signStr);
        HashMap<String, String> resultMap = new HashMap<>(2);
        try {
            //验证签名是否通过
            boolean result = verifiedSign(serialNo, signStr, signature);
            log.warn("微信支付回调,验证签名结果:{}", result);
            if (result) {
                //解密数据
                String plainBody = decryptBody(body);
                log.warn("微信支付回调,解密后的明文:{}", plainBody);
                Map<String, String> map = convertWeChatPayMsgToMap(plainBody);
                //获取订单号
                String orderNo = map.get(WeChatConstant.OUT_TRADE_NO);
                //处理业务逻辑 TODO
                if (map.get(WeChatConstant.TRADE_STATE).equals(WeChatConstant.SUCCESS)) {
                    //处理业务逻辑 TODO
                }
                //响应微信
                map.put("code", "SUCCESS");
                map.put("message", "成功");
            }

        } catch (Exception e) {
            log.warn("微信支付回调异常:{}", e.getMessage());
        }
        return resultMap;
    }


    /**
     * 解密body的密文
     * <p>
     * "resource": {
     * "original_type": "transaction",
     * "algorithm": "AEAD_AES_256_GCM",
     * "ciphertext": "",
     * "associated_data": "",
     * "nonce": ""
     * }
     *
     * @param body
     * @return {@link {@link String}}
     * @author xyl
     * @date 2022/7/12 16:36
     * @explain
     */
    private String decryptBody(String body) throws GeneralSecurityException {
        AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
        JSONObject object = JSONObject.parseObject(body);
        JSONObject resource = object.getJSONObject("resource");
        String ciphertext = resource.getString("ciphertext");
        String associatedData = resource.getString("associated_data");
        String nonce = resource.getString("nonce");
        return aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);

    }


    /**
     * 转换body为map
     *
     * @param plainBody
     * @return {@link {@link Map< String, String>}}
     * @author xyl
     * @date 2022/7/12 16:35
     * @explain
     */
    private Map<String, String> convertWeChatPayMsgToMap(String plainBody) {

        Map<String, String> paramsMap = new HashMap<>(2);

        JSONObject jsonObject = JSONObject.parseObject(plainBody);

        //商户订单号
        paramsMap.put(WeChatConstant.OUT_TRADE_NO, jsonObject.getString(WeChatConstant.OUT_TRADE_NO));

        //交易状态
        paramsMap.put(WeChatConstant.TRADE_STATE, jsonObject.getString(WeChatConstant.TRADE_STATE));

        //微信交易单号
        paramsMap.put(WeChatConstant.TRANSACTION_ID, jsonObject.getString(WeChatConstant.TRANSACTION_ID));

        //交易金额
        String amount = jsonObject.get("amount").toString();
        JSONObject amountJson = JSONObject.parseObject(amount);
        paramsMap.put(WeChatConstant.TOTAL, amountJson.getString(WeChatConstant.TOTAL));
        log.warn("微信V3支付回调,封装返回参数:{}", paramsMap);
        return paramsMap;

    }


    /**
     * 读取数据流
     *
     * @param request
     * @return {@link {@link String}}
     * @author xyl
     * @date 2022/7/12 16:33
     * @explain
     */
    private String getRequestBody(HttpServletRequest request) {

        StringBuilder sb = new StringBuilder();

        try (ServletInputStream inputStream = request.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        ) {
            String line;

            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }

        } catch (IOException e) {
            log.warn("读取数据流异常:{}", e.getMessage());
        }
        return sb.toString();
    }


    /**
     * 查询订单支付情况
     *
     * @param orderNo
     * @return {@link {@link QueryPayVo}}
     * @author xyl
     * @date 2022/8/24 16:07
     * @explain
     */
    @Override
    public QueryPayVo queryPay(String orderNo) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException, IOException {
        String url = domainUrl.concat(WeChatApiTypeEnum.QUERY_PAY.getType() + orderNo + "?mchid=" + mchId);
        JSONObject json = getHttpResponse(url);
        //交易金额
        String amount = json.get("amount").toString();
        JSONObject amountJson = JSONObject.parseObject(amount);
        return new QueryPayVo(json.get("out_trade_no").toString(), json.get("transaction_id").toString(), json.get("trade_type").toString(), json.get("trade_state").toString(), json.get("trade_state_desc").toString(), json.get("success_time").toString(), amountJson.get("total").toString());
    }

    /**
     * 发起Git请求
     *
     * @param url
     * @return {@link {@link HttpResponse}}
     * @author xyl
     * @date 2022/8/25 9:18
     * @explain
     */
    private JSONObject getHttpResponse(String url) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, IOException {
        HttpClient httpClient = HttpClientBuilder.create().build();
        HttpUrl httpurl = HttpUrl.parse(url);
        String getSign = getGetSign(httpurl);
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("Accept", "application/json");
        httpGet.addHeader("Authorization", getSign);
        HttpResponse execute = httpClient.execute(httpGet);
        String s = EntityUtils.toString(execute.getEntity());
        JSONObject json = JSONObject.parseObject(s);
        int statusCode = execute.getStatusLine().getStatusCode();
        if (statusCode != 200) {
            throw new BackendException(json.get("message").toString());
        }
        return json;
    }

    /**
     * 微信支付GET获取签名
     *
     * @param httpurl 请求URL
     * @return {@link {@link String}}
     * @author xyl
     * @date 2022/8/24 16:07
     * @explain
     */
    public String getGetSign(HttpUrl httpurl) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
        HashMap<String, String> token = getToken(WeChatConstant.GET, httpurl, "");
        return "WECHATPAY2-SHA256-RSA2048 mchid=\"" + token.get("mchid") + "\",nonce_str=\"" + token.get("nonce_str") + "\",signature=\"" + token.get("signature") + "\",timestamp=\"" + token.get("timestamp") + "\",serial_no=\"" + token.get("serial_no") + "\"";
    }


    /**
     * 根据退款订单号查询退款情况
     *
     * @param refundOrderNo
     * @return {@link {@link QueryRefundVo}}
     * @author xyl
     * @date 2022/8/25 10:18
     * @explain
     */
    @Override
    public QueryRefundVo queryRefund(String refundOrderNo) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, IOException {
        String url = domainUrl.concat(WeChatApiTypeEnum.QUERY_REFUND.getType() + refundOrderNo);
        JSONObject json = getHttpResponse(url);
        //交易金额
        String amount = json.get("amount").toString();
        JSONObject amountJson = JSONObject.parseObject(amount);
        String total = amountJson.get("total").toString();
        String refund = amountJson.get("refund").toString();
        return new QueryRefundVo(json.get("refund_id").toString(), json.get("out_refund_no").toString(), json.get("transaction_id").toString(), json.get("out_trade_no").toString(), json.get("channel").toString(), json.get("user_received_account").toString(), json.get("success_time").toString(), json.get("status").toString(), total, refund);
    }



}

 相关的配置:

package com.tiyaa.mall.pay.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * @author xyl
 * @date 2022/8/23 10:04
 * @explain
 */
@Configuration
@Data
public class WeChatConfig {

    /**
     * 小程序
     */
    @Value("${weChat.small.appId}")
    public String smallAppId;
    @Value("${weChat.small.secret}")
    public String smallSecret;

    /**
     * h5 微信内頁jspapi调用
     */
    @Value("${weChat.h5.appId}")
    public String h5AppId;
    @Value("${weChat.h5.secret}")
    public String h5Secret;

    /**
     * app
     */
    @Value("${weChat.app.appId}")
    public String appAppId;
    @Value("${weChat.app.secret}")
    public String appSecret;

    /**
     * 商户号 秘钥key 商户API证书序列号  v3秘钥
     */
    @Value("${weChat.mch.mchId}")
    public String mchId;
    @Value("${weChat.mch.mchKey}")
    public String mchKey;
    @Value("${weChat.mch.serialNo}")
    public String serialNo;
    @Value("${weChat.mch.apiV3Key}")
    public String apiV3Key;


    /**
     * 支付回调 退款回调 微信服务地址
     */
    @Value("${weChat.notifyUrl.payNotifyUrl}")
    public String payNotifyUrl;
    @Value("${weChat.notifyUrl.refundNotifyUrl}")
    public String refundNotifyUrl;
    @Value("${weChat.notifyUrl.domainUrl}")
    public String domainUrl;


    /**
     * 项目
     */
    @Value("${spring.profiles.active}")
    public String active;

}
package com.tiyaa.mall.pay.config;

import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;

/**
 * @author xyl
 * @date 2022/8/23 10:15
 * @explain
 */
@Configuration
public class WeChatPayConfig extends WeChatConfig {

    /**
     * 获取商户的私钥文件
     *
     * @return
     */
    public PrivateKey getPrivateKey() {
        InputStream resourceAsStream = this.getClass().getResourceAsStream("/cert/apiclient_key.pem");
        return PemUtil.loadPrivateKey(resourceAsStream);
    }

    /**
     * 获取签名验证器
     *
     * @return
     */
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier() {
        //获取商户私钥
        PrivateKey privateKey = getPrivateKey();
        System.out.println(privateKey);
        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);
        //身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
        // 使用定时更新的签名验证器,不需要传入证书
        return new ScheduledUpdateCertificatesVerifier(
                wechatPay2Credentials,
                apiV3Key.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 获取http请求对象
     *
     * @param verifier
     * @return
     */
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {
        //获取商户私钥
        PrivateKey privateKey = getPrivateKey();
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, serialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

        // 通过WeChatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        return builder.build();
    }

    /**
     * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
     */
    @Bean(name = "wxPayNoSignClient")
    public CloseableHttpClient getWxPayNoSignClient() {
        //获取商户私钥
        PrivateKey privateKey = getPrivateKey();
        //用于构造HttpClient
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                //设置商户信息
                .withMerchant(mchId, serialNo, privateKey)
                //无需进行签名验证、通过withValidator((response) -> true)实现
                .withValidator((response) -> true);

        // 通过WeChatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        return builder.build();
    }
}

DTO:

package com.tiyaa.mall.pay.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author xyl
 * @date 2022/7/11 17:16
 * @explain
 */
@Data
@ApiModel("下单dto")
public class BuyCreateDto {
    @ApiModelProperty(value = "商户订单号(自己的订单号)")
    private String outTradeNo;
    @ApiModelProperty(value = "支付的类型字典11")
    private Integer payType;
    @ApiModelProperty(value = "支付金额")
    private Long total;
    @ApiModelProperty(value = "用户OpenId")
    private String openId;


    public BuyCreateDto() {
    }

    public BuyCreateDto(String outTradeNo, Integer payType, Long total, String openId) {
        this.outTradeNo = outTradeNo;
        this.payType = payType;
        this.total = total;
        this.openId = openId;
    }
}
package com.tiyaa.mall.pay.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.HashMap;

/**
 * @author xyl
 * @date 2022/7/12 13:13
 * @explain
 */
@Data
@ApiModel("获取URL和构建支付mapBo")
public class BuyUrlMapBo {
    @ApiModelProperty(value = "微信支付API接口")
    private String url;
    @ApiModelProperty(value = "构建的支付信息map")
    private HashMap<String, Object> buyMap;
}

枚举:

package com.tiyaa.mall.pay.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author xyl
 * @date 2022/7/12 9:05
 * @explain
 */
@AllArgsConstructor
@Getter
public enum WeChatApiTypeEnum {

    /**
     * 获取商户平台证书的URL
     */
    GET_CERTIFICATES("/v3/certificates"),

    /**
     * JSAPI下单
     */
    JSAPI_PAY("/v3/pay/transactions/jsapi"),

    /**
     * APP下单
     */
    APP_PAY("/v3/pay/transactions/app"),

    /**
     * H5下单
     */
    H5_PAY("/v3/pay/transactions/h5"),

    /**
     * 订单查询
     */
    QUERY_PAY("/v3/pay/transactions/out-trade-no/"),

    /**
     * 订单查询
     */
    QUERY_REFUND("/v3/refund/domestic/refunds/");


    /**
     * 类型
     */
    private final String type;
}
package com.tiyaa.mall.pay.enums;

/**
 * @author chenbinbin
 * @date 2022-03-25 9:25
 * 支付类型枚举类
 */
public enum PayTypeEnum {

    /**
     * @author chenbinbin
     * @date 2022-03-25 9:25
     * 支付类型枚举类
     */
    WX_APP_WECHAT_PAY(0, "小程序微信支付"),
    WX_H5_WECHAT_PAY(1, "公众号微信支付"),
    H5_ALIPAY(2, "H5支付宝支付"),
    APP_WECHAT_PAY(3, "APP微信支付"),
    APP_ALIPAY(4, "APP支付宝支付"),
    USER_BALANCE_PAY(5, "余额支付"),
    BROWSER_H5_WECHAT_PAY(6, "外部浏览器H5微信支付"),
    ;
    private int code;

    private String msg;

    PayTypeEnum() {
    }

    PayTypeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

vo:

package com.tiyaa.mall.pay.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author xyl
 * @date 2022/8/24 15:50
 * @explain
 */
@Data
@ApiModel("支付查询返回Vo")
public class QueryPayVo {
    @ApiModelProperty(value = "订单号")
    private String orderNo;
    @ApiModelProperty(value = "微信订单号")
    private String transactionId;
    @ApiModelProperty(value = "交易类型,枚举值:\n" +
            "JSAPI:公众号支付\n" +
            "NATIVE:扫码支付\n" +
            "APP:APP支付\n" +
            "MICROPAY:付款码支付\n" +
            "MWEB:H5支付\n" +
            "FACEPAY:刷脸支付")
    private String tradeType;
    @ApiModelProperty(value = "交易状态,枚举值:\n" +
            "SUCCESS:支付成功\n" +
            "REFUND:转入退款\n" +
            "NOTPAY:未支付\n" +
            "CLOSED:已关闭\n" +
            "REVOKED:已撤销(仅付款码支付会返回)\n" +
            "USERPAYING:用户支付中(仅付款码支付会返回)\n" +
            "PAYERROR:支付失败(仅付款码支付会返回)\n" +
            "示例值:SUCCESS")
    private String tradeState;
    @ApiModelProperty(value = "交易状态描述\n" +
            "示例值:支付成功")
    private String tradeStateDesc;
    @ApiModelProperty(value = "支付完成时间,遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE,yyyy-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。\n" +
            "示例值:2018-06-08T10:34:56+08:00")
    private String successTime;
    @ApiModelProperty(value = "\t订单总金额,单位为分。\n" +
            "示例值:100")
    private String total;

    public QueryPayVo(String orderNo, String transactionId, String tradeType, String tradeState, String tradeStateDesc, String successTime, String total) {
        this.orderNo = orderNo;
        this.transactionId = transactionId;
        this.tradeType = tradeType;
        this.tradeState = tradeState;
        this.tradeStateDesc = tradeStateDesc;
        this.successTime = successTime;
        this.total = total;
    }
}
package com.tiyaa.mall.pay.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author xyl
 * @date 2022/8/25 9:26
 * @explain
 */
@Data
@ApiModel("微信查询退款Vo")
public class QueryRefundVo {

    @ApiModelProperty(value = "微信支付退款单号\n" +
            "示例值:50000000382019052709732678859")
    private String refundId;
    @ApiModelProperty(value = "商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。\n" +
            "示例值:1217752501201407033233368018")
    private String outRefundNo;
    @ApiModelProperty(value = "微信支付交易订单号\n" +
            "示例值:1217752501201407033233368018")
    private String transactionId;
    @ApiModelProperty(value = "原支付交易对应的商户订单号\n" +
            "示例值:1217752501201407033233368018")
    private String outTradeNo;
    @ApiModelProperty(value = "\t枚举值:\n" +
            "ORIGINAL:原路退款\n" +
            "BALANCE:退回到余额\n" +
            "OTHER_BALANCE:原账户异常退到其他余额账户\n" +
            "OTHER_BANKCARD:原银行卡异常退到其他银行卡\n" +
            "示例值:ORIGINAL")
    private String channel;
    @ApiModelProperty(value = "取当前退款单的退款入账方,有以下几种情况:\n" +
            "1)退回银行卡:{银行名称}{卡类型}{卡尾号}\n" +
            "2)退回支付用户零钱:支付用户零钱\n" +
            "3)退还商户:商户基本账户商户结算银行账户\n" +
            "4)退回支付用户零钱通:支付用户零钱通\n" +
            "示例值:招商银行信用卡0403")
    private String userReceivedAccount;
    @ApiModelProperty(value = "退款成功时间,当退款状态为退款成功时有返回。\n" +
            "示例值:2020-12-01T16:18:12+08:00")
    private String successTime;
    @ApiModelProperty(value = "款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款。\n" +
            "枚举值:\n" +
            "SUCCESS:退款成功\n" +
            "CLOSED:退款关闭\n" +
            "PROCESSING:退款处理中\n" +
            "ABNORMAL:退款异常\n" +
            "示例值:SUCCESS")
    private String status;
    @ApiModelProperty(value = "订单总金额,单位为分\n" +
            "示例值:100")
    private String total;
    @ApiModelProperty(value = "退款标价金额,单位为分,可以做部分退款\n" +
            "示例值:100")
    private String refund;


    public QueryRefundVo(String refundId, String outRefundNo, String transactionId, String outTradeNo, String channel, String userReceivedAccount, String successTime, String status, String total, String refund) {
        this.refundId = refundId;
        this.outRefundNo = outRefundNo;
        this.transactionId = transactionId;
        this.outTradeNo = outTradeNo;
        this.channel = channel;
        this.userReceivedAccount = userReceivedAccount;
        this.successTime = successTime;
        this.status = status;
        this.total = total;
        this.refund = refund;
    }
}

因为阿里代码规范,还设置了点常量

package com.tiyaa.mall.pay.constants;

import org.springframework.stereotype.Component;

/**
 * 微信支付支付常量
 *
 * @author xyl
 * @date 2022/8/23 14:40
 * @explain
 */
public class WeChatConstant {

    public static final String PROD = "prod";
    public static final String APP_ID = "appid";
    public static final String MCH_ID = "mchid";
    public static final String GET = "GET";
    public static final String PACKAGE = "Sign=WXPay";
    public static final String SUCCESS = "SUCCESS";
    public static final String TRADE_STATE = "trade_state";
    public static final String OUT_TRADE_NO = "out_trade_no";
    public static final String ACCOUNT_NO = "account_no";
    public static final String TRANSACTION_ID = "transaction_id";
    public static final String TOTAL = "total";

}

当前支付 和 退款查询 因为小编被安排了其他事情,对接支付后续在更新

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
微信支付v3接口退款是指商户在使用微信支付接口完成支付后,需要对订单进行退款操作。在PHP语言中,可以通过以下步骤实现微信支付V3接口退款。 首先,商户需要在微信商户平台上创建退款证书,可以从微信商务平台下载到API证书。 接下来,商户需要使用PHP调用微信支付v3接口,发送退款请求。可以使用curl库来发送HTTP请求。在请求中,需要设置退款的相关参数,包括商户订单号、退款金额、退款原因、退款结果通知地址等。另外,还需要设置HTTP头部,包括商户证书信息,用于进行身份验证。 然后,商户需要对请求的数据进行签名,并将签名结果作为请求体的一部分。 接下来,使用curl库发送HTTP请求,将请求体和HTTP头部发送给微信支付服务器。 微信支付服务器接收到请求后,对请求进行验证和解析。验证通过后,微信支付服务器会返回退款结果给商户服务器。商户服务器接收到退款结果后,可以根据返回的结果进行相应的处理,如更新订单状态等。 最后,商户服务器需要对返回结果进行处理和存储,以便日后查询和对账。 需要注意的是,商户在使用微信支付v3接口退款时,需要确保请求的安全性,保护商户和客户的信息。在退款时,商户还需要根据微信支付接口文档的要求,处理返回结果,以保证退款操作的准确性和可靠性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值