php
<?php
namespace fast\wx;
class AesUtil {
/**
* AES key(PEM格式)
*
* @var string
*/
private $aesKey;
const KEY_LENGTH_BYTE = 32;
const AUTH_TAG_LENGTH_BYTE = 16;
/**
* 错误信息
* @var string
*/
private $errorInfo = '';
/**
* Constructor
*
* @param string $aesKey 32字节的原始AES密钥(非PEM格式)
* @throws Exception
*/
public function __construct($aesKey) {
// 验证密钥是否为空
if (empty($aesKey)) {
throw new \Exception('AES密钥不能为空');
}
// 验证密钥长度
$keyLength = strlen($aesKey);
if ($keyLength != self::KEY_LENGTH_BYTE) {
throw new \Exception('无效的ApiV3Key,长度应为' . self::KEY_LENGTH_BYTE . '个字节,实际为' . $keyLength . '个字节');
}
$this->aesKey = $aesKey;
}
/**
* 获取最后发生的错误信息
*
* @return string
*/
public function getLastError() {
return $this->errorInfo;
}
/**
* 解密回调数据(修复密钥格式后)
*/
public function decryptToString($associatedData, $nonceStr, $ciphertext) {
$this->errorInfo = '';
// Base64解码密文
$decodedCiphertext = base64_decode($ciphertext);
if ($decodedCiphertext === false) {
$this->errorInfo = '密文Base64解码失败';
return false;
}
// 分离密文和认证标签
$ctext = substr($decodedCiphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
$authTag = substr($decodedCiphertext, -self::AUTH_TAG_LENGTH_BYTE);
// 使用PEM格式密钥解密
$result = openssl_decrypt(
$ctext,
'aes-256-gcm',
$this->aesKey,
OPENSSL_RAW_DATA,
$nonceStr,
$authTag,
$associatedData
);
if ($result === false) {
$errors = [];
while ($error = openssl_error_string()) {
$errors[] = $error;
}
$this->errorInfo = 'OpenSSL解密失败: ' . implode('; ', $errors);
return false;
}
return $result;
}
}
?>
调用
php
public function batches_notify(){
$encryptMsg = file_get_contents('php://input');
// 记录原始数据
file_put_contents(ROOT_PATH.'/runtime/batches_notify'.date('Ym').'.txt',
'原始数据: '.$encryptMsg.PHP_EOL.'时间: '.date('Y-m-d H:i:s').PHP_EOL.PHP_EOL, FILE_APPEND);
// $encryptMsg = '{"id":"a9102053-a245-5376-804e-2ae543187dbc","create_time":"2025-09-28T13:56:55+08:00","resource_type":"encrypt-resource","event_type":"MCHTRANSFER.BATCH.FINISHED","summary":"商家转账批次完成通知","resource":{"original_type":"mch_payment","algorithm":"AEAD_AES_256_GCM","ciphertext":"2obO8Q5Jl9yJZwEo+Cv5tvH65CST1pgrPSGKafizGWTKCwdK08BJLcAyrN9/coOeeb/YTbSUovoy6kpDBv0S/w3HZa3ASj/iUexKv26WMqk7uabUsWQVHxHcs/mm5jrCTnx+4V7CYfacinUx9iLieF5pczpJ38u6sQY3YQ6x7DA5kFSmtgtfwfHEGBXG84kZmlkmYwpt778kI9h85X497o48mbb/gkpife4l9A==","associated_data":"mch_payment","nonce":"lqe0lcxxxxxx"}}';
$data = json_decode($encryptMsg, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return json(['code'=>'FAIL','message'=>'JSON解析错误']);
}
// 验证数据格式
if (!isset($data['resource']['ciphertext'])) {
return json(['code'=>'FAIL','message'=>'回调数据格式不正确']);
}
$wxpay = Config::get('site.wxpay');
$wxpayV3Key = $wxpay['wxpayV3Key'] ?? '';//APIv3密钥
// 解密数据
$aesUtil = new AesUtil($wxpayV3Key);
$decryptedData = $aesUtil->decryptToString(
$data['resource']['associated_data'] ?? '',
$data['resource']['nonce'] ?? '',
$data['resource']['ciphertext']
);
if ($decryptedData === false) {
return json(['code'=>'FAIL','message'=>'数据解密失败']);
}
// 解析业务数据
$transferData = json_decode($decryptedData, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return json(['code'=>'FAIL','message'=>'业务数据JSON解析错误']);
}
// 记录解密数据
file_put_contents(ROOT_PATH.'/runtime/batches_notify_data'.date('Ym').'.txt',
'解密数据: '.$decryptedData.PHP_EOL.'时间: '.date('Y-m-d H:i:s').PHP_EOL.PHP_EOL, FILE_APPEND);
Db::startTrans();
try {
$order_id = $transferData['out_batch_no'] ?? '';
$batch_id = $transferData['batch_id'] ?? '';
$tixian = db('tixian')->where('order_id', $order_id)->find();
// 逻辑处理
} catch (\Exception $e) {
Db::rollback();
file_put_contents(ROOT_PATH.'/runtime/batches_notify_transfer_error'.date('Ym').'.txt',
'错误: '.$e->getMessage().PHP_EOL.'时间: '.date('Y-m-d H:i:s').PHP_EOL.PHP_EOL, FILE_APPEND);
return json(['code'=>'FAIL','message'=>'系统异常']);
}
}