微信支付商家转账常见问题及解决方案
1. 此 IP 地址不允许调用接口,请按开发指引设置
错误信息:
{
"code": "INVALID_REQUEST",
"message": "此 IP 地址不允许调用接口,请按开发指引设置"
}
解决方案: 需在商户平台设置接口 IP 白名单。
- 扫码登录商户平台。
- 进入路径:产品中心 -> 运营工具 -> 商家转账 -> 前往功能 -> 安全能力 -> 点击「接口 IP」。
- 添加开发者 IP 和服务器的 IP。如有 IPv6 地址也需一并填写。

2. APPID 不存在
错误信息:
{
"code": "INVALID_REQUEST",
"message": "APPID 不存在"
}
解决方案: 需将调用的小程序或公众号关联到当前商户号。
- 打开产品中心 -> AppID 账号管理 -> 账号关联。
- 进入授权申请页面提交申请。
- 在微信公众平台侧点击接受授权。
参考文档:APPID 授权管理功能介绍 - 微信支付商户平台

3. 你尚未获取该转账场景
错误信息:
{
"code": "INVALID_REQUEST",
"message": "你尚未获取该转账场景"
}
解决方案: 转账场景指转账的具体用途,不同场景对应不同的参数要求。需先申请并审核通过。
- 进入路径:产品中心 > 运营工具 > 商家转账到零钱 > 前往功能 > 转账场景。
- 选择对应场景并提交申请,等待审核。

4. 暂不支持展示当前传入的用户收款感知
错误信息:
{
"code": "INVALID_REQUEST",
"message": "暂不支持展示当前传入的用户收款感知"
}
解决方案:
用户收款感知(字段 user_recv_perception)用于在微信端展示款项来源提示(如'现金奖励'、'拉新红包'等)。各场景支持的内容不同,需严格对照官方文档填写。
例如佣金报酬场景(transfer_scene_id = 1005),仅支持【劳务报酬】、【报销款】、【企业补贴】、【开工利是】,不传则默认为【劳务报酬】。若填入不支持的内容会报错。

5. 未传入完整且对应的转账场景报备信息,请根据接口文档检查
错误信息:
{
"code": "PARAM_ERROR",
"message": "未传入完整且对应的转账场景报备信息,请根据接口文档检查"
}
解决方案:
转账场景报备信息(字段 transfer_scene_report_infos)用于向微信说明转账用途,符合政策才允许交易。不同场景所需参数不同,需包含转账原因、场景说明、奖励类型等。
以佣金报酬为例,必须传递岗位类型和报酬说明。

6. 商户运营账户资金不足
错误信息:
{
"code": "NOT_ENOUGH",
"message": "商户运营账户资金不足,充值后可以原单号发起重试,请勿更换商户单号"
}
解决方案: 需确保商户运营账户余额充足。注意充值至运营账户而非基本账户。

Java 调用示例
package co.yixiang.modules.transferToUser.utils;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import okhttp3.*;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.List;
/**
* 发起转账
*/
public class TransferToUser {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/fund-app/mch-transfer/transfer-bills";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数
TransferToUser client = new TransferToUser(
"12321321", // 商户号
"12321321321321312", // 商户 API 证书序列号
"E:\\Java_project_self\\privateKey.pem" // 商户 API 证书私钥文件路径
);
TransferToUserRequest request = new TransferToUserRequest();
request.appid = "wx213123213";
request.outBillNo = "plfk212DS" + System.currentTimeMillis();
request.transferSceneId = "1005";
request.openid = "21321321321213";
request.transferAmount = 10L;
request.transferRemark = "佣金提现到账";
request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php";
List<TransferSceneReportInfo> list = new ArrayList<>();
TransferSceneReportInfo info1 = new TransferSceneReportInfo();
info1.infoType = "岗位类型";
info1.infoContent = "分享有礼";
list.add(info1);
TransferSceneReportInfo info2 = new TransferSceneReportInfo();
info2.infoType = "报酬说明";
info2.infoContent = "用户推广获得佣金";
list.add(info2);
request.userRecvPerception = "劳务报酬";
request.transferSceneReportInfos = list;
try {
TransferToUserResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (Exception e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public TransferToUserResponse run(TransferToUserRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
return WXPayUtility.fromJson(respBody, TransferToUserResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
public TransferToUser(String mchid, String certificateSerialNo, String privateKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
}
@Data
public static class TransferToUserRequest {
@SerializedName("appid") public String appid;
@SerializedName("out_bill_no") public String outBillNo;
@SerializedName("transfer_scene_id") public String transferSceneId;
@SerializedName("openid") public String openid;
@SerializedName("user_name") public String userName;
@SerializedName("transfer_amount") public Long transferAmount;
@SerializedName("transfer_remark") public String transferRemark;
@SerializedName("notify_url") public String notifyUrl;
@SerializedName("user_recv_perception") public String userRecvPerception;
@SerializedName("transfer_scene_report_infos") public List<TransferSceneReportInfo> transferSceneReportInfos = new ArrayList<>();
}
@Data
public static class TransferToUserResponse {
@SerializedName("out_bill_no") public String outBillNo;
@SerializedName("transfer_bill_no") public String transferBillNo;
@SerializedName("create_time") public String createTime;
@SerializedName("state") public TransferBillStatus state;
@SerializedName("package_info") public String packageInfo;
}
@Data
public static class TransferSceneReportInfo {
@SerializedName("info_type") public String infoType;
@SerializedName("info_content") public String infoContent;
}
public enum TransferBillStatus {
@SerializedName("ACCEPTED") ACCEPTED,
@SerializedName("PROCESSING") PROCESSING,
@SerializedName("WAIT_USER_CONFIRM") WAIT_USER_CONFIRM,
@SerializedName("TRANSFERING") TRANSFERING,
@SerializedName("SUCCESS") SUCCESS,
@SerializedName("FAIL") FAIL,
@SerializedName("CANCELING") CANCELING,
@SerializedName("CANCELLED") CANCELLED
}
}

