本文档为商家转账升级版本功能描述,升级版本已于2025年1月15日正式上线。请注意对比[升级前],新版本无收款用户管理、商户出资确认功能。
具体官方文档详见:商家转账-发起转账
项目使用的SDK版本wechatpay-java-0.2.12.jar版本
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>
更新之后接口流程:
- 商户服务端调用微信支付的商家转账 API,发起转账请求。请求中包含了转账的金额、收款用户,商户单号等。
- 微信支付收到请求后,先锁定商户资金,直至转账处理完毕。
- 当返回的商家转账订单状态为WAIT_USER_CONFIRM时,调用请求用户确认收款JSAPI或APP调起用户确认收款来调起微信请求用户确认收款
- 当返回的商家转账订单状态为非WAIT_USER_CONFIRM时,可用原商户商户单号进行重试
- 如果用户已通过其他方式提现,商户可以主动发起撤销商家转账。
- 用户看到收款确认信息后进行确认收款操作。
- 用户确认收款后,微信支付会发送转账结果通知给商户。
- 商户服务端收到转账结果通知后,调用微信支付的查询转账订单 API,查询转账的状态,并对用户展示转账结果,或者执行其他后续操作。
- 如果用户24h未确认收款,会退款至商户资金。
直接上代码 ,主要调用方法和新实体类都附上
后端代码相关
WxPayController
/**
*商家转账 - 发起转账
* @return
*/
public InitiateBatchTransferResponseNew payRequest() {
InitiateBatchTransferResponseNew response = null;
InitiateBatchTransferRequestNew request = new InitiateBatchTransferRequestNew();
//商户AppID
request.setAppid(WxpayConfig.app_id);
//商户单号
request.setOutBillNo("test123456789");
//转账金额(分为单位,测试:0.01元)
request.setTransferAmount(1);
//转账场景ID
request.setTransferSceneId("1005"); // /** 转账场景ID 说明:该批次转账使用的转账场景,如不填写则使用商家的默认场景,如无默认场景可为空,可前往“商家转账到零钱-前往功能”中申请。 如:1001-现金营销 */
//用户的openId
request.setOpenid("");
//收款用户姓名
request.setUserName("");
//转账备注
request.setTransferRemark("奖金");//【转账备注】 单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符
//转账场景报备信息 佣金的固定类型
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);
//异步接收微信支付结果通知的回调地址,通知url必须为公网可访问的URL,必须为HTTPS,不能携带参数
request.setNotifyUrl("");
try{
response = wxPayService.initiateBatchTransferNew(request);
}catch (ServiceException e){
e.printStackTrace();
}
return response;
}
/**
* 微信商户零线转账 - 回调通知
* @Context注解 把HTTP请求上下文对象注入进来,HttpServletRequest、HttpServletResponse、UriInfo 等
* @return
*/
public ResponseEntity<Map<String, String>> wxPayCallback(@Context HttpServletRequest request) {
Map<String,String> errMap = new HashMap<>();
try {
logger.info("微信商户零线转账 - 回调通知 /wxpay/callback");
Map<String, String> resMap = withdrawService.wxPaySuccessCallback(request);
return new ResponseEntity<>(resMap, HttpStatus.OK);
} catch (BusinessException e){
errMap.put("code", "FAIL");
errMap.put("message", e.getMessage());
return new ResponseEntity<>(errMap, HttpStatus.BAD_REQUEST);
} catch (Exception e) {
logger.error("微信商户零线转账 - 回调通知 /wxpay/callback:异常!", e);
errMap.put("code", "FAIL");
errMap.put("message", "服务器内部错误");
return new ResponseEntity<>(errMap, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
WxPayService
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.cipher.PrivacyDecryptor;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.http.*;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import ins.fastapp.commons.constant.Constant;
import ins.fastapp.commons.util.exception.BusinessException;
import ins.fastapp.dal.dto.wx.InitiateBatchTransferRequestNew;
import ins.fastapp.dal.dto.wx.InitiateBatchTransferResponseNew;
import ins.fastapp.dal.dto.wx.TransferDetailEntityNew;
import ins.fastapp.dal.service.nocar.YaicCommonService;
import ins.fastapp.dal.service.yongan.payment.wxPay.WxpayConfig;
import org.apache.axis2.classloader.IoUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Date;
/**
* 微信支付 Api相关
*
* @author: jsy
* @date: 2025/02/13 11:30
*/
@Service
public class WxPayService {
private static final Logger logger = LoggerFactory.getLogger(WxPayService.class);
/**
* 商家转账 - 发起转账 - 2025年1月15号之后,商户转账零线必须用户确认收款
*
* @param request 请求体
* @return
*/
public InitiateBatchTransferResponseNew initiateBatchTransferNew(InitiateBatchTransferRequestNew request) {
logger.info("WxPayService.initiateBatchTransferNew request:{}", request.toString());
Config config = new RSAAutoCertificateConfig.Builder()
.merchantId(WxpayConfig.mch_id)
.privateKeyFromPath(WxpayConfig.KeyPath)
.merchantSerialNumber(WxpayConfig.mchSerialNo)
.apiV3Key(WxpayConfig.v3Key)
.build();
String encryptName = config.createEncryptor().encrypt(request.getUserName());
request.setUserName(encryptName);
String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills";
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());
HttpRequest httpRequest =
new HttpRequest.Builder()
.httpMethod(HttpMethod.POST)
.url(requestPath)
.headers(headers)
.body(createRequestBody(request))
.build();
HttpClient httpClient = new DefaultHttpClientBuilder().config(config).build();
HttpResponse<InitiateBatchTransferResponseNew> httpResponse = httpClient.execute(httpRequest, InitiateBatchTransferResponseNew.class);
logger.info("WxPayService.initiateBatchTransferNew response:{}", httpResponse.getServiceResponse());
return httpResponse.getServiceResponse();
}
/**
* 商家转账 - 商户单号查询转账单 - 2025年1月15号之后,商家转账用户确认模式下,根据商户单号查询转账单的详细信息
*
* @param outBillNo 商户系统内部的商家单号
* @return
*/
public TransferDetailEntityNew getTransferDetailByOutNoNew(String outBillNo) {
logger.info("WxPayService.getTransferDetailByOutNoNew request:{}", outBillNo);
Config config = new RSAAutoCertificateConfig.Builder()
.merchantId(WxpayConfig.mch_id)
.privateKeyFromPath(WxpayConfig.KeyPath)
.merchantSerialNumber(WxpayConfig.mchSerialNo)
.apiV3Key(WxpayConfig.v3Key)
.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);
logger.info("WxPayService.getTransferDetailByOutNoNew response:{}", httpResponse.getServiceResponse());
return httpResponse.getServiceResponse().cloneWithCipher(decryptor);
}
/**
* 转json
*
* @param request 请求体
* @return
*/
private static RequestBody createRequestBody(Object request) {
return new JsonRequestBody.Builder().body(new Gson().toJson(request)).build();
}
/**
* 商家转账 - 零钱提现 - 回调通知
*
* @param request
* @return
*/
public TransferDetailEntityNew wxPaySuccessCallback(HttpServletRequest request) {
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");
// 1. 构造 RequestParam
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(wechatPaySerial)
.nonce(wechatpayNonce)
.signature(wechatSignature)
.timestamp(wechatTimestamp)
.body(requestBody)
.build();
// 2. 构建Config RSAAutoCertificateConfig
Config config = new RSAAutoCertificateConfig.Builder()
.merchantId(WxpayConfig.mch_id)
.privateKeyFromPath(WxpayConfig.KeyPath)
.merchantSerialNumber(WxpayConfig.mchSerialNo)
.apiV3Key(WxpayConfig.v3Key)
.build();
logger.info("WxPayService.wxPaySuccessCallback request : wechatPaySerial is [{}] , wechatSignature is [{}] , wechatTimestamp is [{}] , wechatpayNonce is [{}] , requestBody is [{}]",wechatPaySerial,wechatSignature,wechatTimestamp,wechatpayNonce,requestBody);
// 3. 初始化 NotificationParser
NotificationParser parser = new NotificationParser((NotificationConfig) config);
try {
TransferDetailEntityNew entity = parser.parse(requestParam, TransferDetailEntityNew.class);
logger.info("WxPayService.wxPaySuccessCallback responseBody: {}", entity != null ? JSON.toJSONString(entity) : null);
return entity;
} catch (ValidationException e) {
logger.error("Sign verification failed", e);
throw new BusinessException("签名验证失败",Constant.EX_SERVICE);
} catch (Exception e) {
logger.error("Exception occurred while processing", e);
throw new BusinessException("系统内部错误",Constant.EX_SERVICE);
}
}
/**
* 获取post请求中的Body
*
* @param request httpRequest
* @return 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) {
logger.error("获取requestBody异常", e);
} finally {
IoUtil.close(inputStream);
IoUtil.close(reader);
}
return sb.toString();
}
}
InitiateBatchTransferRequestNew
商家零钱转账自定义请求体
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 商家零钱转账自定义请求体
*
* @author: jsy
* @date: 2025/02/07 17:38
*/
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 String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getOutBillNo() {
return outBillNo;
}
public void setOutBillNo(String outBillNo) {
this.outBillNo = outBillNo;
}
public String getTransferSceneId() {
return transferSceneId;
}
public void setTransferSceneId(String transferSceneId) {
this.transferSceneId = transferSceneId;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getTransferAmount() {
return transferAmount;
}
public void setTransferAmount(Integer transferAmount) {
this.transferAmount = transferAmount;
}
public String getTransferRemark() {
return transferRemark;
}
public void setTransferRemark(String transferRemark) {
this.transferRemark = transferRemark;
}
public String getNotifyUrl() {
return notifyUrl;
}
public void setNotifyUrl(String notifyUrl) {
this.notifyUrl = notifyUrl;
}
public List<TransferSceneReportInfoNew> getTransferSceneReportInfos() {
return transferSceneReportInfos;
}
public void setTransferSceneReportInfos(List<TransferSceneReportInfoNew> transferSceneReportInfos) {
this.transferSceneReportInfos = transferSceneReportInfos;
}
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 +
'}';
}
}
InitiateBatchTransferResponseNew
商家零钱转账自定义返回体
import com.google.gson.annotations.SerializedName;
import java.util.Objects;
/**
* 商家零钱转账自定义返回体
*
* @author: jsy
* @date: 2025/02/07 17:38
*/
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;
public String getOutBillNo() {
return outBillNo;
}
public void setOutBillNo(String outBillNo) {
this.outBillNo = outBillNo;
}
public String getTransferBillNo() {
return transferBillNo;
}
public void setTransferBillNo(String transferBillNo) {
this.transferBillNo = transferBillNo;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getFailReason() {
return failReason;
}
public void setFailReason(String failReason) {
this.failReason = failReason;
}
public String getPackageInfo() {
return packageInfo;
}
public void setPackageInfo(String packageInfo) {
this.packageInfo = 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 "InitiateBatchTransferResponse{" +
"outBillNo='" + outBillNo + '\'' +
", transferBillNo='" + transferBillNo + '\'' +
", createTime='" + createTime + '\'' +
", state='" + state + '\'' +
", failReason=" + failReason +
", packageInfo='" + packageInfo + '\'' +
'}';
}
}
TransferDetailEntityNew
商户单号查询转账单实体类信息
import com.google.gson.annotations.SerializedName;
import com.wechat.pay.java.core.cipher.PrivacyDecryptor;
import java.util.Objects;
/**
* 商户单号查询转账单实体类信息
*
* @author: jsy
* @date: 2025/02/13 11:28
*/
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 说明:单据状态 */
@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;
public String getMchId() {
return mchId;
}
public void setMchId(String mchId) {
this.mchId = mchId;
}
public String getOutBillNo() {
return outBillNo;
}
public void setOutBillNo(String outBillNo) {
this.outBillNo = outBillNo;
}
public String getTransferBillNo() {
return transferBillNo;
}
public void setTransferBillNo(String transferBillNo) {
this.transferBillNo = transferBillNo;
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Integer getTransferAmount() {
return transferAmount;
}
public void setTransferAmount(Integer transferAmount) {
this.transferAmount = transferAmount;
}
public String getTransferRemark() {
return transferRemark;
}
public void setTransferRemark(String transferRemark) {
this.transferRemark = transferRemark;
}
public String getFailReason() {
return failReason;
}
public void setFailReason(String failReason) {
this.failReason = failReason;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public String getUpdateTime() {
return updateTime;
}
public void setUpdateTime(String updateTime) {
this.updateTime = 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 "TransferDetailEntityNew{" +
"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;
}
}
TransferSceneReportInfoNew
转账场景报备信息实体类
import com.google.gson.annotations.SerializedName;
import java.util.Objects;
/**
* 转账场景报备信息实体类
*
* @author: jsy
* @date: 2025/02/07 17:48
*/
public class TransferSceneReportInfoNew {
/** 信息类型 Y 说明:请根据产品文档确认当前转账场景下需传入的信息类型,需按要求填入,有多个字段时需填写完整
如:转账场景为1000-现金营销,需填入活动名称、奖励说明 */
@SerializedName("info_type")
private String infoType;
/** 信息内容 Y 说明:请根据信息类型,描述当前这笔转账单的转账背景
如:
信息类型为活动名称,请在信息内容描述用户参与活动的名称,如新会员有礼
信息类型为奖励说明,请在信息内容描述用户因为什么奖励获取这笔资金,如注册会员抽奖一等奖 */
@SerializedName("info_content")
private String infoContent;
public String getInfoType() {
return infoType;
}
public void setInfoType(String infoType) {
this.infoType = infoType;
}
public String getInfoContent() {
return infoContent;
}
public void setInfoContent(String infoContent) {
this.infoContent = 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();
}
}
注意:
新接口添加了转账场景相关参数,具体官方文档转账场景
1.首先要去申请转账场景
2.必须传入固定的类型:
比如我申请的场景:佣金报酬
如果申请的比如是:企业赔付
前端代码相关(vue项目大致思路一样)
前端主要是新增了个JSAPI调起用户确认收款,此处也附上实现相关
main.js 初始化存上location.href,ios加载wx.config会默认使用第一次加载的路径,需要缓存第一次的url,使用缓存的url
import Vue from 'vue'
import './plugins/axios'
import App from './App.vue'
import './plugins/vant.js'
import 'amfe-flexible';
import router from './router'
import store from './store'
import fm from './utils/fmoney'
Vue.prototype.fm = fm
Vue.config.productionTip = false
new Vue({
router: router,
store,
render: h => h(App),
created() {
localStorage.setItem('firstEntryUrl', encodeURIComponent(location.href));
}
}).$mount('#app')
wechat.js类
import wx from 'weixin-js-sdk'
/**
* JSAPI调起用户确认收款
* @param packageInfo 跳转页面的package信息 - 商家转账付款单跳转收款页package信息,商家转账付款单受理成功时返回给商户
* @returns {Promise<unknown>}
*/
function waitUserConfirmPay(packageInfo) {
return new Promise((resolve, reject) => {
function isIOS() {
return /iPhone|iPad|iPod/i.test(navigator.userAgent);
}
let url = location.href;
if (isIOS() && localStorage.getItem('firstEntryUrl' ) ) {
url = localStorage.getItem('firstEntryUrl' )
}
const jsApiList = ['requestMerchantTransfer'];
console.log('注册的url', url)
window.axios({
method: 'GET',
url: '/**/**/**?url=' + url,
headers: {'Content-Type': 'application/json' }
}).then(result => {
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: result.appid, // 必填,公众号的唯一标识
timestamp: result.timestamp, // 必填,生成签名的时间戳
nonceStr: result.noncestr, // 必填,生成签名的随机串
signature: result.signature, // 必填,签名
jsApiList // 必填,需要使用的JS接口列表,JSAPI调起用户确认收款
})
wx.ready(function () {
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
console.log('微信调用ready表示成功')
wx.checkJsApi({
jsApiList,
success: function (res) {
if (res.checkResult['requestMerchantTransfer']) {
// eslint-disable-next-line no-undef
WeixinJSBridge.invoke('requestMerchantTransfer', {
//商户号
mchId: process.env.MCH_ID,
//appid
appId: process.env.APP_ID,
package: packageInfo,
},
function (res) {
resolve(res);
}
);
} else {
alert('你的微信版本过低,请更新至最新版本。');
reject(new Error('微信版本过低'));
}
},
fail: function(res) {
reject({
payFail: res
})
}
});
})
wx.error(function (res) {
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
console.log('wxOpen微信调用错误' ,res)
reject(new Error('微信配置错误'));
})
}).catch(error => {
console.error('请求签名失败', error);
reject(error);
});
});
}
export default waitUserConfirmPay
test.vue
import waitUserConfirmPay from "@/utils/wechat";
//【跳转领取页面的package信息】发起转账返回的参数
waitUserConfirmPay(packageInfo)
总结
感谢您的阅读,希望这篇博客能给您带来启发和帮助。如果有任何问题或想法,欢迎在评论区留言与我们交流