Files
soul/开发文档/5、接口/提现功能完整技术文档.md
卡若 afc2376e96 v1.19 全面改版:VIP会员系统、我的收益、创业老板排行、阅读量排序
- 后端: users表新增VIP字段, 4个VIP API (purchase/status/profile/members)
- 后端: hot接口改按user_tracks阅读量排序
- 后端: orders表支持vip产品类型, migrate新增vip_fields迁移
- 小程序「我的」: 推广中心改为我的收益, 头像VIP标识, VIP入口卡片
- 小程序「我的」: 最近阅读显示真实章节名称
- 小程序首页: 去掉内容概览, 新增创业老板排行(4列网格)
- 小程序首页: 精选推荐从hot接口获取, goToRead增加track记录
- 新增页面: VIP详情页, 会员详情页
- 开发文档精简为10个标准目录, 创建SKILL.md, 需求日志规范化

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 14:07:41 +08:00

26 KiB
Raw Blame History

提现功能技术文档微信支付API集成

文档说明

本文档专注于微信支付商家转账到零钱API的集成方法,包括:

  • 微信支付官方API文档
  • 签名生成算法
  • 加密解密算法
  • 完整代码实现
  • 测试验证方法

适用场景:实现用户提现功能,将资金从商户号转账到用户微信零钱。


目录

  1. 业务场景
  2. 微信支付官方API文档
  3. 前置准备
  4. API集成
  5. 签名算法
  6. 加密解密
  7. 代码实现
  8. 测试验证

业务场景

典型流程

用户申请提现
    ↓
系统审核通过
    ↓
调用微信支付【商家转账到零钱API】
    ↓
微信返回处理中(PROCESSING)
    ↓
微信异步处理(7-15秒)
    ↓
微信【主动回调】通知转账结果
    ↓
系统接收回调,验签、解密
    ↓
更新提现状态
    ↓
用户确认收款

关键步骤

  1. 发起转账调用微信API发起转账
  2. 接收回调:接收微信异步通知
  3. 验证签名:验证回调的真实性
  4. 解密数据:解密回调中的加密数据
  5. 查询状态:主动查询转账状态

微信支付官方API文档

核心API

API名称 官方文档地址
🔥 商家转账到零钱 https://pay.weixin.qq.com/doc/v3/merchant/4012716434
📋 查询转账单(商户单号) https://pay.weixin.qq.com/doc/v3/merchant/4012716456
📋 查询转账单(微信单号) https://pay.weixin.qq.com/doc/v3/merchant/4012716457
🔐 签名生成与验证 https://pay.weixin.qq.com/doc/v3/merchant/4013053249
🔒 敏感信息加密 https://pay.weixin.qq.com/doc/v3/merchant/4012070130
🔓 回调通知解密 https://pay.weixin.qq.com/doc/v3/merchant/4012071382
📝 转账场景报备 https://pay.weixin.qq.com/doc/v3/merchant/4012716437
错误码查询 https://pay.weixin.qq.com/doc/v3/merchant/4012070193
📜 平台证书管理 https://pay.weixin.qq.com/doc/v3/merchant/4012154180

开发指引


前置准备

1. 获取配置信息

登录微信商户平台:https://pay.weixin.qq.com

配置项 说明 获取路径
商户号(mch_id) 微信支付商户号 账户中心 → 商户信息
APIv3密钥(api_v3_key) 32字节密钥用于加密解密 账户中心 → API安全 → 设置APIv3密钥
商户私钥(apiclient_key.pem) 用于请求签名 账户中心 → API安全 → 申请证书
证书序列号(cert_serial_no) 商户证书标识 从证书文件提取
平台证书(wechat_pay_pub_key) 用于验证回调签名 下载或通过API获取
小程序AppId 小程序标识 小程序管理后台

2. 提取证书序列号

使用OpenSSL命令

openssl x509 -in apiclient_cert.pem -noout -serial

输出:

serial=4A1DB62CD5C9BE0B6FC51C30621D6F99686E75C5

使用PHP

<?php
$certContent = file_get_contents('apiclient_cert.pem');
$certData = openssl_x509_parse($certContent);
echo strtoupper(dechex($certData['serialNumber']));
?>

3. 配置IP白名单

路径:微信商户平台 → 账户中心 → API安全 → IP配置

添加服务器公网IP地址。

获取服务器IP

curl ifconfig.me

4. 配置转账场景

路径:微信商户平台 → 产品中心 → 商家转账到零钱 → 前往功能

可选场景:

  • 1000:现金营销
  • 1005:营销活动

5. 环境要求

  • PHP >= 7.0
  • OpenSSL 扩展(必须)
  • cURL 扩展(必须)
  • JSON 扩展(必须)
  • TLS 1.2+

检查环境

php -v
php -m | grep openssl
php -m | grep curl

API集成

1. 商家转账到零钱API

基本信息

  • 接口地址https://api.mch.weixin.qq.com/v3/transfer/batches
  • 请求方法POST
  • Content-Typeapplication/json

请求头

Authorization: WECHATPAY2-SHA256-RSA2048 mchid="商户号",nonce_str="随机字符串",signature="签名",timestamp="时间戳",serial_no="证书序列号"
Content-Type: application/json
Accept: application/json
User-Agent: YourApp/1.0

请求参数

{
  "appid": "wx6489c26045912fe1",
  "out_batch_no": "BATCH202601291234567890",
  "batch_name": "提现",
  "batch_remark": "用户提现",
  "total_amount": 5000,
  "total_num": 1,
  "transfer_detail_list": [
    {
      "out_detail_no": "TX202601291234567890",
      "transfer_amount": 5000,
      "transfer_remark": "提现",
      "openid": "odq3g5IOG-Z1WLpbeG_amUme8EZk"
    }
  ],
  "transfer_scene_id": "1005",
  "transfer_scene_report_infos": [
    {
      "info_type": "岗位类型",
      "info_content": "兼职人员"
    },
    {
      "info_type": "报酬说明",
      "info_content": "当日兼职费"
    }
  ]
}

参数说明

字段 类型 必填 说明
appid string 小程序AppId
out_batch_no string 商户批次单号,商户下唯一
batch_name string 批次名称
batch_remark string 批次备注
total_amount integer 转账总金额,单位:
total_num integer 转账总笔数
transfer_detail_list array 转账明细列表
transfer_scene_id string 转账场景ID1000或1005
transfer_scene_report_infos array 场景报备信息

transfer_detail_list说明

字段 类型 必填 说明
out_detail_no string 商户明细单号
transfer_amount integer 转账金额,单位:
transfer_remark string 转账备注
openid string 收款用户OpenId

场景报备信息场景ID=1005

[
  {
    "info_type": "岗位类型",
    "info_content": "兼职人员"
  },
  {
    "info_type": "报酬说明",
    "info_content": "当日兼职费"
  }
]

重要

  • info_type 必须是固定值
  • 金额单位是元 * 100

响应数据

成功响应

{
  "out_batch_no": "BATCH202601291234567890",
  "batch_id": "1030000071100999991182020050700019480001",
  "create_time": "2026-01-29T12:30:00+08:00",
  "batch_status": "PROCESSING"
}

字段说明

字段 说明
out_batch_no 商户批次单号
batch_id 微信批次单号
create_time 批次创建时间
batch_status 批次状态PROCESSING/SUCCESS/FAIL

失败响应

{
  "code": "PARAM_ERROR",
  "message": "参数错误"
}

2. 查询转账单API

按商户单号查询

接口地址

GET https://api.mch.weixin.qq.com/v3/transfer/batches/batch-id/{batch_id}/details/detail-id/{detail_id}

路径参数

  • batch_id商户批次单号需URL编码
  • detail_id商户明细单号需URL编码

示例

GET /v3/transfer/batches/batch-id/BATCH202601291234567890/details/detail-id/TX202601291234567890

响应示例

{
  "mchid": "1318592501",
  "out_batch_no": "BATCH202601291234567890",
  "batch_id": "1030000071100999991182020050700019480001",
  "out_detail_no": "TX202601291234567890",
  "detail_id": "1040000071100999991182020050700019500100",
  "detail_status": "SUCCESS",
  "transfer_amount": 5000,
  "transfer_remark": "提现",
  "openid": "odq3g5IOG-Z1WLpbeG_amUme8EZk",
  "initiate_time": "2026-01-29T12:30:00+08:00",
  "update_time": "2026-01-29T12:30:15+08:00"
}

状态说明

detail_status 说明
PROCESSING 转账中
SUCCESS 转账成功
FAIL 转账失败

3. 转账结果通知(回调)

回调触发

当转账状态变更时,微信支付会主动向配置的 notify_url 发送POST请求。

回调请求头

Wechatpay-Signature: 签名值
Wechatpay-Timestamp: 1769653396
Wechatpay-Nonce: R0PDA5lOV3IMrBjrvbCH5U4L3Lb0gg8L
Wechatpay-Serial: 642B2B33557205BA79A1CFF08EA2A2478D67BD63
Wechatpay-Signature-Type: WECHATPAY2-SHA256-RSA2048
Content-Type: application/json

回调请求体(加密)

{
  "id": "cb29e425-ca17-59fb-8045-8e5b58917154",
  "create_time": "2026-01-29T10:23:11+08:00",
  "resource_type": "encrypt-resource",
  "event_type": "MCHTRANSFER.BILL.FINISHED",
  "summary": "商家转账单据终态通知",
  "resource": {
    "original_type": "mch_payment",
    "algorithm": "AEAD_AES_256_GCM",
    "ciphertext": "加密的数据...",
    "associated_data": "mch_payment",
    "nonce": "随机字符串"
  }
}

解密后的数据

{
  "mch_id": "1318592501",
  "out_bill_no": "TX202601291234567890",
  "transfer_bill_no": "1330000114850082601290057112302122",
  "transfer_amount": 5000,
  "state": "SUCCESS",
  "openid": "odq3g5IOG-Z1WLpbeG_amUme8EZk",
  "create_time": "2026-01-29T12:30:00+08:00",
  "update_time": "2026-01-29T12:30:15+08:00"
}

state状态说明

state 说明
PROCESSING 转账中
SUCCESS 转账成功
FAIL 转账失败
WAIT_USER_CONFIRM 待用户确认
TRANSFERING 正在转账

回调响应

处理完成后,返回给微信:

{
  "code": "SUCCESS"
}

签名算法

1. 签名生成(请求签名)

签名串格式

请求方法\n
请求URL路径\n
请求时间戳\n
随机字符串\n
请求报文主体\n

示例

POST
/v3/transfer/batches
1234567890
RandomString123456
{"appid":"wx6489c26045912fe1"}

重要:每部分末尾都有 \n 换行符。

签名步骤

  1. 构建签名串
  2. 使用商户私钥进行SHA256withRSA签名
  3. 对签名结果进行Base64编码

PHP实现

function buildSignature($method, $url, $timestamp, $nonce, $body, $privateKeyPath) {
    // 1. 构建签名串
    $signStr = $method . "\n" 
             . $url . "\n" 
             . $timestamp . "\n" 
             . $nonce . "\n" 
             . $body . "\n";
    
    // 2. 加载私钥
    $privateKeyContent = file_get_contents($privateKeyPath);
    $privateKeyResource = openssl_pkey_get_private($privateKeyContent);
    
    // 3. 使用私钥签名
    openssl_sign($signStr, $signature, $privateKeyResource, 'sha256WithRSAEncryption');
    
    // 4. Base64编码
    return base64_encode($signature);
}

构建Authorization头

function buildAuthorization($mchId, $timestamp, $nonce, $signature, $serialNo) {
    return sprintf(
        'WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",signature="%s",timestamp="%d",serial_no="%s"',
        $mchId,
        $nonce,
        $signature,
        $timestamp,
        $serialNo
    );
}

2. 签名验证(回调验签)

验签串格式

时间戳\n
随机字符串\n
请求报文主体\n

示例

1769653396
R0PDA5lOV3IMrBjrvbCH5U4L3Lb0gg8L
{"id":"cb29e425-ca17-59fb-8045-8e5b58917154",...}

PHP实现

function verifySignature($timestamp, $nonce, $body, $signature, $publicKeyPath) {
    // 1. 构建验签串
    $verifyStr = $timestamp . "\n" 
               . $nonce . "\n" 
               . $body . "\n";
    
    // 2. Base64解码签名
    $signatureDecode = base64_decode($signature);
    
    // 3. 加载平台公钥
    $publicKeyContent = file_get_contents($publicKeyPath);
    $publicKeyResource = openssl_pkey_get_public($publicKeyContent);
    
    // 4. 验证签名
    $result = openssl_verify(
        $verifyStr, 
        $signatureDecode, 
        $publicKeyResource, 
        'sha256WithRSAEncryption'
    );
    
    return $result === 1;  // 1表示验证成功
}

重要:验签使用的是微信支付平台公钥,不是商户私钥!


加密解密

回调数据解密

算法信息

  • 算法AEAD_AES_256_GCM
  • 密钥APIv3密钥32字节
  • 密文格式:实际密文 + 认证标签16字节

解密步骤

  1. 提取加密数据ciphertext、nonce、associated_data
  2. Base64解码密文
  3. 分离密文和认证标签最后16字节
  4. 使用AES-256-GCM解密
  5. 解析JSON数据

PHP实现

function decryptCallbackData($ciphertext, $nonce, $associatedData, $apiV3Key) {
    // 1. 检查APIv3密钥长度必须32字节
    if (strlen($apiV3Key) !== 32) {
        throw new Exception('APIv3密钥长度必须为32字节');
    }
    
    // 2. Base64解码密文
    $ciphertextDecoded = base64_decode($ciphertext);
    
    // 3. 分离密文和认证标签
    $authTag = substr($ciphertextDecoded, -16);
    $ctext = substr($ciphertextDecoded, 0, -16);
    
    // 4. 使用AES-256-GCM解密
    $decrypted = openssl_decrypt(
        $ctext,                 // 密文
        'aes-256-gcm',          // 算法
        $apiV3Key,              // 密钥
        OPENSSL_RAW_DATA,       // 选项
        $nonce,                 // 随机串
        $authTag,               // 认证标签
        $associatedData         // 附加数据
    );
    
    if ($decrypted === false) {
        throw new Exception('解密失败');
    }
    
    // 5. 解析JSON
    return json_decode($decrypted, true);
}

使用示例

$resource = $callbackData['resource'];
$decrypted = decryptCallbackData(
    $resource['ciphertext'],
    $resource['nonce'],
    $resource['associated_data'],
    'wx3e31b068be59ddc131b068be59ddc2'  // APIv3密钥
);

代码实现

完整的微信支付转账类

<?php

class WechatPayTransfer
{
    private $mchId;
    private $appId;
    private $apiV3Key;
    private $privateKey;
    private $certSerialNo;
    
    public function __construct($config)
    {
        $this->mchId = $config['mch_id'];
        $this->appId = $config['app_id'];
        $this->apiV3Key = $config['api_v3_key'];
        $this->certSerialNo = $config['cert_serial_no'];
        
        // 加载私钥
        $privateKeyContent = file_get_contents($config['private_key']);
        $this->privateKey = openssl_pkey_get_private($privateKeyContent);
    }
    
    /**
     * 发起转账
     */
    public function createTransfer($params)
    {
        $url = '/v3/transfer/batches';
        $method = 'POST';
        
        // 构建请求数据
        $data = [
            'appid' => $this->appId,
            'out_batch_no' => 'BATCH' . date('YmdHis') . mt_rand(1000, 9999),
            'batch_name' => $params['batch_name'] ?? '提现',
            'batch_remark' => $params['batch_remark'] ?? '用户提现',
            'total_amount' => $params['transfer_amount'],
            'total_num' => 1,
            'transfer_detail_list' => [
                [
                    'out_detail_no' => $params['out_detail_no'],
                    'transfer_amount' => $params['transfer_amount'],
                    'transfer_remark' => $params['transfer_remark'],
                    'openid' => $params['openid'],
                ]
            ],
            'transfer_scene_id' => $params['transfer_scene_id'] ?? '1005',
        ];
        
        // 添加场景报备信息
        if (!empty($params['transfer_scene_report_infos'])) {
            $data['transfer_scene_report_infos'] = $params['transfer_scene_report_infos'];
        }
        
        $body = json_encode($data, JSON_UNESCAPED_UNICODE);
        
        // 生成签名
        $timestamp = time();
        $nonce = $this->generateNonce();
        $signature = $this->buildSignature($method, $url, $timestamp, $nonce, $body);
        
        // 构建Authorization
        $authorization = $this->buildAuthorization($timestamp, $nonce, $signature);
        
        // 发送请求
        return $this->request($method, $url, $body, $authorization);
    }
    
    /**
     * 查询转账单
     */
    public function queryTransfer($batchNo, $detailNo)
    {
        $url = "/v3/transfer/batches/batch-id/" . urlencode($batchNo) 
             . "/details/detail-id/" . urlencode($detailNo);
        $method = 'GET';
        
        $timestamp = time();
        $nonce = $this->generateNonce();
        $signature = $this->buildSignature($method, $url, $timestamp, $nonce, '');
        $authorization = $this->buildAuthorization($timestamp, $nonce, $signature);
        
        return $this->request($method, $url, '', $authorization);
    }
    
    /**
     * 验证回调签名
     */
    public function verifyCallback($headers, $body, $publicKey)
    {
        $timestamp = $headers['wechatpay-timestamp'];
        $nonce = $headers['wechatpay-nonce'];
        $signature = $headers['wechatpay-signature'];
        
        $verifyStr = $timestamp . "\n" . $nonce . "\n" . $body . "\n";
        $signatureDecode = base64_decode($signature);
        
        $publicKeyContent = file_get_contents($publicKey);
        $publicKeyResource = openssl_pkey_get_public($publicKeyContent);
        
        $result = openssl_verify($verifyStr, $signatureDecode, $publicKeyResource, 'sha256WithRSAEncryption');
        
        return $result === 1;
    }
    
    /**
     * 解密回调数据
     */
    public function decryptCallbackResource($resource)
    {
        $ciphertext = $resource['ciphertext'];
        $nonce = $resource['nonce'];
        $associatedData = $resource['associated_data'];
        
        if (strlen($this->apiV3Key) !== 32) {
            throw new \Exception('APIv3密钥长度必须为32字节');
        }
        
        $ciphertextDecoded = base64_decode($ciphertext);
        $authTag = substr($ciphertextDecoded, -16);
        $ctext = substr($ciphertextDecoded, 0, -16);
        
        $decrypted = openssl_decrypt(
            $ctext,
            'aes-256-gcm',
            $this->apiV3Key,
            OPENSSL_RAW_DATA,
            $nonce,
            $authTag,
            $associatedData
        );
        
        if ($decrypted === false) {
            throw new \Exception('解密失败');
        }
        
        return json_decode($decrypted, true);
    }
    
    /**
     * 生成签名
     */
    private function buildSignature($method, $url, $timestamp, $nonce, $body)
    {
        $signStr = $method . "\n" 
                 . $url . "\n" 
                 . $timestamp . "\n" 
                 . $nonce . "\n" 
                 . $body . "\n";
        
        openssl_sign($signStr, $signature, $this->privateKey, 'sha256WithRSAEncryption');
        
        return base64_encode($signature);
    }
    
    /**
     * 构建Authorization头
     */
    private function buildAuthorization($timestamp, $nonce, $signature)
    {
        return sprintf(
            'WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",signature="%s",timestamp="%d",serial_no="%s"',
            $this->mchId,
            $nonce,
            $signature,
            $timestamp,
            $this->certSerialNo
        );
    }
    
    /**
     * 生成随机字符串
     */
    private function generateNonce($length = 32)
    {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $nonce = '';
        for ($i = 0; $i < $length; $i++) {
            $nonce .= $chars[mt_rand(0, strlen($chars) - 1)];
        }
        return $nonce;
    }
    
    /**
     * 发送HTTP请求
     */
    private function request($method, $url, $body, $authorization)
    {
        $fullUrl = 'https://api.mch.weixin.qq.com' . $url;
        
        $headers = [
            'Authorization: ' . $authorization,
            'Content-Type: application/json',
            'Accept: application/json',
            'User-Agent: YourApp/1.0'
        ];
        
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $fullUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        
        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        }
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        $result = json_decode($response, true);
        
        if ($httpCode >= 200 && $httpCode < 300) {
            return ['success' => true, 'data' => $result];
        } else {
            return [
                'success' => false,
                'error_code' => $result['code'] ?? 'UNKNOWN',
                'error_msg' => $result['message'] ?? '未知错误'
            ];
        }
    }
}

使用示例

1. 发起转账

// 初始化配置
$config = [
    'mch_id' => '1318592501',
    'app_id' => 'wx6489c26045912fe1',
    'api_v3_key' => 'wx3e31b068be59ddc131b068be59ddc2',
    'private_key' => '/path/to/apiclient_key.pem',
    'cert_serial_no' => '4A1DB62CD5C9BE0B6FC51C30621D6F99686E75C5',
];

$wechatPay = new WechatPayTransfer($config);

// 发起转账
$result = $wechatPay->createTransfer([
    'out_detail_no' => 'TX' . date('YmdHis') . mt_rand(1000, 9999),
    'transfer_amount' => 5000,  // 50元 = 5000分
    'transfer_remark' => '提现',
    'openid' => 'odq3g5IOG-Z1WLpbeG_amUme8EZk',
    'transfer_scene_id' => '1005',
    'transfer_scene_report_infos' => [
        ['info_type' => '岗位类型', 'info_content' => '兼职人员'],
        ['info_type' => '报酬说明', 'info_content' => '当日兼职费'],
    ],
]);

if ($result['success']) {
    echo "转账成功: " . json_encode($result['data']);
} else {
    echo "转账失败: " . $result['error_msg'];
}

2. 查询转账单

$result = $wechatPay->queryTransfer('BATCH202601291234567890', 'TX202601291234567890');

if ($result['success']) {
    echo "状态: " . $result['data']['detail_status'];
} else {
    echo "查询失败: " . $result['error_msg'];
}

3. 处理回调

// 接收回调
$headers = [
    'wechatpay-signature' => $_SERVER['HTTP_WECHATPAY_SIGNATURE'],
    'wechatpay-timestamp' => $_SERVER['HTTP_WECHATPAY_TIMESTAMP'],
    'wechatpay-nonce' => $_SERVER['HTTP_WECHATPAY_NONCE'],
    'wechatpay-serial' => $_SERVER['HTTP_WECHATPAY_SERIAL'],
];

$body = file_get_contents('php://input');
$callbackData = json_decode($body, true);

// 验证签名
$verified = $wechatPay->verifyCallback($headers, $body, '/path/to/wechat_pay_pub_key.pem');

if ($verified) {
    // 解密数据
    $decrypted = $wechatPay->decryptCallbackResource($callbackData['resource']);
    
    // 处理转账结果
    if ($decrypted['state'] === 'SUCCESS') {
        echo "转账成功: " . $decrypted['out_bill_no'];
    }
    
    // 返回成功
    echo json_encode(['code' => 'SUCCESS']);
} else {
    echo json_encode(['code' => 'FAIL', 'message' => '签名验证失败']);
}

测试验证

1. 签名生成测试

$method = 'POST';
$url = '/v3/transfer/batches';
$timestamp = time();
$nonce = 'RandomString123456';
$body = '{"appid":"wx6489c26045912fe1"}';

$signature = buildSignature($method, $url, $timestamp, $nonce, $body, 'apiclient_key.pem');

echo "签名: " . $signature . "\n";

2. 小额转账测试

// 测试金额0.01元 = 1分
$result = $wechatPay->createTransfer([
    'out_detail_no' => 'TEST' . time(),
    'transfer_amount' => 1,  // 1分
    'transfer_remark' => '测试',
    'openid' => 'test_openid',
    'transfer_scene_id' => '1005',
    'transfer_scene_report_infos' => [
        ['info_type' => '岗位类型', 'info_content' => '测试'],
        ['info_type' => '报酬说明', 'info_content' => '测试'],
    ],
]);

3. 解密测试

$resource = [
    'ciphertext' => 'xxx',
    'nonce' => 'xxx',
    'associated_data' => 'mch_payment',
];

try {
    $decrypted = decryptCallbackData(
        $resource['ciphertext'],
        $resource['nonce'],
        $resource['associated_data'],
        'wx3e31b068be59ddc131b068be59ddc2'
    );
    print_r($decrypted);
} catch (Exception $e) {
    echo "解密失败: " . $e->getMessage();
}

4. 常见问题

问题 原因 解决方法
签名验证失败 证书序列号错误 重新提取证书序列号
IP白名单错误 服务器IP未配置 添加到微信商户平台
解密失败 APIv3密钥错误 检查密钥长度32字节
场景报备错误 info_type不正确 使用固定值
余额不足 商户号余额不足 充值商户号

附录

A. 错误码对照表

https://pay.weixin.qq.com/doc/v3/merchant/4012070193

错误码 说明 处理建议
PARAM_ERROR 参数错误 检查请求参数格式
NOTENOUGH 商户余额不足 充值商户号
INVALID_REQUEST 不符合业务规则 检查业务逻辑
SYSTEM_ERROR 系统错误 稍后重试
FREQUENCY_LIMITED 频率限制 降低请求频率
APPID_MCHID_NOT_MATCH appid和mch_id不匹配 检查配置

B. 转账状态说明

状态 说明 处理方式
PROCESSING 转账中 等待回调或主动查询
SUCCESS 转账成功 完成流程
FAIL 转账失败 检查失败原因
WAIT_USER_CONFIRM 待用户确认 等待用户操作
TRANSFERING 正在转账 等待处理完成

C. 开发工具


文档版本v3.0纯微信支付API版
更新时间2026-01-29
适用场景:微信支付商家转账到零钱功能集成


总结

本文档提供了微信支付转账功能的完整集成方案:

3个核心API

  • 发起转账:POST /v3/transfer/batches
  • 查询转账:GET /v3/transfer/batches/batch-id/{batch_id}/details/detail-id/{detail_id}
  • 接收回调:异步通知

3个核心算法

  • 签名生成SHA256withRSA + Base64
  • 签名验证:使用平台公钥
  • 数据解密AEAD_AES_256_GCM

完整代码实现

  • WechatPayTransfer类可直接使用
  • 包含发起转账、查询、验签、解密全部功能

根据本文档可以快速集成微信支付转账功能。