微信开发-商家转账到零钱

先检查申请的商户号是否接入升级版本功能,在2025-01-18后微信转账到零钱功能升级,有所变化,当前文档只适用于未接入升级版本功能的商户号

什么是商家转账到零钱?

为了满足商家向用户微信零钱转账的需求,微信支付推出“商家转账到零钱”服务,方便商户可以通过“商家转账到零钱”一次向单个或多个用户的微信零钱转账。商家转账到零钱提供了两种转账方式:“页面发起转账”“API发起转账”,为商户提供了简便、免费、安全的转账服务。使用“商家转账到零钱”可以帮助商户更加便捷、安全地向用户转账,提高转账效率。
在这里插入图片描述

要求

1、商户号已入驻90日且截止今日回推30天商户号保持连续不间断的交易。
2、登录微信支付商户平台-产品中心,开通付款到零钱(资质暂不支持小微商户、个体工商户。)。

转账流程

在这里插入图片描述

1、微信文档地址

产品介绍:

https://pay.weixin.qq.com/docs/merchant/products/batch-transfer-to-balance/introduction.html

API文档地址:

https://pay.weixin.qq.com/docs/merchant/apis/batch-transfer-to-balance/transfer-batch/initiate-batch-transfer.html

2、前期准备

2.1、微信支付

2.1.1、商户号

商户可登录微信商户平台,在【账户中心】->【商户信息】找到商户号
在这里插入图片描述

2.1.2、商户证书

商户可登录微信商户平台,在【账户中心】->【API安全】目录下载证书。
在这里插入图片描述

2.1.3、apiV3key

商户可登录微信商户平台,进入【账户中心 > API安全 】目录,设置APIV3密钥。
在这里插入图片描述

2.2、支付载体申请(这里是微信小程序和APP)

微信支付的产品体系全部搭载于微信的社交体系之上,所以普通商户或服务商商户接入微信支付之前,都需要有一个微信社交载体,该载体对应的ID即为AppID。
对于普通商户,该社交载体可以是公众号小程序App等。

  • 如申请社交载体为公众号,请前往 公众平台 申请。
  • 如申请社交载体为小程序,请前往 小程序平台申请。
  • 如商户已拥有自己的App,且希望该App接入微信支付,请前往 开放平台 申请。

各类社交载体一旦申请成功后,可以登录对应平台查看账号信息以获取对应的AppID和相关秘钥等信息。

2.3、绑定AppID及mchid

商户可登录微信商户平台> 产品中心 > APPID账号管理 > 我关联的APPID账号
AppID和mchid全部申请完毕后,需要建立两者之间的绑定关系。
普通商户模式下,AppID与mchid之间的关系为多对多,即一个AppID下可以绑定多个mchid,而一个mchid也可以绑定多个AppID。

2.4、商户开通商家转账到零钱。

商户可登录微信商户平台> 产品中心 > 运营工具 > 商家转账到零钱。
微信接入步骤文档地址:

https://pay.weixin.qq.com/docs/merchant/products/batch-transfer-to-balance/preparation.html

3、接口开发

3.1、引入相关依赖

maven依赖

微信支付官方推荐的SDK

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

3.2、微信支付配置类

yaml配置

wechat:
  pay:
    # 微信支付商户号
    merchantId: **********
    # 商户证书路径
    certPemPath: /cert/apiclient_cert.pem
    # 商户API私钥路径
    privateKeyPath: /cert/apiclient_key.pem
    # 商户APIv3密钥
    apiV3key: 07****************************47

证书文件

在resource下新建一个cert文件,将商户证书放在cert文件中,如下
在这里插入图片描述
当前我们yaml中配置的是相对路径的地址,因为我们是放在项目的resource目录中的,如果要用绝对路径,将WechatPayAutoConfiguration类中的CLASS_PATH改为"file:"就行

  • classpath: 是从类路径加载资源,通常用于获取项目内嵌的资源(例如存放在 resources 目录下的文件),并不依赖于文件系统的具体位置。
  • file: 是从文件系统加载资源,路径需要指定在本地或网络文件系统中可以访问的具体位置。

微信支付配置类

微信支付配置类WechatPayProperties用于读取上方application.yml文件中以wechat.pay为前缀的配置信息,并将这些配置映射到类的字段中。

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


/**
 * @Author: zhoujinchuan
 * @Description:  (微信支付配置类)
 * @Date: 2024-12-02 14:10:40
 */
@Data
@Component
@ConfigurationProperties(prefix = "wechat.pay")
public class WechatPayProperties {
    /**
     * 微信支付商户号
     */
    private String merchantId;
    
    /**
     * 商户证书路径
     */
    private String certPemPath;
    
    /**
     * 商户API私钥路径
     */
    private String privateKeyPath;
    
    /**
     * 商户APIv3密钥
     */
    private String apiV3key;
    
    /**
     * 支付回调通知地址--微信支付的
     */
    private String payNotifyUrl;
}

3.3、微信支付相关自动配置

这个类WechatPayAutoConfiguration主要用于微信支付的自动配置,提供了微信支付相关的配置和服务初始化。它通过 Spring 的@Configuration注解表示这是一个配置类,负责设置和管理微信支付所需的各种配置信息,并通过@Bean注解暴露出不同的 Bean 供 Spring 容器管理。

  • Config:该方法读取微信支付的证书和私钥文件,配置微信支付所需的证书信息(如证书序列号、私钥等)
  • TransferBatchService:这是一个用于处理微信支付转账的服务。该服务需要Config配置对象作为构造参数。
  • NotificationParser:用于解析这些回调信息,提取出支付状态等数据,便于商户系统处理。
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.transferbatch.TransferBatchService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.cert.*;


/**
 * @Author: zhoujinchuan
 * @Description:  (微信支付相关自动配置)
 * @Date: 2024-12-02 14:10:40
 */
@Slf4j
@Configuration
public class WechatPayAutoConfiguration {

    @Autowired
    private WechatPayProperties properties;

    @Autowired
    private ResourceLoader resourceLoader;

    private static final String CLASS_PATH = "classpath:";


    /**
     * 自动更新证书
     *
     * @return RSAAutoCertificateConfig
     */
    @Bean
    public Config config() throws IOException {
        String path = CLASS_PATH + properties.getCertPemPath();
        Resource resourceCert = resourceLoader.getResource(path);
        X509Certificate certificate = getCertificate(resourceCert.getInputStream());
        String merchantSerialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
        log.info("==========证书序列号:{},商户信息:{}", merchantSerialNumber, certificate.getSubjectDN());
        String privatePath = CLASS_PATH + properties.getPrivateKeyPath();
        Resource resourcePrivate = resourceLoader.getResource(privatePath);
        String privateKey = inputStreamToString(resourcePrivate.getInputStream());
        log.info("==========加载微信私钥配置:{}", privateKey);
        RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder()
                .merchantId(properties.getMerchantId())
                .privateKey(privateKey)
                .merchantSerialNumber(merchantSerialNumber)
                .apiV3Key(properties.getApiV3key())
                .build();
        return config;
    }

    /**
     * 微信转账对象
     * @param config Config
     * @return JsapiServiceExtension
     */
    @Bean
    public TransferBatchService transferBatchService(Config config){
        log.info("==========加载微信转账对象");
        TransferBatchService service = new TransferBatchService.Builder().config(config).build();
        return service;
    }

    /**
     * 微信回调对象
     *
     * @param config Config
     * @return NotificationParser
     */
    @Bean
    public NotificationParser notificationParser(Config config) {
        log.info("==========加载微信回调解析对象");
        NotificationParser parser = new NotificationParser((NotificationConfig) config);
        return parser;
    }

    /**
     * 读取私钥文件,将文件流读取成string
     *
     * @param inputStream
     * @return
     * @throws IOException
     */
    public String inputStreamToString(InputStream inputStream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder stringBuilder = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
        }
        reader.close();
        return stringBuilder.toString();
    }

    /**
     * 获取证书 将文件流转成证书文件
     *
     * @param inputStream 证书文件
     * @return {@link X509Certificate} 获取证书
     */
    public static X509Certificate getCertificate(InputStream inputStream) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书", e);
        }
    }

}

另外,这里还可以注入一些其他的服务,这里列举一些这些常见的微信支付的服务,都可以用对应Service的Builder构建所需要的对象
在这里插入图片描述

3.4、支付方法

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.service.transferbatch.TransferBatchService;
import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferResponse;
import com.wechat.pay.java.service.transferbatch.model.TransferDetailInput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;

/**
 * @Author: zhoujinchuan
 * @Description: ()
 * @Date: 2024/12/4 17:09
 */
@Service
public class WxTransferServiceImpl implements IWxTransferService {

    private static final Logger logger = LoggerFactory.getLogger(WxTransferServiceImpl.class);

    @Resource
    private TransferBatchService transferBatchService;

    @Override
    public void pay(String type) {
        // 发起转账------------------
        //批次编号,批次名称,批次备注
        String outBatchNo = RandomUtil.randomNumbers(10);
        String batchName = outBatchNo;
        String batchRemark = outBatchNo;
        //创建批次对象
        InitiateBatchTransferRequest batchesRequest = new InitiateBatchTransferRequest();
        //【商户appid】 申请商户号的appid或商户号绑定的appid(企业号corpid即为此appid)
        String appId = "wx****************";
        batchesRequest.setAppid(appId);
        //【商家批次单号】 商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
        batchesRequest.setOutBatchNo(outBatchNo);
        //【批次名称】 该笔批量转账的名称
        batchesRequest.setBatchName(batchName);
        //【批次备注】 转账说明,UTF8编码,最多允许32个字符
        batchesRequest.setBatchRemark(batchRemark);
        //【转账总笔数】 一个转账批次单最多发起一千笔转账。转账总笔数必须与批次内所有明细之和保持一致,否则无法发起转账操作
        batchesRequest.setTotalNum(1);
        //【转账明细列表】
        ArrayList<TransferDetailInput> transferDetails = new ArrayList<>();
        BigDecimal totalAmount = BigDecimal.ZERO;
        for (int i = 0; i < 1; i++) {
            TransferDetailInput transferDetail = new TransferDetailInput();
            String outDetailNo = RandomUtil.generateTxNo(10);
            //【商家明细单号】 商户系统内部区分转账批次单下不同转账明细单的唯一标识,要求此参数只能由数字、大小写字母组成
            transferDetail.setOutDetailNo(outDetailNo);
            //【转账金额】 转账金额单位为“分”
            transferDetail.setTransferAmount(new BigDecimal("0.3").multiply(BigDecimal.valueOf(100)).longValue());
            //【转账备注】 单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符
            transferDetail.setTransferRemark("提现佣金到账");
            //【收款用户openid】以实际对应载体的openid为准
            transferDetail.setOpenid("oolmK6-gkW93ib-kUd5km0LLJJbo");
            //【收款用户姓名】 收款方真实姓名
            transferDetail.setUserName("周某");
            //把收款人对象放到批次里面
            transferDetails.add(transferDetail);
            //累计总金额
            totalAmount = totalAmount.add(new BigDecimal("0.3"));
        }
        batchesRequest.setTransferDetailList(transferDetails);
        //【转账总金额】 转账金额单位为“分”。转账总金额必须与批次内所有明细转账金额之和保持一致,否则无法发起转账操作
        batchesRequest.setTotalAmount(totalAmount.multiply(BigDecimal.valueOf(100)).longValue());
        //该批次转账使用的转账场景
        //batchesRequest.setTransferSceneId();
        InitiateBatchTransferResponse transferBatchesResult = null;
        logger.error("请求参数:{}", JSONUtil.toJsonStr(batchesRequest));
        try {
            transferBatchesResult = transferBatchService.initiateBatchTransfer(batchesRequest);
            logger.error("返回参数: {}", JSONUtil.toJsonStr(transferBatchesResult));
        }catch (ServiceException e){
            e.printStackTrace();
            logger.error("企业支付失败:[msg:{}]", e.getMessage());
        }
        logger.error("企业支付完成:[msg:{}]", transferBatchesResult);
        // 转账结束 --------------
    }
}

appid:这里对应这第二步支付绑定的载体的appid。比如这里我用的微信小程序,就是微信小程序的appId,(企业号corpid即为此appid)。
openid:是微信用来标识某个用户在特定载体下的唯一身份的标识符。它能唯一标识微信用户在该应用中的身份。企业号用户需要使用企业号useridopenid接口将企业成员的userid转换成openid

  • 唯一性:在同一个应用(载体)下,openid 唯一对应某个微信用户。
  • 隔离性:如果同一个微信用户在不同的应用下,openid 是不同的。这是为了保护用户隐私和避免跨应用关联用户。

注意:多个载体的情况,批次appId的是用的哪个appId,下方明细中的openid就要对应到对应载体中获取的openid
例如:

String appId = "";
//模拟多个载体
if(true){
	//小程序
	appId = "wx****************";
}else{
	//APP
	appId = "wx****************";
}
batchesRequest.setAppid(appId);

获取openid的方法

user_name:收款方真实姓名。支持标准RSA算法和国密算法,公钥由微信侧提供,微信SDK已经封装了加密,这里只需填写明文就行。

  • 明细转账金额<0.3元时,不允许填写收款用户姓名。
  • 明细转账金额>= 2,000元时,该笔明细必须填写收款用户姓名。
  • 同一批次转账明细中的姓名字段传入规则需保持一致,也即全部填写、或全部不填写
  • 若商户传入收款用户姓名,微信支付会校验用户openid与姓名是否一致,并提供电子回单

transfer_scene_id:该批次转账使用的转账场景,如不填写则使用商家的默认场景,如无默认场景可为空,可前往“商家转账到零钱-前往功能”中申请。如:1001-现金营销
notify_url:转账通知,这里和支付通知不一样,需要再转账请求中传入参数notify_url,但是在当前wechatpay-javaSDK中没有包含设置通知地址的地方(SDK提交了相关issues)。当前是通过定时任务定时去手动请求通过商家明细单号查询明细单接口来查询转账的状态。

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-java</artifactId>
    <version>0.2.15</version>
</dependency>
<新版本中好像已经包含了notify_url,但是我这里还是用的主动查询的方式>

3.5、查询转账明细

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.wechat.pay.java.service.transferbatch.TransferBatchService;
import com.wechat.pay.java.service.transferbatch.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;

/**
 * @Author: zhoujinchuan
 * @Description: ()
 * @Date: 2024/12/13 11:42
 */
@Service
public class WxTransferServiceImpl implements IWxTransferService {

    private static final Logger logger = LoggerFactory.getLogger(WxTransferServiceImpl.class);

    @Resource
    private TransferBatchService transferBatchService;

    /**
     * 通过商家明细单号查询明细单
     * @param outBatchNo
     * @param outDetailNo
     */
    public void getTransferDetail(String outBatchNo,String outDetailNo){
        GetTransferDetailByOutNoRequest request = new GetTransferDetailByOutNoRequest();
        request.setOutBatchNo(outBatchNo);
        request.setOutDetailNo(outDetailNo);
        TransferDetailEntity result = transferBatchService.getTransferDetailByOutNo(request);
        if (result != null){
            String detailStatus = result.getDetailStatus();
            WxZzBatchDetailStatusEnum statusEnum = WxZzBatchDetailStatusEnum.fromCode(detailStatus);
            //明细最后一次状态变更的时间
            Date resultUpdateTime = DateUtil.parse(result.getUpdateTime(),"yyyy-MM-dd'T'HH:mm:ssXXX");
            if (WxZzBatchDetailStatusEnum.SUCCESS.equals(statusEnum)) {
                //转账成功处理
            } else if (WxZzBatchDetailStatusEnum.FAIL.equals(statusEnum)) {
                //转账失败处理
                //明细失败原因枚举
                FailReasonType failReason = result.getFailReason();
                //明细失败原因中文转译
                String failReasonCh = WeChatPayFailReasonEnum.getLabelByFailReasonType(failReason);
            } else {
                logger.error("查询明细结果----其他状态:{},{}", detailStatus,statusEnum);
            }
        }else {
            logger.error("查询明细结果----响应异常");
        }
    }

}


detail_status:明细状态。

  • INIT:初始态。 系统转账校验中
  • WAIT_PAY:待确认。待商户确认, 符合免密条件时, 系统会自动扭转为转账中
  • PROCESSING:转账中。正在处理中,转账结果尚未明确
  • SUCCESS:转账成功
  • FAIL:转账失败。需要确认失败原因后,再决定是否重新发起对该笔明细单的转账(并非整个转账批次单)

明细状态的自定义枚举类WxZzBatchDetailStatusEnum

/**
 * @Author: zhoujinchuan
 * @Description: ()
 * @Date: 2024/12/4 9:19
 */
public enum WxZzBatchDetailStatusEnum {

    /**
     * 微信转账到用户明细状态枚举
     */
    INIT("INIT", "初始态"),
    WAIT_PAY("WAIT_PAY", "待确认"),
    PROCESSING("PROCESSING", "转账中"),
    SUCCESS("SUCCESS", "转账成功"),
    FAIL("FAIL", "转账失败"),
    ;

    private String code;
    private String label;

    WxZzBatchDetailStatusEnum(String code, String label) {
        this.code = code;
        this.label = label;
    }

    public String getCode() {
        return code;
    }

    public String getLabel() {
        return label;
    }

    /**
     * 通过code获取枚举
     * @param code
     * @return
     */
    public static WxZzBatchDetailStatusEnum fromCode(String code) {
        for (WxZzBatchDetailStatusEnum status : WxZzBatchDetailStatusEnum.values()) {
            if (status.getCode().equals(code)) {
                return status;
            }
        }
        return null;
    }

}

fail_reason:明细失败原因,如果转账失败则有失败原因。

  • ACCOUNT_FROZEN:该用户账户被冻结
  • REAL_NAME_CHECK_FAIL:收款人未实名认证,需要用户完成微信实名认证
  • NAME_NOT_CORRECT:收款人姓名校验不通过,请核实信息
  • OPENID_INVALID:Openid格式错误或者不属于商家公众账号
  • TRANSFER_QUOTA_EXCEED:超过用户单笔收款额度,核实产品设置是否准确
  • DAY_RECEIVED_QUOTA_EXCEED:超过用户单日收款额度,核实产品设置是否准确
  • MONTH_RECEIVED_QUOTA_EXCEED:超过用户单月收款额度,核实产品设置是否准确
  • DAY_RECEIVED_COUNT_EXCEED:超过用户单日收款次数,核实产品设置是否准确
  • PRODUCT_AUTH_CHECK_FAIL:未开通该权限或权限被冻结,请核实产品权限状态
  • OVERDUE_CLOSE:超过系统重试期,系统自动关闭
  • ID_CARD_NOT_CORRECT:收款人身份证校验不通过,请核实信息
  • ACCOUNT_NOT_EXIST:该用户账户不存在
  • TRANSFER_RISK:该笔转账可能存在风险,已被微信拦截
  • OTHER_FAIL_REASON_TYPE:其它失败原因
  • REALNAME_ACCOUNT_RECEIVED_QUOTA_EXCEED:用户账户收款受限,请引导用户在微信支付查看详情
  • RECEIVE_ACCOUNT_NOT_PERMMIT:未配置该用户为转账收款人,请在产品设置中调整,添加该用户为收款人
  • PAYEE_ACCOUNT_ABNORMAL:用户账户收款异常,请联系用户完善其在微信支付的身份信息以继续收款
  • PAYER_ACCOUNT_ABNORMAL:商户账户付款受限,可前往商户平台获取解除功能限制指引
  • TRANSFER_SCENE_UNAVAILABLE:该转账场景暂不可用,请确认转账场景ID是否正确
  • TRANSFER_SCENE_INVALID:你尚未获取该转账场景,请确认转账场景ID是否正确
  • TRANSFER_REMARK_SET_FAIL:转账备注设置失败, 请调整后重新再试
  • RECEIVE_ACCOUNT_NOT_CONFIGURE:请前往商户平台-商家转账到零钱-前往功能-转账场景中添加
  • BLOCK_B2C_USERLIMITAMOUNT_BSRULE_MONTH:超出用户单月转账收款20w限额,本月不支持继续向该用户付款
  • BLOCK_B2C_USERLIMITAMOUNT_MONTH:用户账户存在风险收款受限,本月不支持继续向该用户付款
  • MERCHANT_REJECT:商户员工(转账验密人)已驳回转账
  • MERCHANT_NOT_CONFIRM:商户员工(转账验密人)超时未验密

转账明细查询返回的是FailReasonType枚举类,但是没有中文转译,我我这边自定义了一个失败原因枚举类WeChatPayFailReasonEnum进行中文原因转译

import com.wechat.pay.java.service.transferbatch.model.FailReasonType;

/**
 * @Author: zhoujinchuan
 * @Description: 枚举类,包含微信支付失败原因
 * @Date: 2024-12-02 14:14:40
 */
public enum WeChatPayFailReasonEnum {

    /**
     * 微信转账失败原因枚举
     */
    ACCOUNT_FROZEN("ACCOUNT_FROZEN", "该用户账户被冻结"),
    REAL_NAME_CHECK_FAIL("REAL_NAME_CHECK_FAIL", "收款人未实名认证,需要用户完成微信实名认证"),
    NAME_NOT_CORRECT("NAME_NOT_CORRECT", "收款人姓名校验不通过,请核实信息"),
    OPENID_INVALID("OPENID_INVALID", "Openid格式错误或者不属于商家公众账号"),
    TRANSFER_QUOTA_EXCEED("TRANSFER_QUOTA_EXCEED", "超过用户单笔收款额度,核实产品设置是否准确"),
    DAY_RECEIVED_QUOTA_EXCEED("DAY_RECEIVED_QUOTA_EXCEED", "超过用户单日收款额度,核实产品设置是否准确"),
    MONTH_RECEIVED_QUOTA_EXCEED("MONTH_RECEIVED_QUOTA_EXCEED", "超过用户单月收款额度,核实产品设置是否准确"),
    DAY_RECEIVED_COUNT_EXCEED("DAY_RECEIVED_COUNT_EXCEED", "超过用户单日收款次数,核实产品设置是否准确"),
    PRODUCT_AUTH_CHECK_FAIL("PRODUCT_AUTH_CHECK_FAIL", "未开通该权限或权限被冻结,请核实产品权限状态"),
    OVERDUE_CLOSE("OVERDUE_CLOSE", "超过系统重试期,系统自动关闭"),
    ID_CARD_NOT_CORRECT("ID_CARD_NOT_CORRECT", "收款人身份证校验不通过,请核实信息"),
    ACCOUNT_NOT_EXIST("ACCOUNT_NOT_EXIST", "该用户账户不存在"),
    TRANSFER_RISK("TRANSFER_RISK", "该笔转账可能存在风险,已被微信拦截"),
    OTHER_FAIL_REASON_TYPE("OTHER_FAIL_REASON_TYPE", "其它失败原因"),
    REALNAME_ACCOUNT_RECEIVED_QUOTA_EXCEED("REALNAME_ACCOUNT_RECEIVED_QUOTA_EXCEED", "用户账户收款受限,请引导用户在微信支付查看详情"),
    RECEIVE_ACCOUNT_NOT_PERMMIT("RECEIVE_ACCOUNT_NOT_PERMMIT", "未配置该用户为转账收款人,请在产品设置中调整,添加该用户为收款人"),
    PAYEE_ACCOUNT_ABNORMAL("PAYEE_ACCOUNT_ABNORMAL", "用户账户收款异常,请联系用户完善其在微信支付的身份信息以继续收款"),
    PAYER_ACCOUNT_ABNORMAL("PAYER_ACCOUNT_ABNORMAL", "商户账户付款受限,可前往商户平台获取解除功能限制指引"),
    TRANSFER_SCENE_UNAVAILABLE("TRANSFER_SCENE_UNAVAILABLE", "该转账场景暂不可用,请确认转账场景ID是否正确"),
    TRANSFER_SCENE_INVALID("TRANSFER_SCENE_INVALID", "你尚未获取该转账场景,请确认转账场景ID是否正确"),
    TRANSFER_REMARK_SET_FAIL("TRANSFER_REMARK_SET_FAIL", "转账备注设置失败,请调整后重新再试"),
    RECEIVE_ACCOUNT_NOT_CONFIGURE("RECEIVE_ACCOUNT_NOT_CONFIGURE", "请前往商户平台-商家转账到零钱-前往功能-转账场景中添加"),
    BLOCK_B2C_USERLIMITAMOUNT_BSRULE_MONTH("BLOCK_B2C_USERLIMITAMOUNT_BSRULE_MONTH", "超出用户单月转账收款20w限额,本月不支持继续向该用户付款"),
    BLOCK_B2C_USERLIMITAMOUNT_MONTH("BLOCK_B2C_USERLIMITAMOUNT_MONTH", "用户账户存在风险收款受限,本月不支持继续向该用户付款"),
    MERCHANT_REJECT("MERCHANT_REJECT", "商户员工(转账验密人)已驳回转账"),
    MERCHANT_NOT_CONFIRM("MERCHANT_NOT_CONFIRM", "商户员工(转账验密人)超时未验密");

    private String code;
    private String label;

    WeChatPayFailReasonEnum(String code, String label) {
        this.code = code;
        this.label = label;
    }

    public String getCode() {
        return code;
    }

    public String getLabel() {
        return label;
    }

    public static String getLabelByFailReasonType(FailReasonType failReason){
        for (WeChatPayFailReasonEnum value : values()) {
            if (value.getCode().equals(String.valueOf(failReason))) {
                return value.getLabel();
            }
        }
        return "未知原因";
    }
}
<think>好的,用户想了解微信商家转账接口的正确调用流程,特别是调用顺序和流程文档相关的API。我需要先整理微信支付的相关文档,确保信息准确。 首先,用户提到的引用[3]是关于商家转账零钱的API文档,里面提到了请求头、签名算法等信息。根据官方文档,调用流程应该包括准备参数、生成签名、发送请求和处理响应这几个步骤。需要确认每个步骤的具体要求和参数。 接下来,用户可能关心的是接口调用的具体顺序。比如,是否需要先获取证书,如何构造请求头,签名生成的详细步骤,以及如何处理回调通知。这些都需要详细说明,并且结合示例代码会更清晰。 然后,用户提供的引用中有nodejs的示例,比如引用[2]提到使用fs.readFileSync读取证书。这可能涉及到在代码中如何加载证书和密钥,这部分需要强调安全存储和正确加载的方法。 还需要注意,微信支付API对敏感信息加密的要求,比如Wechatpay-Serial字段和签名算法。必须确保用户了解如何生成正确的签名,避免因签名错误导致接口调用失败。 另外,错误处理和异步通知也是关键部分。用户需要知道如何处理可能的错误码,以及如何验证回调通知的合法性,保证交易的安全性。 最后,生成相关问题时要围绕调用流程的细节,比如参数准备、签名生成、错误处理等,帮助用户深入理解可能遇到的难点和解决方案。</think>### 微信商家转账接口调用流程 微信商家转账零钱的API调用流程分为以下几个核心步骤,需严格遵守文档[^3]中的技术规范: --- #### **1. 前置条件准备** - **商户证书**:需加载`apiclient_key.pem`私钥文件(参考引用[2]的代码示例) ```javascript const key = fs.readFileSync('./config/apiclient_key.pem'); ``` - **接口权限**:开通“商家转账零钱”功能并配置IP白名单 - **敏感信息加密**:需使用微信支付平台证书加密敏感字段(如用户姓名) --- #### **2. 请求头构造** 必须包含以下字段(全部为必填项): ```http Authorization: 签名值(通过RSA算法生成) Accept: application/json Content-Type: application/json Wechatpay-Serial: 微信支付平台证书序列号 ``` --- #### **3. 签名生成流程(关键步骤)** 1. 构造请求方法+URL,如:`POST /v3/transfer/batches` 2. 生成时间戳(Unix时间戳)和随机字符串 3. 拼接签名串: ``` 请求方法\n URL路径\n 时间戳\n 随机串\n 请求体\n ``` 4. 使用商户私钥进行SHA256-RSA签名 ```javascript const signStr = `${method}\n${url}\n${timestamp}\n${nonce}\n${body}\n`; const signature = crypto.createSign('RSA-SHA256').update(signStr).sign(key, 'base64'); ``` --- #### **4. 请求体参数示例** ```json { "appid": "wx1234567890abcdef", "out_batch_no": "BATCH_202308010001", "batch_name": "8月薪资发放", "batch_remark": "2023年8月工资", "total_amount": 100000, "total_num": 1, "transfer_detail_list": [ { "out_detail_no": "DETAIL_001", "transfer_amount": 100000, "transfer_remark": "工资", "openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o", "user_name": "张三" } ] } ``` --- #### **5. 异步通知处理** - 微信支付会通过回调通知转账结果 - 需验证签名合法性并返回应答: ```json { "code": "SUCCESS", "message": "成功" } ``` --- #### **6. 错误处理要点** - HTTP状态码200但业务错误时,检查`error.code`字段 - 常见错误: - `PARAM_ERROR`: 参数格式错误 - `SIGN_ERROR`: 签名验证失败 - `FREQ_LIMIT`: 接口调用频率超限 ---
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值