【新版】微信支付V3-商家打款(java版本)

 1.支付配置

注意:
(1)微信商户平台必须开通【商家打款】功能
(2)该代码仅支持2025.1.15后创建的商户号
(3)只支持商户公钥验签模式

文件 application.properties新增以下配置

wxpay.appId=XXXXXXXXXXXXXXXXXX
wxpay.secret=XXXXXXXXXXXXXXXXXX
wxpay.mchId=XXXXXXXXXXXXXXXXXX
wxpay.transferSceneId=XXXXXXXXXXXXXXXXXX
wxpay.privateKeyFromPath=XXXXXXXXXXXXXXXXXX
wxpay.platformCertPath=XXXXXXXXXXXXXXXXXX
wxpay.publicKeyFromPath=XXXXXXXXXXXXXXXXXX
wxpay.publicKeyId=XXXXXXXXXXXXXXXXXX
wxpay.merchantSerialNumber=XXXXXXXXXXXXXXXXXX
wxpay.apiV3Key=XXXXXXXXXXXXXXXXXX
wxpay.transferUrl=XXXXXXXXXXXXXXXXXX
wxpay.transferNotifyUrl=XXXXXXXXXXXXXXXXXX

2.业务代码实现

<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>6.0.0</version> <!-- Jakarta EE 10 对应的版本 -->
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version>
</dependency>

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.9</version>
</dependency>

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.22</version>
</dependency>

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

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

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

<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.4.1</version>
</dependency>

<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.4.1</version>
</dependency>
import cn.hutool.core.io.IoUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.gson.Gson;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAPublicKeyConfig;
import com.wechat.pay.java.core.cipher.PrivacyDecryptor;
import com.wechat.pay.java.core.http.*;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RSAPublicKeyNotificationConfig;
import com.wechat.pay.java.core.notification.RequestParam;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestBody;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
@RestController
@RequestMapping("/wxPay")
public class WxPayController {

    @Value("${wxpay.appId}")
    private String appId;

    @Value("${wxpay.secret}")
    private String secret;

    @Value("${wxpay.mchId}")
    private String mchId;

    @Value("${wxpay.transferSceneId}")
    private String transferSceneId;

    @Value("${wxpay.privateKeyFromPath}")
    private String privateKeyFromPath;

    @Value("${wxpay.publicKeyFromPath}")
    private String publicKeyFromPath;

    @Value("${wxpay.publicKeyId}")
    private String publicKeyId;

    @Value("${wxpay.merchantSerialNumber}")
    private String merchantSerialNumber;

    @Value("${wxpay.apiV3Key}")
    private String apiV3Key;

    @Value("${wxpay.transferUrl}")
    private String transferUrl;

    @Value("${wxpay.transferNotifyUrl}")
    private String transferNotifyUrl;

    /**
     * 微信商家打款
     */
    @PostMapping("/merchantPayout")
    public R<String> merchantPayout(@RequestBody WxPayTranferReq wxPayTranferReq) {
        log.info("【微信商家打款】入参:{}", JSON.toJSONString(wxPayTranferReq));
        // 商家转账
        InitiateBatchTransferRequestNew request = new InitiateBatchTransferRequestNew();
        request.setAppid(appId);
        String outBillNo = wxPayTranferReq.getOutBillNo();
        if (StringUtils.isEmpty(wxPayTranferReq.getOutBillNo())) {
            outBillNo = generateOutBillNo();
        } else {
            // 实时查询订单状态
            TransferDetailEntityNew transferDetailEntityNew = queryOrderDetail(outBillNo);
            if (!transferDetailEntityNew.getState().equals("FAIL") && !transferDetailEntityNew.getState().equals(
                    "CANCELLED")) {
                InitiateBatchTransferResponseNew transferDetailEntity = new InitiateBatchTransferResponseNew();
                transferDetailEntity.setState(transferDetailEntityNew.getState());
                String stateMsg = handleTransferStatus(transferDetailEntity);
                return R.fail(stateMsg);
            } else {
                // 转账失败或转账撤销完成可重新生成订单号
                outBillNo = generateOutBillNo();
            }
        }
        request.setOutBillNo(outBillNo);
        request.setTransferSceneId(transferSceneId);
        request.setOpenid(wxPayTranferReq.getOpenId());
        try {
            double transferAmount = Double.parseDouble(wxPayTranferReq.getTransferAmount());
            // 转换为分
            long amountInCents = Math.round(transferAmount * 100);
            request.setTransferAmount((int) amountInCents);
            // 检查转账金额是否大于等于2,000元
            if (amountInCents >= 200000) {
                if (wxPayTranferReq.getUserName() == null || wxPayTranferReq.getUserName().trim().isEmpty()) {
                    return R.fail("转账金额大于等于2,000元时,必须填写收款用户姓名");
                }
            }
            request.setUserName(wxPayTranferReq.getUserName());
        } catch (NumberFormatException e) {
            log.error("【微信商家打款】金额格式错误: {}", wxPayTranferReq.getTransferAmount(), e);
            return R.fail("金额格式错误");
        }
        request.setTransferRemark(wxPayTranferReq.getTransferRemark());
        request.setNotifyUrl(transferNotifyUrl);
        // 转账场景报备信息 佣金的固定类型
        List<TransferSceneReportInfoNew> transferDetailList = new ArrayList<>();
        TransferSceneReportInfoNew transferSceneReportInfoNew = new TransferSceneReportInfoNew();
        transferSceneReportInfoNew.setInfoType("活动名称");
        transferSceneReportInfoNew.setInfoContent("活动");
        transferDetailList.add(transferSceneReportInfoNew);
        TransferSceneReportInfoNew transferSceneReportInfoNew1 = new TransferSceneReportInfoNew();
        transferSceneReportInfoNew1.setInfoType("奖励说明");
        transferSceneReportInfoNew1.setInfoContent("说明");
        transferDetailList.add(transferSceneReportInfoNew1);
        request.setTransferSceneReportInfos(transferDetailList);
        // 微信商家转账
        log.info("【微信商家打款】入参:{}", JSON.toJSONString(request));
        Config config =
                new RSAPublicKeyConfig.Builder()
                        .merchantId(mchId)
                        .privateKeyFromPath(privateKeyFromPath)
                        .publicKeyFromPath(publicKeyFromPath)
                        .publicKeyId(publicKeyId)
                        .merchantSerialNumber(merchantSerialNumber)
                        .apiV3Key(apiV3Key)
                        .build();
        String encryptName = config.createEncryptor().encrypt(request.getUserName());
        request.setUserName(encryptName);
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader("Accept", MediaType.APPLICATION_JSON.getValue());
        headers.addHeader("Content-Type", MediaType.APPLICATION_JSON.getValue());
        headers.addHeader("Wechatpay-Serial", config.createEncryptor().getWechatpaySerial());
        // 请求体
        JsonRequestBody body = new JsonRequestBody.Builder().body(new Gson().toJson(request)).build();
        log.info("【微信商家打款】请求体:{}", new Gson().toJson(request));
        HttpRequest httpRequest =
                new HttpRequest.Builder()
                        .httpMethod(HttpMethod.POST)
                        .url(transferUrl)
                        .headers(headers)
                        .body(body)
                        .build();
        HttpClient httpClient = new DefaultHttpClientBuilder().config(config).build();
        HttpResponse<InitiateBatchTransferResponseNew> httpResponse = httpClient.execute(httpRequest,
                InitiateBatchTransferResponseNew.class);
        log.info("【微信商家打款】返回结果:{}", httpResponse.getServiceResponse());
        InitiateBatchTransferResponseNew serviceResponse = httpResponse.getServiceResponse();
        log.info("【微信商家打款-更新平台订单状态和流水号,跳转领取页面的package信息】 开始..............");
        // TODO 更新平台订单状态和流水号

        log.info("【微信商家打款-更新平台订单状态和流水号,跳转领取页面的package信息】 结束..............");
        return R.ok(serviceResponse.toString());
    }

    /**
     * 微信商家打款-回调通知
     */
    @PostMapping("/transferNotify")
    public ResponseEntity<Map<String, String>> wxPayCallback(HttpServletRequest request) {
        Map<String, String> errMap = new HashMap<>();
        try {
            log.info("进入.....【微信商家打款-回调通知】");

            // 获取请求体
            String requestBody = getBodyString(request, "UTF-8");
            // 证书序列号(微信平台)   验签的“微信支付平台证书”所对应的平台证书序列号
            String wechatPaySerial = request.getHeader("Wechatpay-Serial");
            // 微信传递过来的签名   验签的签名值
            String wechatSignature = request.getHeader("Wechatpay-Signature");
            // 验签的时间戳
            String wechatTimestamp = request.getHeader("Wechatpay-Timestamp");
            // 验签的随机字符串
            String wechatpayNonce = request.getHeader("Wechatpay-Nonce");

            log.info("收到【微信商家打款-回调通知】请求数据:======= wechatPaySerial is {} ," +
                            " wechatSignature is {} , " +
                            "wechatTimestamp is {} , " +
                            "wechatpayNonce  is {}, requestBody is {}",
                    wechatPaySerial, wechatSignature, wechatTimestamp, wechatpayNonce, requestBody);

            // 构造 RequestParam
            RequestParam requestParam = new RequestParam.Builder()
                    .serialNumber(wechatPaySerial)
                    .nonce(wechatpayNonce)
                    .signature(wechatSignature)
                    .timestamp(wechatTimestamp)
                    .body(requestBody)
                    .build();

            // 构建 RSAPublicKeyNotificationConfig
            RSAPublicKeyNotificationConfig config = new RSAPublicKeyNotificationConfig.Builder()
                    .publicKeyFromPath(publicKeyFromPath) // 从文件路径加载公钥
                    .publicKeyId(publicKeyId)        // 设置公钥ID
                    .apiV3Key(apiV3Key)              // 设置API v3密钥
                    .build();

            log.info("【微信商家打款-回调通知】-构建Config 参数:==========wechatPaySerial is {} ," +
                            " wechatSignature is {} , " +
                            "wechatTimestamp is {} , " +
                            "wechatpayNonce  is {}, requestBody is {}",
                    wechatPaySerial, wechatSignature, wechatTimestamp, wechatpayNonce, requestBody);

            // 初始化 NotificationParser
            NotificationParser parser = new NotificationParser(config);
            log.info("【微信商家打款-回调通知】-构建Parser:" + parser);

            try {
                // 解析回调通知
                TransferDetailEntityNew transferDetailEntityNew = parser.parse(requestParam,
                        TransferDetailEntityNew.class);
                log.info("【微信商家打款-回调通知】返回结果: {}", transferDetailEntityNew != null ?
                        JSON.toJSONString(transferDetailEntityNew) : null);

                assert transferDetailEntityNew != null;
                if (transferDetailEntityNew.getOutBillNo() != null) {
                    // TODO  转账成功更新业务状态以及订单状态

                }
                // 返回成功响应
                Map<String, String> resMap = JSON.parseObject(JSON.toJSONString(transferDetailEntityNew),
                        new TypeReference<>() {
                        });
                return new ResponseEntity<>(resMap, HttpStatus.OK);
            } catch (Exception e) {
                log.error("【微信商家打款-回调通知】处理时发生异常:", e);
                throw new RuntimeException(e);
            }
        } catch (Exception e) {
            errMap.put("code", "FAIL");
            errMap.put("message", e.getMessage());
            return new ResponseEntity<>(errMap, HttpStatus.BAD_REQUEST);
        }
    }

    /**
     * 查询账单
     */
    @GetMapping("/queryTransferDetail")
    public R<TransferDetailEntityNew> queryTransferDetail(String outBillNo) {
        return R.ok(queryOrderDetail(outBillNo));
    }

    /**
     * 查询账单
     */
    public TransferDetailEntityNew queryOrderDetail(String outBillNo) {
        log.info("【商户单号查询转账单】入参: {}", outBillNo);
        Config config =
                new RSAPublicKeyConfig.Builder()
                        .merchantId(mchId)
                        .privateKeyFromPath(privateKeyFromPath)
                        .publicKeyFromPath(publicKeyFromPath)
                        .publicKeyId(publicKeyId)
                        .merchantSerialNumber(merchantSerialNumber)
                        .apiV3Key(apiV3Key)
                        .build();
        String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no" +
                "/{out_bill_no}";
        requestPath = requestPath.replace("{out_bill_no}", UrlEncoder.urlEncode(outBillNo));
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader("Accept", MediaType.APPLICATION_JSON.getValue());
        headers.addHeader("Content-Type", MediaType.APPLICATION_JSON.getValue());
        HttpRequest httpRequest =
                new HttpRequest.Builder()
                        .httpMethod(HttpMethod.GET)
                        .url(requestPath)
                        .headers(headers)
                        .build();
        PrivacyDecryptor decryptor = config.createDecryptor();
        HttpClient httpClient = new DefaultHttpClientBuilder().config(config).build();
        HttpResponse<TransferDetailEntityNew> httpResponse = httpClient.execute(httpRequest,
                TransferDetailEntityNew.class);
        log.info("【商户单号查询转账单】返回: {}", httpResponse.getServiceResponse());
        return httpResponse.getServiceResponse().cloneWithCipher(decryptor);
    }

    /**
     * 根据转账单状态返回提示信息(固定描述)
     */
    public String handleTransferStatus(InitiateBatchTransferResponseNew transferDetailEntity) {
        if (transferDetailEntity == null) {
            return "转账单详情为空,无法处理";
        }
        String state = transferDetailEntity.getState();
        return switch (state) {
            case "ACCEPTED" -> "转账已受理,无需再次发起";
            case "PROCESSING" ->
                    "转账锁定资金中。如果一直停留在该状态,建议检查账户余额是否足够,如余额不足,可充值后再原单重试。";
            case "WAIT_USER_CONFIRM" -> "待收款用户确认";
            case "TRANSFERING" -> "转账中,可拉起微信收款确认页面再次重试确认收款";
            case "SUCCESS" -> "转账成功,无需再次发起";
            case "FAIL" -> "转账失败";
            case "CANCELING" -> "订单正在处理取消,请稍后再试";
            case "CANCELLED" -> "转账撤销完成";
            default -> "未知状态:" + state + ",请检查转账单状态";
        };
    }

    /**
     * 获取post请求中的Body
     */
    public static String getBodyString(HttpServletRequest request, String charSet) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            // 读取流并将流写出去,避免数据流中断;
            reader = new BufferedReader(new InputStreamReader(inputStream, charSet));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("获取requestBody异常:", e);
        } finally {
            IoUtil.close(inputStream);
            IoUtil.close(reader);
        }
        return sb.toString();
    }

    /**
     * 生成商户单号
     */
    public static String generateOutBillNo() {
        // 获取当前时间戳(精确到毫秒)
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        // 格式化时间戳
        String timestamp = dateFormat.format(new Date());
        // 生成随机数(6位随机数)
        Random random = new Random();
        // 生成0到999999之间的随机数
        int randomNum = random.nextInt(999999);
        // 补足6位
        String randomStr = String.format("%06d", randomNum);
        // 组合时间戳和随机数
        return timestamp + randomStr;
    }

}
/**
 * 返回状态码
 */
public class HttpStatus {

    /**
     * 操作成功
     */
    public static final int SUCCESS = 200;

    /**
     * 对象创建成功
     */
    public static final int CREATED = 201;

    /**
     * 请求已经被接受
     */
    public static final int ACCEPTED = 202;

    /**
     * 操作已经执行成功,但是没有返回数据
     */
    public static final int NO_CONTENT = 204;

    /**
     * 资源已被移除
     */
    public static final int MOVED_PERM = 301;

    /**
     * 重定向
     */
    public static final int SEE_OTHER = 303;

    /**
     * 资源没有被修改
     */
    public static final int NOT_MODIFIED = 304;

    /**
     * 参数列表错误(缺少,格式不匹配)
     */
    public static final int BAD_REQUEST = 400;

    /**
     * 未授权
     */
    public static final int UNAUTHORIZED = 401;

    /**
     * 访问受限,授权过期
     */
    public static final int FORBIDDEN = 403;

    /**
     * 资源,服务未找到
     */
    public static final int NOT_FOUND = 404;

    /**
     * 不允许的http方法
     */
    public static final int BAD_METHOD = 405;

    /**
     * 资源冲突,或者资源被锁
     */
    public static final int CONFLICT = 409;

    /**
     * 不支持的数据,媒体类型
     */
    public static final int UNSUPPORTED_TYPE = 415;

    /**
     * 系统内部错误
     */
    public static final int ERROR = 500;

    /**
     * 接口未实现
     */
    public static final int NOT_IMPLEMENTED = 501;

    /**
     * 系统警告消息
     */
    public static final int WARN = 601;
}


import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 商家零钱转账自定义请求体
 */
@Setter
@Getter
public class InitiateBatchTransferRequestNew {

    /** 商户appid Y 说明:申请商户号的appid或商户号绑定的appid(企业号corpid即为此appid) */
    @SerializedName("appid")
    private String appid;

    /** 商户单号 Y 说明:商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一 */
    @SerializedName("out_bill_no")
    private String outBillNo;

    /** 转账场景ID Y 说明:该笔转账使用的转账场景,可前往“商户平台-产品中心-商家转账”中申请。如:1001-现金营销 */
    @SerializedName("transfer_scene_id")
    private String transferSceneId;

    /** 收款用户OpenID Y 说明:商户AppID下,某用户的OpenID  */
    @SerializedName("openid")
    private String openid;

    /** 收款用户姓名 N 说明:收款方真实姓名。需要加密传入,支持标准RSA算法和国密算法,公钥由微信侧提供。
     转账金额 >= 2,000元时,该笔明细必须填写
     若商户传入收款用户姓名,微信支付会校验收款用户与输入姓名是否一致,并提供电子回单 */
    @SerializedName("user_name")
    private String userName;

    /** 转账金额 Y 说明:转账金额单位为“分”。*/
    @SerializedName("transfer_amount")
    private Integer transferAmount;

    /** 转账备注 Y 说明:转账备注,用户收款时可见该备注信息,UTF8编码,最多允许32个字符。*/
    @SerializedName("transfer_remark")
    private String transferRemark;

    /** 通知地址 N 说明:异步接收微信支付结果通知的回调地址,通知url必须为公网可访问的url,必须为https,不能携带参数。 */
    @SerializedName("notify_url")
    private String notifyUrl;

    /** 转账场景报备信息 Y 说明:各转账场景下需报备的内容,可通过 产品文档 了解 */
    @SerializedName("transfer_scene_report_infos")
    private List<TransferSceneReportInfoNew> transferSceneReportInfos = new ArrayList<>();

    public InitiateBatchTransferRequestNew() {
        super();
    }

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) return false;
        InitiateBatchTransferRequestNew that = (InitiateBatchTransferRequestNew) o;
        return Objects.equals(appid, that.appid) && Objects.equals(outBillNo, that.outBillNo) && Objects.equals(transferSceneId, that.transferSceneId) && Objects.equals(openid, that.openid) && Objects.equals(userName, that.userName) && Objects.equals(transferAmount, that.transferAmount) && Objects.equals(transferRemark, that.transferRemark) && Objects.equals(notifyUrl, that.notifyUrl) && Objects.equals(transferSceneReportInfos, that.transferSceneReportInfos);
    }

    @Override
    public int hashCode() {
        return Objects.hash(appid, outBillNo, transferSceneId, openid, userName, transferAmount, transferRemark,
                notifyUrl, transferSceneReportInfos);
    }

    @Override
    public String toString() {
        return "InitiateBatchTransferRequestNew{" +
                "appid='" + appid + '\'' +
                ", outBillNo='" + outBillNo + '\'' +
                ", transferSceneId='" + transferSceneId + '\'' +
                ", openid='" + openid + '\'' +
                ", userName='" + userName + '\'' +
                ", transferAmount=" + transferAmount +
                ", transferRemark='" + transferRemark + '\'' +
                ", notifyUrl='" + notifyUrl + '\'' +
                ", transferSceneReportInfos=" + transferSceneReportInfos +
                '}';
    }
}

import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;
import java.util.Objects;

/**
 * 商家零钱转账自定义返回体
 */
@Setter
@Getter
public class InitiateBatchTransferResponseNew {

    /** 商户单号 Y 说明:商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一 */
    @SerializedName("out_bill_no")
    private String outBillNo;

    /** 微信转账单号 Y 说明:微信转账单号,微信商家转账系统返回的唯一标识 */
    @SerializedName("transfer_bill_no")
    private String transferBillNo;

    /** 单据创建时间 Y 说明:单据受理成功时返回,按照使用rfc3339所定义的格式,格式为yyyy-MM-DDThh:mm:ss+TIMEZONE  */
    @SerializedName("create_time")
    private String createTime;

    /** 收单据状态 Y 说明:商家转账订单状态 */
    @SerializedName("state")
    private String state;

    /** 失败原因 Y 说明:订单已失败或者已退资金时,返回失败原因“分”。*/
    @SerializedName("fail_reason")
    private String failReason;

    /** 跳转领取页面的package信息 Y 说明:跳转微信支付收款页的package信息,APP调起用户确认收款或JSAPI调起用户确认收款需要使用的参数。*/
    @SerializedName("package_info")
    private String packageInfo;

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) return false;
        InitiateBatchTransferResponseNew that = (InitiateBatchTransferResponseNew) o;
        return Objects.equals(outBillNo, that.outBillNo) && Objects.equals(transferBillNo, that.transferBillNo) && Objects.equals(createTime, that.createTime) && Objects.equals(state, that.state) && Objects.equals(failReason, that.failReason) && Objects.equals(packageInfo, that.packageInfo);
    }

    @Override
    public int hashCode() {
        return Objects.hash(outBillNo, transferBillNo, createTime, state, failReason, packageInfo);
    }

    @Override
    public String toString() {
        return "{" +
                "outBillNo='" + outBillNo + '\'' +
                ", transferBillNo='" + transferBillNo + '\'' +
                ", createTime='" + createTime + '\'' +
                ", state='" + state + '\'' +
                ", failReason=" + failReason +
                ", packageInfo='" + packageInfo + '\'' +
                '}';
    }
}

import lombok.Data;

@Data
public class IWxPayParamVO {

    // rowId(行数据唯一id)
    private String rowId;

    // 微信用户openId(必填)
    private String openId;

    // 转账金额(必填)
    private String transferAmount;

    // 转账备注
    private String transferRemark;

}

import java.io.Serial;
import java.io.Serializable;

/**
 * 响应信息主体
 */
public class R<T> implements Serializable {

    @Serial
    private static final long serialVersionUID = 1L;

    /** 成功 */
    public static final int SUCCESS = HttpStatus.SUCCESS;

    /** 失败 */
    public static final int FAIL = HttpStatus.ERROR;

    private int code;

    private String msg;

    private T data;

    public static <T> R<T> ok() {
        return restResult(null, SUCCESS, "操作成功");
    }

    public static <T> R<T> ok(T data) {
        return restResult(data, SUCCESS, "操作成功");
    }

    public static <T> R<T> ok(T data, String msg) {
        return restResult(data, SUCCESS, msg);
    }

    public static <T> R<T> fail() {
        return restResult(null, FAIL, "操作失败");
    }

    public static <T> R<T> fail(String msg) {
        return restResult(null, FAIL, msg);
    }

    public static <T> R<T> fail(T data) {
        return restResult(data, FAIL, "操作失败");
    }

    public static <T> R<T> fail(T data, String msg) {
        return restResult(data, FAIL, msg);
    }

    public static <T> R<T> fail(int code, String msg) {
        return restResult(null, code, msg);
    }

    private static <T> R<T> restResult(T data, int code, String msg) {
        R<T> apiResult = new R<>();
        apiResult.setCode(code);
        apiResult.setData(data);
        apiResult.setMsg(msg);
        return apiResult;
    }

    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;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static <T> Boolean isError(R<T> ret) {
        return !isSuccess(ret);
    }

    public static <T> Boolean isSuccess(R<T> ret) {
        return R.SUCCESS == ret.getCode();
    }
}

import com.google.gson.annotations.SerializedName;
import com.wechat.pay.java.core.cipher.PrivacyDecryptor;
import lombok.Getter;
import lombok.Setter;
import java.util.Objects;

/**
 * 商户单号查询转账单实体类信息
 */
@Setter
@Getter
public class TransferDetailEntityNew {

    /** 商户号 Y 说明:微信支付分配的商户号 */
    @SerializedName("mch_id")
    private String mchId;

    /** 商户单号 Y 说明:商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一 */
    @SerializedName("out_bill_no")
    private String outBillNo;

    /** 商家转账订单号 Y 说明:商家转账订单的主键,唯一定义此资源的标识 */
    @SerializedName("transfer_bill_no")
    private String transferBillNo;

    /** 商户appid Y 说明:申请商户号的appid或商户号绑定的appid(企业号corpid即为此appid) */
    @SerializedName("appid")
    private String appid;

    /** 单据状态 Y 说明:单据状态  */
    // ACCEPTED: 转账已受理
    // PROCESSING: 转账锁定资金中。如果一直停留在该状态,建议检查账户余额是否足够,如余额不足,可充值后再原单重试。
    // WAIT_USER_CONFIRM: 待收款用户确认,可拉起微信收款确认页面进行收款确认
    // TRANSFERING: 转账中,可拉起微信收款确认页面再次重试确认收款
    // SUCCESS: 转账成功
    // FAIL: 转账失败
    // CANCELING: 商户撤销请求受理成功,该笔转账正在撤销中
    // CANCELLED: 转账撤销完成
    @SerializedName("state")
    private String state;

    /** 转账金额 Y 说明:转账金额单位为“分”。*/
    @SerializedName("transfer_amount")
    private Integer transferAmount;

    /** 转账备注 Y 说明:转账备注,用户收款时可见该备注信息,UTF8编码,最多允许32个字符。*/
    @SerializedName("transfer_remark")
    private String transferRemark;

    /** 失败原因 N 说明:订单已失败或者已退资金时,返回失败原因。 */
    @SerializedName("fail_reason")
    private String failReason;

    /** 收款用户OpenID Y 说明:商户AppID下,某用户的OpenID  */
    @SerializedName("openid")
    private String openid;

    /** 收款用户姓名 N 说明:收款方真实姓名。需要加密传入,支持标准RSA算法和国密算法,公钥由微信侧提供。
     转账金额 >= 2,000元时,该笔明细必须填写
     若商户传入收款用户姓名,微信支付会校验收款用户与输入姓名是否一致,并提供电子回单 */
    @SerializedName("user_name")
    private String userName;

    /** 单据创建时间 N 说明:单据受理成功时返回,按照使用rfc3339所定义的格式,格式为yyyy-MM-DDThh:mm:ss+TIMEZONE */
    @SerializedName("create_time")
    private String createTime;

    /** 最后一次状态变更时间 N 说明:单据最后更新时间,按照使用rfc3339所定义的格式,格式为yyyy-MM-DDThh:mm:ss+TIMEZONE */
    @SerializedName("update_time")
    private String updateTime;

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) return false;
        TransferDetailEntityNew that = (TransferDetailEntityNew) o;
        return Objects.equals(mchId, that.mchId) && Objects.equals(outBillNo, that.outBillNo) && Objects.equals(transferBillNo, that.transferBillNo) && Objects.equals(appid, that.appid) && Objects.equals(state, that.state) && Objects.equals(transferAmount, that.transferAmount) && Objects.equals(transferRemark, that.transferRemark) && Objects.equals(failReason, that.failReason) && Objects.equals(openid, that.openid) && Objects.equals(userName, that.userName) && Objects.equals(createTime, that.createTime) && Objects.equals(updateTime, that.updateTime);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mchId, outBillNo, transferBillNo, appid, state, transferAmount, transferRemark, failReason, openid, userName, createTime, updateTime);
    }

    @Override
    public String toString() {
        return "{" +
                "mchId='" + mchId + '\'' +
                ", outBillNo='" + outBillNo + '\'' +
                ", transferBillNo='" + transferBillNo + '\'' +
                ", appid='" + appid + '\'' +
                ", state='" + state + '\'' +
                ", transferAmount=" + transferAmount +
                ", transferRemark='" + transferRemark + '\'' +
                ", failReason='" + failReason + '\'' +
                ", openid='" + openid + '\'' +
                ", userName='" + userName + '\'' +
                ", createTime='" + createTime + '\'' +
                ", updateTime='" + updateTime + '\'' +
                '}';
    }

    public TransferDetailEntityNew cloneWithCipher(PrivacyDecryptor encryptor) {
        TransferDetailEntityNew copy = new TransferDetailEntityNew();
        copy.mchId = mchId;
        copy.outBillNo = outBillNo;
        copy.transferBillNo = transferBillNo;
        copy.appid = appid;
        copy.state = state;
        copy.transferAmount = transferAmount;
        copy.transferRemark = transferRemark;
        copy.failReason = failReason;
        copy.openid = openid;
        if (userName != null && !userName.isEmpty()) {
            copy.userName = encryptor.decrypt(userName);
        }
        copy.createTime = createTime;
        copy.updateTime = updateTime;
        return copy;
    }
}

import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;

import java.util.Objects;

/**
 * 转账场景报备信息实体类
 */
@Setter
@Getter
public class TransferSceneReportInfoNew {

    /** 信息类型 Y 说明:请根据产品文档确认当前转账场景下需传入的信息类型,需按要求填入,有多个字段时需填写完整
     如:转账场景为1000-现金营销,需填入活动名称、奖励说明 */
    @SerializedName("info_type")
    private String infoType;

    /** 信息内容 Y 说明:请根据信息类型,描述当前这笔转账单的转账背景
     如:
     信息类型为活动名称,请在信息内容描述用户参与活动的名称,如新会员有礼
     信息类型为奖励说明,请在信息内容描述用户因为什么奖励获取这笔资金,如注册会员抽奖一等奖 */
    @SerializedName("info_content")
    private String infoContent;

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) return false;
        TransferSceneReportInfoNew that = (TransferSceneReportInfoNew) o;
        return Objects.equals(infoType, that.infoType) && Objects.equals(infoContent, that.infoContent);
    }

    @Override
    public int hashCode() {
        return Objects.hash(infoType, infoContent);
    }

    @Override
    public String toString() {
        return "TransferSceneReportInfo{" +
                "infoType='" + infoType + '\'' +
                ", infoContent='" + infoContent + '\'' +
                '}';
    }

    public TransferSceneReportInfoNew() {
        super();
    }
}

import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;

@Getter
@Setter
public class WeChatPayCallbackNotification {

    /** 应用ID */
    @SerializedName("appid")
    private String appid;

    /** 商户号 */
    @SerializedName("mchid")
    private String mchid;

    /** 商户订单号 */
    @SerializedName("out_trade_no")
    private String outTradeNo;

    /** 微信支付订单号 */
    @SerializedName("transaction_id")
    private String transactionId;

    /** 交易类型 */
    @SerializedName("trade_type")
    private String tradeType;

    /** 交易状态 */
    @SerializedName("trade_state")
    private String tradeState;

    /** 交易状态描述 */
    @SerializedName("trade_state_desc")
    private String tradeStateDesc;

    /** 银行类型 */
    @SerializedName("bank_type")
    private String bankType;

    /** 支付完成时间 */
    @SerializedName("success_time")
    private String successTime;

    /** 金额信息 */
    @SerializedName("amount")
    private Amount amount;

    /** 支付者信息 */
    @SerializedName("payer")
    private Payer payer;

    /** 商户自定义数据 */
    @SerializedName("attach")
    private String attach;

    /** 场景信息 */
    @SerializedName("scene_info")
    private String sceneInfo;

    /** 优惠券信息 */
    @SerializedName("promotion_detail")
    private PromotionDetail promotionDetail;

    // 金额详情类
    @Getter
    @Setter
    public static class Amount {
        /** 用户支付金额 */
        @SerializedName("payer_total")
        private Integer payerTotal;

        /** 订单总金额 */
        @SerializedName("total")
        private Integer total;

        /** 货币类型 */
        @SerializedName("currency")
        private String currency;

        /** 用户支付货币类型 */
        @SerializedName("payer_currency")
        private String payerCurrency;
    }

    // 支付者信息类
    @Getter
    @Setter
    public static class Payer {
        /** 用户标识 */
        @SerializedName("openid")
        private String openid;
    }

    // 优惠券信息类
    @Getter
    @Setter
    public static class PromotionDetail {
        // 根据实际需求添加优惠券相关字段
    }
}

import lombok.Data;

@Data
public class WxPayTranferReq {

    // rowId(行数据唯一id)
    private String rowId;

    // 微信用户openId(必填)
    private String openId;

    // 用户真实姓名(选填,但转账金额 >= 2,000元时必填)
    private String userName;

    // 转账金额(必填)
    private String transferAmount;

    // 转账备注(必填,用户收款时可见该备注信息)
    private String transferRemark;

    // 订单号
    private String outBillNo;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值