微信支付V3版商家转账到零钱(2025-01-15更新后版本)

本文档为商家转账升级版本功能描述,升级版本已于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>

更新之后接口流程:

  1. 商户服务端调用微信支付的商家转账 API,发起转账请求。请求中包含了转账的金额、收款用户,商户单号等。
  2. 微信支付收到请求后,先锁定商户资金,直至转账处理完毕。
  3. 当返回的商家转账订单状态为WAIT_USER_CONFIRM时,调用请求用户确认收款JSAPI或APP调起用户确认收款来调起微信请求用户确认收款
  4. 当返回的商家转账订单状态为非WAIT_USER_CONFIRM时,可用原商户商户单号进行重试
  5. 如果用户已通过其他方式提现,商户可以主动发起撤销商家转账。
  6. 用户看到收款确认信息后进行确认收款操作。
  7. 用户确认收款后,微信支付会发送转账结果通知给商户。
  8. 商户服务端收到转账结果通知后,调用微信支付的查询转账订单 API,查询转账的状态,并对用户展示转账结果,或者执行其他后续操作。
  9. 如果用户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)

总结
感谢您的阅读,希望这篇博客能给您带来启发和帮助。如果有任何问题或想法,欢迎在评论区留言与我们交流

评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值