微信企業付款到零錢(紅包)接口:https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack,需雙向證書認證、XML 簽名、POST 請求。
<?php
class WechatRedPacket {
// 接口配置
private $url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack';
// 商戶配置(建議從配置文件讀取)
private $mch_id;
private $appid;
private $send_name;
private $total_num = 1;
private $wishing;
private $act_name;
private $remark;
private $api_key; // API 密鑰(簽名用)
private $cert_path; // 證書路徑
private $key_path; // 私鑰路徑
private $rootca_path; // CA 證書路徑
// 運行時變量
private $nonce_str;
private $client_ip;
public function __construct(array $config) {
$this->mch_id = $config['mch_id'];
$this->appid = $config['appid'];
$this->send_name = $config['send_name'];
$this->wishing = $config['wishing'];
$this->act_name = $config['act_name'];
$this->remark = $config['remark'];
$this->api_key = $config['api_key'];
$this->cert_path = $config['cert_path'];
$this->key_path = $config['key_path'];
$this->rootca_path = $config['rootca_path'];
$this->client_ip = $_SERVER['SERVER_ADDR'] ?? '127.0.0.1';
$this->nonce_str = $this->generateNonceStr(32);
}
// 發送紅包主入口
public function send(string $mch_billno, string $openid, int $total_amount): array {
$sign = $this->makeSign(compact('mch_billno', 'openid', 'total_amount'));
$data = [
'nonce_str' => $this->nonce_str,
'mch_billno' => $mch_billno, // 商戶訂單號:mch_id + ymd + 10位流水
'mch_id' => $this->mch_id,
'wxappid' => $this->appid,
'send_name' => $this->send_name,
're_openid' => $openid, // 用戶 openid
'total_amount'=> $total_amount, // 金額(分)
'total_num' => $this->total_num,
'wishing' => $this->wishing,
'client_ip' => $this->client_ip,
'act_name' => $this->act_name,
'remark' => $this->remark,
'sign' => $sign,
];
$xml = $this->arrayToXml($data);
$response = $this->curlPostSsl($this->url, $xml);
return $this->xmlToArray($response);
}
// 生成簽名(按鍵名 ASCII 排序拼接 + key)
private function makeSign(array $params): string {
// 過濾空值並按鍵名排序
$params = array_filter($params, fn($v) => $v !== '');
ksort($params);
$stringA = '';
foreach ($params as $k => $v) {
$stringA .= "{$k}={$v}&";
}
$stringSignTemp = $stringA . "key={$this->api_key}";
return strtoupper(md5($stringSignTemp));
}
// 數組轉 XML(支持 CDATA)
private function arrayToXml(array $data): string {
$xml = "";
foreach ($data as $k => $v) {
if (is_numeric($v)) {
$xml .= "<{$k}>{$v}{$k}>";
} else {
$xml .= "<{$k}>{$k}>";
}
}
$xml .= " ";
return $xml;
}
// cURL 雙向證書請求
private function curlPostSsl(string $url, string $xml, int $timeout = 30): string {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $xml,
CURLOPT_SSL_VERIFYPEER => false, // 生產環境建議 true 並配置 CA
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_SSLCERTTYPE => 'PEM',
CURLOPT_SSLCERT => $this->cert_path,
CURLOPT_SSLKEYTYPE => 'PEM',
CURLOPT_SSLKEY => $this->key_path,
CURLOPT_CAINFO => $this->rootca_path,
]);
$response = curl_exec($ch);
if ($response === false) {
throw new \RuntimeException("cURL Error: " . curl_error($ch));
}
curl_close($ch);
return $response;
}
// XML 轉數組
private function xmlToArray(string $xml): array {
libxml_disable_entity_loader(true);
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
}
// 生成隨機字符串
private function generateNonceStr(int $length = 32): string {
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
$str = '';
for ($i = 0; $i < $length; $i++) {
$str .= $chars[random_int(0, strlen($chars) - 1)];
}
return $str;
}
}
$config = [
'mch_id' => '1234567890',
'appid' => 'wx8888888888888888',
'send_name' => '測試商戶',
'wishing' => '恭喜發財',
'act_name' => '新年紅包',
'remark' => '測試發送',
'api_key' => 'your_api_key_32_chars',
'cert_path' => '/path/to/apiclient_cert.pem',
'key_path' => '/path/to/apiclient_key.pem',
'rootca_path' => '/path/to/rootca.pem',
];
$redPacket = new WechatRedPacket($config);
// 商戶訂單號格式:mch_id + Ymd + 10位流水
$mch_billno = $config['mch_id'] . date('Ymd') . str_pad(random_int(1, 9999999999), 10, '0', STR_PAD_LEFT);
$openid = 'oUpF8uMuAJO_M2pxb1Q9zNjWeSs0'; // 用戶 openid
$amount = 100; // 1.00 元 = 100 分
try {
$result = $redPacket->send($mch_billno, $openid, $amount);
print_r($result);
/* 成功返回示例:
Array (
[return_code] => SUCCESS
[result_code] => SUCCESS
[mch_billno] => 1234567890202401150000000001
[send_listid] => 1000000000202401150000000001
)
*/
} catch (\Exception $e) {
echo "發送失敗: " . $e->getMessage();
}
| 項目 | 要求 |
|---|---|
| libcurl 版本 | ≥ 7.20.1 支持雙向證書 |
| 證書格式 | PEM 格式,含申請商戶號時下載的證書 |
| 金額單位 | 分(整數),1 元 = 100 分 |
| 商戶訂單號 | 唯一,格式:mch_id + Ymd + 10位數字 |
| IP 白名單 | 商戶平台需配置調用服務器 IP |
| 單次限額 | 普通紅包 ≤ 200 元,裂變紅包 ≤ 500 元 |
SEND_FAILED:系統錯誤,重試NOTENOUGH:餘額不足PARAM_ERROR:參數錯誤(檢查簽名、金額、openid)OPENID_ERROR:openid 不屬於該公眾號SIGN_ERROR:簽名驗證失敗(檢查 API Key、參數排序)此封裝類結構清晰、異常處理完善,可直接集成到 Laravel、ThinkPHP、Yii 等框架的 Service 層中。