php微信商家转账回调通知数据解密

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'=>'系统异常']);
        }
    }
相关推荐
q***06293 小时前
ThinkPHP和PHP的区别
开发语言·php
Protein_zmm5 小时前
第一章 计算机网络和因特网(下)
服务器·计算机网络·php
q***9947 小时前
IPV6公网暴露下的OPENWRT防火墙安全设置(只允许访问局域网中指定服务器指定端口其余拒绝)
服务器·安全·php
u***u6858 小时前
PHP在电商中的WooCommerce
开发语言·php
冠希陈、8 小时前
PHP 过滤敏感词(含类库)
开发语言·php·内容敏感词
v***87049 小时前
QoS质量配置
开发语言·智能路由器·php
JaguarJack10 小时前
不用 Web 服务器也能跑 PHP?这事比你想的有意思
php·服务端
一生要强的ymy12 小时前
Polar PHP是世界上最好的语言(困难)
开发语言·php
胡八一1 天前
解决PHP未检测到您服务器环境的sqlite3数据库扩展报错
服务器·数据库·php
名字不相符1 天前
攻防世界WEB难度一(个人记录)
学习·php·web·萌新