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