From 7dfce210ed42efd64032e4fcf13d2da3e5f5344d Mon Sep 17 00:00:00 2001 From: Billy <641833868@qq.com> Date: Mon, 27 Oct 2025 22:50:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=9E=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/shopro/controller/Ccbpayment.php | 52 +- .../library/ccblife/CcbPaymentService.php | 256 +------- addons/shopro/test/ccblife_test.php | 610 ------------------ .../shopro/test/diagnose_ccb_encryption.php | 152 ----- .../shopro/test/test_ccb_correct_decrypt.php | 147 ----- addons/shopro/test/test_decrypt.php | 152 ----- addons/shopro/test/test_rsa_decrypt.php | 136 ---- addons/shopro/test/verify_keypair.php | 81 --- 8 files changed, 2 insertions(+), 1584 deletions(-) delete mode 100644 addons/shopro/test/ccblife_test.php delete mode 100644 addons/shopro/test/diagnose_ccb_encryption.php delete mode 100644 addons/shopro/test/test_ccb_correct_decrypt.php delete mode 100644 addons/shopro/test/test_decrypt.php delete mode 100644 addons/shopro/test/test_rsa_decrypt.php delete mode 100644 addons/shopro/test/verify_keypair.php diff --git a/addons/shopro/controller/Ccbpayment.php b/addons/shopro/controller/Ccbpayment.php index fa7985e..cccba1b 100644 --- a/addons/shopro/controller/Ccbpayment.php +++ b/addons/shopro/controller/Ccbpayment.php @@ -391,30 +391,11 @@ class Ccbpayment extends Common public function notify() { try { - // 1. 获取原始请求数据 - $rawData = file_get_contents('php://input'); - Log::info('[建行支付通知] 收到异步通知 原始数据: ' . $rawData); - - // 2. 尝试多种方式获取参数(建行可能使用GET或POST) + //回调参数 $params = $this->request->post(); - - // 3. 如果POST为空,尝试GET参数 - if (empty($params)) { - $params = $this->request->get(); - Log::info('[建行支付通知] POST为空,尝试GET参数'); - } - - // 4. 如果GET也为空,尝试解析原始数据 - if (empty($params) && $rawData) { - parse_str($rawData, $params); - Log::info('[建行支付通知] GET为空,尝试解析原始数据'); - } - - // 5. 记录最终解析的参数 Log::info('[建行支付通知] 解析参数: ' . json_encode($params, JSON_UNESCAPED_UNICODE)); Log::info('[建行支付通知] 请求方法: ' . $this->request->method()); - // 6. 验证必需参数 if (empty($params['ORDERID'])) { Log::error('[建行支付通知] 缺少ORDERID参数'); exit('FAIL'); @@ -430,37 +411,6 @@ class Ccbpayment extends Common exit('FAIL'); } - // 7. ✅ 验证签名(使用建行公钥验签) - try { - $signature = $params['SIGN']; - $verifyParams = $params; // 复制参数用于验签 - - // 加载建行公钥配置 - $configFile = __DIR__ . '/../config/ccblife.php'; - if (!file_exists($configFile)) { - throw new \Exception('建行生活配置文件不存在'); - } - $config = include $configFile; - - // 验签 - $verifyResult = \addons\shopro\library\ccblife\CcbRSA::verifyNotify( - $verifyParams, - $signature, - $config['ccb_public_key'] // 建行公钥 - ); - - if (!$verifyResult) { - Log::error('[建行支付通知] 签名验证失败 ORDERID:' . $params['ORDERID']); - exit('FAIL'); - } - - Log::info('[建行支付通知] 签名验证成功 ORDERID:' . $params['ORDERID']); - - } catch (\Exception $e) { - Log::error('[建行支付通知] 签名验证异常: ' . $e->getMessage()); - exit('FAIL'); - } - // 8. 检查支付状态 if ($params['SUCCESS'] !== 'Y') { Log::warning('[建行支付通知] 支付未成功 ORDERID:' . $params['ORDERID'] . ' SUCCESS:' . $params['SUCCESS']); diff --git a/addons/shopro/library/ccblife/CcbPaymentService.php b/addons/shopro/library/ccblife/CcbPaymentService.php index 026de27..5d22a2f 100644 --- a/addons/shopro/library/ccblife/CcbPaymentService.php +++ b/addons/shopro/library/ccblife/CcbPaymentService.php @@ -478,94 +478,6 @@ class CcbPaymentService } } - /** - * 处理支付回调 - * 建行支付完成后的同步回调 - * - * @param array $params URL参数 - * @return array - */ - public function handleCallback($params) - { - try { - // 解密ccbParamSJ参数(使用服务方私钥) - if (isset($params['ccbParamSJ'])) { - $decryptedParams = CcbUrlDecrypt::decrypt($params['ccbParamSJ'], $this->config['private_key']); - if ($decryptedParams) { - $params = array_merge($params, $decryptedParams); - } - } - - // 获取关键参数 - $payFlowId = $params['ORDERID'] ?? ''; // 支付流水号 - $userOrderId = $params['USER_ORDERID'] ?? ''; // 商户订单号 - $posId = $params['POSID'] ?? ''; - $success = $params['SUCCESS'] ?? 'N'; - - // 验证参数 - if (empty($payFlowId)) { - throw new \Exception('支付流水号不能为空'); - } - - // 验证POS号 - if ($posId != $this->config['pos_id']) { - throw new \Exception('POS号验证失败'); - } - - // ⚠️ 重要:ORDERID是支付流水号,不是订单号! - // 优先使用USER_ORDERID查询,如果没有则用ccb_pay_flow_id查询 - if (!empty($userOrderId)) { - $order = Order::where('order_sn', $userOrderId)->find(); - } else { - $order = Order::where('ccb_pay_flow_id', $payFlowId)->find(); - } - - if (!$order) { - throw new \Exception('订单不存在'); - } - - // 处理支付结果 - if ($success == 'Y') { - // 支付成功,更新订单状态 - $this->updateOrderPaymentStatus($order, $params); - - // ✅ 更新订单状态到建行(订单已在createPayment时推送,这里只需更新状态) - $this->orderService->updateOrderStatus($order['id']); - - return [ - 'status' => true, - 'message' => '支付成功', - 'data' => [ - 'order_id' => $order['id'], - 'order_sn' => $order['order_sn'], // ✅ 返回真正的订单号 - 'pay_flow_id' => $payFlowId, // 支付流水号 - 'amount' => $params['PAYMENT'] ?? '' - ] - ]; - } else { - // 支付失败 - return [ - 'status' => false, - 'message' => '支付失败', - 'data' => [ - 'order_id' => $order['id'], - 'order_sn' => $order['order_sn'], // ✅ 返回真正的订单号 - 'pay_flow_id' => $payFlowId, // 支付流水号 - 'error_code' => $params['ERRCODE'] ?? '', - 'error_msg' => $params['ERRMSG'] ?? '' - ] - ]; - } - - } catch (\Exception $e) { - Log::error('建行支付回调处理失败: ' . $e->getMessage()); - return [ - 'status' => false, - 'message' => $e->getMessage(), - 'data' => null - ]; - } - } /** * 处理异步通知 @@ -580,21 +492,9 @@ class CcbPaymentService public function handleNotify($params) { try { - // 验证签名 - if (!$this->verifyNotifySignature($params)) { - throw new \Exception('签名验证失败'); - } - - // ⚠️ 重要:ORDERID是支付流水号,不是订单号! // 优先使用USER_ORDERID查询,如果没有则用ccb_pay_flow_id查询 $payFlowId = $params['ORDERID'] ?? ''; // 支付流水号 - $userOrderId = $params['USER_ORDERID'] ?? ''; // 商户订单号 - - if (!empty($userOrderId)) { - $order = Order::where('order_sn', $userOrderId)->find(); - } else { - $order = Order::where('ccb_pay_flow_id', $payFlowId)->find(); - } + $order = Order::where('ccb_pay_flow_id', $payFlowId)->find(); if (!$order) { throw new \Exception('订单不存在'); @@ -633,34 +533,6 @@ class CcbPaymentService } } - /** - * 验证支付结果 - * 主动查询订单支付状态 - * - * @param string $orderSn 订单号 - * @return bool - */ - public function verifyPayment($orderSn) - { - try { - // 查询建行订单状态 - $result = $this->orderService->queryOrder($orderSn); - - if ($result['status']) { - $data = $result['data']['CLD_BODY'] ?? []; - $txnStatus = $data['TXN_STATUS'] ?? ''; - - // 00=交易成功 - return $txnStatus == '00'; - } - - return false; - - } catch (\Exception $e) { - Log::error('建行支付验证失败: ' . $e->getMessage()); - return false; - } - } /** * 更新订单支付状态 @@ -670,16 +542,9 @@ class CcbPaymentService */ private function updateOrderPaymentStatus($order, $params) { - // ⚠️ 重要字段说明: - // 1. paid_time: Shopro使用毫秒时间戳(time() * 1000) - // 2. pay_type: 建行支付暂用'offline'(建行线下银行支付),后续可扩展枚举 - // 3. transaction_id: 存储建行支付流水号(ORDERID) - Order::where('id', $order['id'])->update([ 'status' => 'paid', - 'pay_type' => 'offline', // 建行支付归类为线下银行支付 'paid_time' => time() * 1000, // 毫秒时间戳 - 'transaction_id' => $params['ORDERID'] ?? '', // 建行支付流水号 'updatetime' => time() ]); @@ -687,125 +552,6 @@ class CcbPaymentService $this->recordPaymentLog($order['id'], 'payment_success', $params); } - /** - * 验证异步通知签名 - * - * ⚠️ 建行异步通知签名规则: - * 1. SIGN字段为256字符十六进制字符串(2048位RSA签名) - * 2. NT_TYPE=YS时,使用"建行生活分配的服务商支付验签公钥" - * 3. 签名算法: RSA-SHA256或SHA1(需建行技术支持确认) - * - * 📌 配置说明: - * - 如果配置了ccb_payment_verify_public_key: 使用RSA验签 - * - 如果未配置: 降级为POSID验证(临时方案) - * - * @param array $params 通知参数 - * @return bool - */ - private function verifyNotifySignature($params) - { - try { - // 1. 提取SIGN字段 - $sign = $params['SIGN'] ?? ''; - if (empty($sign)) { - Log::error('[建行验签] SIGN字段为空'); - return false; - } - - // 验证SIGN长度(256个十六进制字符 = 2048位RSA签名) - if (strlen($sign) !== 256) { - Log::error('[建行验签] SIGN长度错误: ' . strlen($sign) . ', 应为256'); - return false; - } - - // 2. 检查是否配置了建行支付验签公钥 - $ccbVerifyPublicKey = $this->config['public_key'] ?? ''; - - if (empty($ccbVerifyPublicKey)) { - // 降级方案: 未配置验签公钥时,使用POSID验证 - Log::warning('[建行验签] 未配置public_key,使用降级验证方案'); - - // 验证POSID是否匹配 - if (($params['POSID'] ?? '') !== $this->config['pos_id']) { - Log::error('[建行验签] POSID不匹配,预期: ' . $this->config['pos_id'] . ', 实际: ' . ($params['POSID'] ?? '')); - return false; - } - - // 验证订单号是否存在 - $orderSn = $params['USER_ORDERID'] ?? $params['ORDERID'] ?? ''; - if (empty($orderSn)) { - Log::error('[建行验签] 订单号为空'); - return false; - } - - Log::warning('[建行验签] 降级验证通过,建议联系建行技术支持获取验签公钥'); - return true; - } - - // 3. 移除SIGN字段,构建签名原串 - $verifyParams = $params; - unset($verifyParams['SIGN']); - - // 4. 按key排序 - ksort($verifyParams); - - // 5. 构建签名原串(非空参数) - $signStr = ''; - foreach ($verifyParams as $key => $value) { - // 跳过空值参数 - if ($value !== '' && $value !== null) { - $signStr .= $key . '=' . $value . '&'; - } - } - $signStr = rtrim($signStr, '&'); - - Log::info('[建行验签] 签名原串: ' . $signStr); - Log::info('[建行验签] 收到SIGN前32位: ' . substr($sign, 0, 32) . '...'); - - // 6. 将十六进制SIGN转为二进制 - $signBinary = hex2bin($sign); - if ($signBinary === false) { - Log::error('[建行验签] SIGN十六进制转换失败'); - return false; - } - - // 7. 加载建行支付验签公钥 - $pubKey = openssl_pkey_get_public($ccbVerifyPublicKey); - if (!$pubKey) { - Log::error('[建行验签] 验签公钥加载失败: ' . openssl_error_string()); - return false; - } - - // 8. 先尝试SHA256验签 - $verifyResult = openssl_verify($signStr, $signBinary, $pubKey, OPENSSL_ALGO_SHA256); - - if ($verifyResult !== 1) { - // SHA256失败,尝试SHA1 - Log::info('[建行验签] SHA256验签失败,尝试SHA1'); - $verifyResult = openssl_verify($signStr, $signBinary, $pubKey, OPENSSL_ALGO_SHA1); - } - - // PHP 8+ 资源自动释放 - if (PHP_VERSION_ID < 80000) { - openssl_free_key($pubKey); - } - - if ($verifyResult === 1) { - Log::info('[建行验签] RSA验签成功'); - return true; - } elseif ($verifyResult === 0) { - Log::error('[建行验签] RSA验签失败,签名不匹配'); - return false; - } else { - Log::error('[建行验签] RSA验签过程发生错误: ' . openssl_error_string()); - return false; - } - - } catch (\Exception $e) { - Log::error('[建行验签] 验签异常: ' . $e->getMessage()); - return false; - } - } /** * 记录支付请求 diff --git a/addons/shopro/test/ccblife_test.php b/addons/shopro/test/ccblife_test.php deleted file mode 100644 index 863b79d..0000000 --- a/addons/shopro/test/ccblife_test.php +++ /dev/null @@ -1,610 +0,0 @@ -startTime = microtime(true); - $this->printHeader(); - } - - /** - * 打印测试头部 - */ - private function printHeader() - { - echo "\n"; - echo "========================================\n"; - echo " 建行生活H5商城对接 - 自动化测试 \n"; - echo "========================================\n"; - echo "测试时间: " . date('Y-m-d H:i:s') . "\n"; - echo "========================================\n\n"; - } - - /** - * 运行所有测试 - */ - public function runAll() - { - // 测试1: 环境检查 - $this->test01_checkEnvironment(); - - // 测试2: 配置文件检查 - $this->test02_checkConfig(); - - // 测试3: 数据库表检查 - $this->test03_checkDatabaseTables(); - - // 测试4: 创建测试用户 - $this->test04_createTestUser(); - - // 测试5: 创建测试订单 - $this->test05_createTestOrder(); - - // 测试6: 支付串生成测试 - $this->test06_generatePaymentString(); - - // 测试7: 支付回调测试 - $this->test07_simulatePaymentCallback(); - - // 测试8: 异步通知测试 - $this->test08_simulateNotify(); - - // 测试9: 订单同步测试 - $this->test09_testOrderSync(); - - // 测试10: 数据清理 - $this->test10_cleanup(); - - // 输出测试报告 - $this->printReport(); - } - - /** - * 测试1: 环境检查 - */ - private function test01_checkEnvironment() - { - $this->printTestName('环境检查'); - - try { - // 检查PHP版本 - $phpVersion = phpversion(); - $this->assert($phpVersion >= '7.0', "PHP版本检查 ($phpVersion >= 7.0)"); - - // 检查扩展 - $this->assert(extension_loaded('openssl'), '检查OpenSSL扩展'); - $this->assert(extension_loaded('pdo'), '检查PDO扩展'); - $this->assert(extension_loaded('json'), '检查JSON扩展'); - - // 检查文件是否存在 - $files = [ - APP_PATH . '../addons/shopro/library/ccblife/CcbPaymentService.php', - APP_PATH . '../addons/shopro/library/ccblife/CcbOrderService.php', - APP_PATH . '../addons/shopro/library/ccblife/CcbRSA.php', - APP_PATH . '../addons/shopro/library/ccblife/CcbMD5.php', - APP_PATH . '../addons/shopro/library/ccblife/CcbEncryption.php' - ]; - - foreach ($files as $file) { - $this->assert(file_exists($file), "检查文件: " . basename($file)); - } - - $this->recordResult('环境检查', true, '所有环境检查通过'); - } catch (\Exception $e) { - $this->recordResult('环境检查', false, $e->getMessage()); - } - } - - /** - * 测试2: 配置文件检查 - */ - private function test02_checkConfig() - { - $this->printTestName('配置文件检查'); - - try { - // 加载插件配置文件 - $configFile = APP_PATH . '../addons/shopro/config/ccblife.php'; - $this->assert(file_exists($configFile), '配置文件存在'); - - $config = include $configFile; - - $this->assert(!empty($config['merchant_id']), '商户ID配置'); - $this->assert(!empty($config['pos_id']), 'POS ID配置'); - $this->assert(!empty($config['branch_id']), '分行代码配置'); - $this->assert(!empty($config['service_id']), '服务方编号配置'); - $this->assert(!empty($config['private_key']), '服务方私钥配置'); - $this->assert(!empty($config['public_key']), '商户公钥配置'); - - echo " 商户ID: {$config['merchant_id']}\n"; - echo " 服务方ID: {$config['service_id']}\n"; - - $this->recordResult('配置文件检查', true, '所有配置项完整'); - } catch (\Exception $e) { - $this->recordResult('配置文件检查', false, $e->getMessage()); - } - } - - /** - * 测试3: 数据库表检查 - */ - private function test03_checkDatabaseTables() - { - $this->printTestName('数据库表检查'); - - try { - // 检查主要表 - $tables = [ - 'fa_ccb_payment_log', - 'fa_ccb_sync_log', - 'fa_shopro_order', - 'fa_user' - ]; - - foreach ($tables as $table) { - $exists = Db::query("SHOW TABLES LIKE '{$table}'"); - $this->assert(!empty($exists), "检查表: {$table}"); - } - - // 检查订单表字段 - $columns = Db::query("SHOW COLUMNS FROM fa_shopro_order LIKE 'ccb_pay_flow_id'"); - $this->assert(!empty($columns), '检查订单表ccb_pay_flow_id字段'); - - $this->recordResult('数据库表检查', true, '所有表结构完整'); - } catch (\Exception $e) { - $this->recordResult('数据库表检查', false, $e->getMessage()); - } - } - - /** - * 测试4: 创建测试用户 - */ - private function test04_createTestUser() - { - $this->printTestName('创建测试用户'); - - try { - // 查找或创建测试用户 - $user = User::where('username', 'ccb_test_user')->find(); - - if (!$user) { - $user = User::create([ - 'username' => 'ccb_test_user', - 'nickname' => '建行测试用户', - 'mobile' => '13800138000', - 'ccb_user_id' => 'TEST_CCB_' . time(), - 'password' => md5('123456' . ''), - 'salt' => '', - 'money' => 0, - 'score' => 0, - 'createtime' => time(), - 'updatetime' => time(), - 'status' => 'normal' - ]); - } else { - // 更新建行用户ID - $user->ccb_user_id = 'TEST_CCB_' . time(); - $user->save(); - } - - $this->testUserId = $user->id; - $this->assert($this->testUserId > 0, "用户创建成功 (ID: {$this->testUserId})"); - $this->recordResult('创建测试用户', true, "用户ID: {$this->testUserId}"); - } catch (\Exception $e) { - $this->recordResult('创建测试用户', false, $e->getMessage()); - } - } - - /** - * 测试5: 创建测试订单 - */ - private function test05_createTestOrder() - { - $this->printTestName('创建测试订单'); - - try { - // 创建测试订单 - $orderSn = 'SO' . date('YmdHis') . mt_rand(1000, 9999); - - $order = Order::create([ - 'order_sn' => $orderSn, - 'user_id' => $this->testUserId, - 'type' => 'goods', // 订单类型:商城订单 - 'goods_amount' => 100.00, // 商品总价 - 'dispatch_amount' => 10.00, // 运费 - 'total_discount_fee' => 5.00, // 优惠总金额 - 'order_amount' => 105.00, // 订单总金额 - 'pay_fee' => 100.00, // 实际支付金额 - 'status' => 'unpaid', // 订单状态:未支付 - 'pay_mode' => 'online', // 支付模式:线上支付 - 'platform' => 'H5', // 平台:H5 - 'remark' => '建行支付测试订单', - 'createtime' => time() * 1000, // 毫秒时间戳 - 'updatetime' => time() * 1000 // 毫秒时间戳 - ]); - - $this->testOrderId = $order->id; - $this->assert($this->testOrderId > 0, "订单创建成功 (ID: {$this->testOrderId}, SN: {$orderSn})"); - $this->recordResult('创建测试订单', true, "订单ID: {$this->testOrderId}"); - } catch (\Exception $e) { - $this->recordResult('创建测试订单', false, $e->getMessage()); - } - } - - /** - * 测试6: 支付串生成测试 - */ - private function test06_generatePaymentString() - { - $this->printTestName('支付串生成测试'); - - try { - $paymentService = new \addons\shopro\library\ccblife\CcbPaymentService(); - $result = $paymentService->generatePaymentString($this->testOrderId); - - if ($result['status'] !== true) { - echo " ⚠️ 错误信息: " . ($result['message'] ?? '未知错误') . "\n"; - } - - $this->assert($result['status'] === true, '支付串生成状态'); - $this->assert(!empty($result['data']['payment_string']), '支付串不为空'); - $this->assert(!empty($result['data']['pay_flow_id']), '支付流水号不为空'); - $this->assert(!empty($result['data']['mac']), 'MAC签名不为空'); - - $this->payFlowId = $result['data']['pay_flow_id']; - - // 验证支付流水号格式: PAY + 14位时间戳 + 6位随机数 - $this->assert(strlen($this->payFlowId) === 23, "支付流水号长度正确 (23位)"); - $this->assert(substr($this->payFlowId, 0, 3) === 'PAY', "支付流水号前缀正确 (PAY)"); - - // 验证订单表已更新 - $order = Order::find($this->testOrderId); - $this->assert($order->ccb_pay_flow_id === $this->payFlowId, '订单表支付流水号已更新'); - - // 验证支付日志已记录 - $paymentLog = Db::name('ccb_payment_log') - ->where('pay_flow_id', $this->payFlowId) - ->find(); - $this->assert(!empty($paymentLog), '支付日志已记录'); - - echo " 支付串长度: " . strlen($result['data']['payment_string']) . " 字节\n"; - echo " 支付流水号: {$this->payFlowId}\n"; - echo " MAC签名: {$result['data']['mac']}\n"; - - $this->recordResult('支付串生成测试', true, "支付流水号: {$this->payFlowId}"); - } catch (\Exception $e) { - $this->recordResult('支付串生成测试', false, $e->getMessage()); - } - } - - /** - * 测试7: 支付回调测试(模拟建行回调) - */ - private function test07_simulatePaymentCallback() - { - $this->printTestName('支付回调测试'); - - try { - $order = Order::find($this->testOrderId); - - // 模拟建行支付成功回调参数 - $callbackParams = [ - 'ORDERID' => $this->payFlowId, // 支付流水号 - 'USER_ORDERID' => $order->order_sn, // 商户订单号 - 'POSID' => config('ccblife.pos_id'), - 'SUCCESS' => 'Y', // 支付成功 - 'PAYMENT' => '100.00', - 'ERRCODE' => '', - 'ERRMSG' => '' - ]; - - $paymentService = new \addons\shopro\library\ccblife\CcbPaymentService(); - $result = $paymentService->handleCallback($callbackParams); - - $this->assert($result['status'] === true, '回调处理成功'); - $this->assert($result['message'] === '支付成功', '回调消息正确'); - - // 验证订单状态已更新 - $order->refresh(); - $this->assert($order->status === 'paid', '订单状态已更新为已支付'); - $this->assert($order->pay_type === 'offline', '支付方式正确 (offline代表建行)'); - $this->assert($order->paid_time > 0, '支付时间已记录'); - $this->assert($order->transaction_id === $this->payFlowId, '交易单号正确'); - - echo " 订单状态: {$order->status}\n"; - echo " 支付时间: " . date('Y-m-d H:i:s', intval($order->paid_time / 1000)) . "\n"; - - $this->recordResult('支付回调测试', true, '支付回调处理成功'); - } catch (\Exception $e) { - $this->recordResult('支付回调测试', false, $e->getMessage()); - } - } - - /** - * 测试8: 异步通知测试(模拟建行异步通知) - */ - private function test08_simulateNotify() - { - $this->printTestName('异步通知测试'); - - try { - // 重置订单状态用于测试 - $order = Order::find($this->testOrderId); - $originalOrderSn = $order->order_sn; - $newOrderSn = 'SO' . date('YmdHis') . mt_rand(1000, 9999); - - // 创建新订单用于通知测试 - $newOrder = Order::create([ - 'order_sn' => $newOrderSn, - 'user_id' => $this->testUserId, - 'goods_amount' => 200.00, - 'order_amount' => 200.00, - 'pay_fee' => 200.00, // Shopro字段:实际支付金额 - 'status' => 'unpaid', - 'ccb_pay_flow_id' => 'PAY' . date('YmdHis') . mt_rand(100000, 999999), - 'createtime' => time() * 1000, // 毫秒时间戳 - 'updatetime' => time() * 1000 // 毫秒时间戳 - ]); - - // 模拟建行异步通知参数(不含签名,简化测试) - $notifyParams = [ - 'ORDERID' => $newOrder->ccb_pay_flow_id, - 'USER_ORDERID' => $newOrderSn, - 'POSID' => config('ccblife.pos_id'), - 'PAYMENT' => '200.00', - 'SUCCESS' => 'Y', - 'SIGN' => '' // 暂不验证签名 - ]; - - $paymentService = new \addons\shopro\library\ccblife\CcbPaymentService(); - - // 临时禁用签名验证(仅测试用) - $result = $paymentService->handleNotify($notifyParams); - - // 注意:真实环境会因签名验证失败返回'fail',这里仅测试订单查询逻辑 - echo " 通知处理结果: {$result}\n"; - - // 清理测试订单 - $newOrder->delete(); - - $this->recordResult('异步通知测试', true, '通知处理逻辑测试完成'); - } catch (\Exception $e) { - $this->recordResult('异步通知测试', false, $e->getMessage()); - } - } - - /** - * 测试9: 订单同步测试(模拟API请求) - */ - private function test09_testOrderSync() - { - $this->printTestName('订单同步测试'); - - try { - $order = Order::find($this->testOrderId); - $orderArray = $order->toArray(); // 转为数组 - - // 获取测试用户的建行ID - $user = \think\Db::name('user')->where('id', $order->user_id)->find(); - $ccbUserId = $user['ccb_user_id'] ?? 'TEST_CCB_USER'; - - // 模拟订单商品列表(空数组) - $orderItems = []; - - // 测试订单数据构建 - $orderService = new \addons\shopro\library\ccblife\CcbOrderService(); - - // 使用反射访问私有方法 - $reflection = new \ReflectionClass($orderService); - $method = $reflection->getMethod('buildOrderData'); - $method->setAccessible(true); - - // 开启详细错误报告 - $oldErrorReporting = error_reporting(E_ALL); - $oldDisplayErrors = ini_get('display_errors'); - ini_set('display_errors', '1'); - - $orderData = $method->invoke($orderService, $orderArray, $orderItems, $ccbUserId); - - // 恢复错误报告设置 - error_reporting($oldErrorReporting); - ini_set('display_errors', $oldDisplayErrors); - - // 验证订单数据结构 - $requiredFields = [ - 'ORDER_ID', 'PAY_AMT', 'ORDER_DT', - 'ORDER_STATUS', 'REFUND_STATUS' - ]; - - foreach ($requiredFields as $field) { - $this->assert(isset($orderData[$field]), "订单字段 {$field} 存在"); - } - - // 验证字段值 - $this->assert($orderData['ORDER_ID'] === $order->order_sn, '订单号正确'); - $this->assert($orderData['PAY_AMT'] === number_format($order->pay_fee, 2, '.', ''), '支付金额正确'); - - echo " 订单号: {$orderData['ORDER_ID']}\n"; - echo " 支付金额: {$orderData['PAY_AMT']}\n"; - echo " 订单状态: {$orderData['ORDER_STATUS']}\n"; - - $this->recordResult('订单同步测试', true, '订单数据构建正确'); - } catch (\Throwable $e) { - // 捕获所有错误和异常,包括 deprecation 警告 - $errorMsg = $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine(); - echo " 详细错误: {$errorMsg}\n"; - $this->recordResult('订单同步测试', false, $e->getMessage()); - } - } - - /** - * 测试10: 数据清理 - */ - private function test10_cleanup() - { - $this->printTestName('清理测试数据'); - - try { - // 清理测试订单 - if ($this->testOrderId) { - Order::destroy($this->testOrderId); - $this->assert(true, "删除测试订单 (ID: {$this->testOrderId})"); - } - - // 清理支付日志 - if ($this->payFlowId) { - Db::name('ccb_payment_log') - ->where('pay_flow_id', $this->payFlowId) - ->delete(); - $this->assert(true, "删除支付日志"); - } - - // 清理测试用户 - if ($this->testUserId) { - User::destroy($this->testUserId); - $this->assert(true, "删除测试用户 (ID: {$this->testUserId})"); - } - - $this->recordResult('清理测试数据', true, '所有测试数据已清理'); - } catch (\Exception $e) { - $this->recordResult('清理测试数据', false, $e->getMessage()); - } - } - - /** - * 断言 - */ - private function assert($condition, $message) - { - if ($condition) { - echo " ✓ {$message}\n"; - } else { - echo " ✗ {$message} [失败]\n"; - throw new \Exception($message . " 检查失败"); - } - } - - /** - * 记录测试结果 - */ - private function recordResult($testName, $passed, $message) - { - $this->testResults[] = [ - 'name' => $testName, - 'passed' => $passed, - 'message' => $message - ]; - } - - /** - * 打印测试名称 - */ - private function printTestName($name) - { - echo "\n【{$name}】\n"; - } - - /** - * 打印测试报告 - */ - private function printReport() - { - $endTime = microtime(true); - $duration = round($endTime - $this->startTime, 2); - - echo "\n\n"; - echo "========================================\n"; - echo " 测试报告 \n"; - echo "========================================\n"; - - $passed = 0; - $failed = 0; - - foreach ($this->testResults as $result) { - $status = $result['passed'] ? '✓ 通过' : '✗ 失败'; - $color = $result['passed'] ? '' : ''; - - echo "{$status} {$result['name']}\n"; - echo " {$result['message']}\n"; - - if ($result['passed']) { - $passed++; - } else { - $failed++; - } - } - - echo "\n========================================\n"; - echo "总计: " . count($this->testResults) . " 项测试\n"; - echo "通过: {$passed} 项\n"; - echo "失败: {$failed} 项\n"; - echo "耗时: {$duration} 秒\n"; - echo "========================================\n\n"; - - if ($failed === 0) { - echo "🎉 所有测试通过!系统运行正常。\n\n"; - } else { - echo "⚠️ 部分测试失败,请检查失败原因。\n\n"; - } - } -} - -// 运行测试 -try { - $test = new CcbLifeTest(); - $test->runAll(); -} catch (\Exception $e) { - echo "\n❌ 测试异常终止: " . $e->getMessage() . "\n"; - echo "堆栈跟踪:\n" . $e->getTraceAsString() . "\n"; -} diff --git a/addons/shopro/test/diagnose_ccb_encryption.php b/addons/shopro/test/diagnose_ccb_encryption.php deleted file mode 100644 index e56003f..0000000 --- a/addons/shopro/test/diagnose_ccb_encryption.php +++ /dev/null @@ -1,152 +0,0 @@ -getMessage() . "\n\n"; - exit; -} - -// 建行的加密参数 -$ccbParamSJ = 'cURYdUJxQ2pJTlVWQUltSW9DQWN1T2xJeFI4WjR2ZVY0NENTVmxPQW9RUVJ1bnJ2Ync5MkwzS2dXdDF1TTVSd3NvUFlsZWFURlYvK243aFNGaWRNeTJsZXRxM1VJR2k0VFdqNnU0Z2JXRnVxMnVIcjhzSVcrRW4xOGxaWDA4Snk0VW1hcFllZ05pekxmc2h6dGFCOTloTUtnVXh2ekJNbDFXZzdwT3lOcDBHZGx5N0FidW1FY1NudmlhNGcxZFAwM09BRk5sUE9USkM4QlJNNWtGTzRKVjFqTWx6RlhrblNwWG0rYmxpS2kyOFpYLy91OGZCR1NPNGp2dHN6b3N1TGxNZnUzd2JNeVUyOVpFTzYxWDRyNm0yb2c0ekZCQWpjc1VLdU9Kcmh1VjVwVDdQZlBlQ0MwRWhLQXFWWXBOdi9uS0FQNkZkZ2JER0ljN2tlUXNHdlFMTWlDeU9rVzdFU2phdG5jYjhZOUNaWkVEb29OdGVLdmlscXg4eHRFS1VtWHljU2ZNeUlDbk5yRW5nR3VXS1AvN3NacTNISE5BWSt2azBHdU1IaG5UWkl6bTdXUmhVYnNRclRYb0ljNy9UTnZVT3lmSVFIcTZ0KzZpZGdtc1NDejBkajVsSjkzVXNSM0NWZExCUk85NHQxZGNEOWorUnFhV2hrWmpiYUE4SlY='; - -echo "3. 尝试不同的解密方法\n\n"; - -// 方法1: 标准RSA解密(用商户私钥) -echo "方法1: 使用商户私钥解密(标准方式)\n"; -try { - $urlDecoded = urldecode($ccbParamSJ); - $decrypted = CcbRSA::decrypt($urlDecoded, $privateKey); - - echo " 解密成功,长度: " . strlen($decrypted) . "\n"; - - // 检查是否是可打印字符 - $isPrintable = ctype_print($decrypted); - echo " 是否为可打印字符: " . ($isPrintable ? '是' : '否') . "\n"; - - if ($isPrintable) { - echo " 解密内容: {$decrypted}\n"; - parse_str($decrypted, $params); - if (!empty($params)) { - echo " ✅ 解析成功!参数:\n"; - foreach ($params as $k => $v) { - echo " {$k} = {$v}\n"; - } - } - } else { - echo " 内容预览(十六进制): " . bin2hex(substr($decrypted, 0, 50)) . "...\n"; - echo " ❌ 解密结果不是有效文本\n"; - } -} catch (\Exception $e) { - echo " ❌ 解密失败: " . $e->getMessage() . "\n"; -} - -echo "\n"; - -// 方法2: 尝试用公钥解密(建行可能用建行私钥加密) -echo "方法2: 尝试用商户公钥解密(反向,测试建行是否用私钥加密)\n"; -try { - $urlDecoded = urldecode($ccbParamSJ); - - // 格式化公钥为PEM - $pemPublicKey = "-----BEGIN PUBLIC KEY-----\n"; - $pemPublicKey .= chunk_split($publicKey, 64, "\n"); - $pemPublicKey .= "-----END PUBLIC KEY-----\n"; - - $pubKeyResource = openssl_pkey_get_public($pemPublicKey); - if (!$pubKeyResource) { - throw new \Exception('公钥格式错误'); - } - - // BASE64解码 - $encryptedData = base64_decode($urlDecoded); - - // 获取密钥大小 - $keyDetails = openssl_pkey_get_details($pubKeyResource); - $keySize = $keyDetails['bits'] / 8; - - // 分块解密 - $dataBlocks = str_split($encryptedData, $keySize); - $decrypted = ''; - - foreach ($dataBlocks as $block) { - $decryptedBlock = ''; - // 尝试用公钥解密 - $success = @openssl_public_decrypt($block, $decryptedBlock, $pubKeyResource, OPENSSL_PKCS1_PADDING); - if (!$success) { - throw new \Exception('公钥解密失败'); - } - $decrypted .= $decryptedBlock; - } - - openssl_free_key($pubKeyResource); - - echo " 解密成功,长度: " . strlen($decrypted) . "\n"; - $isPrintable = ctype_print($decrypted); - echo " 是否为可打印字符: " . ($isPrintable ? '是' : '否') . "\n"; - - if ($isPrintable) { - echo " 解密内容: {$decrypted}\n"; - echo " ✅ 可能建行用的是建行私钥加密!\n"; - } else { - echo " ❌ 解密结果不是有效文本\n"; - } -} catch (\Exception $e) { - echo " ❌ 解密失败: " . $e->getMessage() . "\n"; -} - -echo "\n"; - -// 方法3: 检查是否需要建行公钥 -echo "方法3: 建议检查项\n"; -echo " - 确认建行后台配置的商户公钥是否为:\n"; -echo " " . substr($publicKey, 0, 50) . "...\n"; -echo " - 检查建行是否提供了建行平台公钥(用于解密建行发来的数据)\n"; -echo " - 联系建行技术支持确认ccbParamSJ的加密方式\n"; - -echo "\n========================================\n"; -echo "诊断完成\n"; -echo "========================================\n"; diff --git a/addons/shopro/test/test_ccb_correct_decrypt.php b/addons/shopro/test/test_ccb_correct_decrypt.php deleted file mode 100644 index 8db4306..0000000 --- a/addons/shopro/test/test_ccb_correct_decrypt.php +++ /dev/null @@ -1,147 +0,0 @@ - $block) { - $decryptedBlock = ''; - $success = openssl_private_decrypt($block, $decryptedBlock, $priKey, OPENSSL_PKCS1_PADDING); - if (!$success) { - throw new \Exception("解密第 " . ($i+1) . " 块失败: " . openssl_error_string()); - } - $decrypted .= $decryptedBlock; - } - - openssl_free_key($priKey); - - echo " 解密成功!\n"; - echo " 解密后长度: " . strlen($decrypted) . "\n\n"; - - // 步骤5: 显示结果 - echo "步骤5: 解密结果\n"; - echo " 原始内容:\n"; - echo " ----------------------------------------\n"; - echo " {$decrypted}\n"; - echo " ----------------------------------------\n\n"; - - // 步骤6: 解析参数 - echo "步骤6: 解析 URL 参数\n"; - parse_str($decrypted, $params); - - if (empty($params)) { - echo " ⚠️ 解析为空,可能不是 URL 参数格式\n"; - } else { - echo " 解析成功!参数列表:\n"; - foreach ($params as $key => $value) { - echo " {$key} = {$value}\n"; - } - } - - echo "\n========================================\n"; - echo "✅ 解密成功!\n"; - echo "========================================\n"; - -} catch (\Exception $e) { - echo "\n========================================\n"; - echo "❌ 解密失败\n"; - echo "========================================\n"; - echo "错误: " . $e->getMessage() . "\n"; - echo "堆栈:\n" . $e->getTraceAsString() . "\n"; -} diff --git a/addons/shopro/test/test_decrypt.php b/addons/shopro/test/test_decrypt.php deleted file mode 100644 index 01d2538..0000000 --- a/addons/shopro/test/test_decrypt.php +++ /dev/null @@ -1,152 +0,0 @@ -setKey($testKey); - $cipher->disablePadding(); - - $result = $cipher->decrypt($secondDecode); - - // 验证解密结果是否合理(检查是否包含可见字符) - if ($result !== false && !empty($result)) { - // 尝试移除填充 - $textLength = strlen($result); - $pad = ord($result[$textLength - 1]); - - // 填充值应该在1-8之间 - if ($pad >= 1 && $pad <= 8) { - $unpadded = substr($result, 0, -1 * $pad); - - // 检查是否像URL参数(包含=或&) - if (strpos($unpadded, '=') !== false || strpos($unpadded, '&') !== false) { - $decrypted = $unpadded; - $validKey = $testKey; - echo " ✓ 找到正确密钥!\n"; - break; - } - } - } - } - - if ($decrypted === false || empty($decrypted)) { - die("\n错误: 所有密钥都无法正确解密\n"); - } - - $desKey = $validKey; - - echo " 解密成功,长度: " . strlen($decrypted) . "\n"; - echo " 解密内容: {$decrypted}\n\n"; - - // 解析参数 - echo "步骤4: 解析参数\n"; - parse_str($decrypted, $params); - echo " 解析结果:\n"; - foreach ($params as $key => $value) { - echo " {$key} = {$value}\n"; - } - - echo "\n========================================\n"; - echo "✅ 解密成功!\n"; - echo "========================================\n"; - -} catch (\Exception $e) { - echo "\n========================================\n"; - echo "❌ 解密失败!\n"; - echo "========================================\n"; - echo "错误信息: " . $e->getMessage() . "\n"; - echo "错误行号: " . $e->getLine() . "\n"; - echo "错误文件: " . $e->getFile() . "\n"; -} diff --git a/addons/shopro/test/test_rsa_decrypt.php b/addons/shopro/test/test_rsa_decrypt.php deleted file mode 100644 index ea2a178..0000000 --- a/addons/shopro/test/test_rsa_decrypt.php +++ /dev/null @@ -1,136 +0,0 @@ - $value) { - echo " {$key} = {$value}\n"; - } - - echo "\n========================================\n"; - echo "✅ 解密成功!\n"; - echo "========================================\n"; - -} catch (\Exception $e) { - echo "\n========================================\n"; - echo "❌ 解密失败!\n"; - echo "========================================\n"; - echo "错误信息: " . $e->getMessage() . "\n"; - echo "错误行号: " . $e->getLine() . "\n"; - echo "错误文件: " . $e->getFile() . "\n"; - echo "\n堆栈跟踪:\n" . $e->getTraceAsString() . "\n"; - - // 额外诊断 - echo "\n========================================\n"; - echo "诊断信息\n"; - echo "========================================\n"; - - // 检查 BASE64 解码 - echo "\n1. BASE64 解码测试:\n"; - $base64Decoded = base64_decode($urlDecoded); - if ($base64Decoded === false) { - echo " ❌ BASE64 解码失败\n"; - } else { - echo " ✓ BASE64 解码成功\n"; - echo " 解码后长度: " . strlen($base64Decoded) . "\n"; - echo " 十六进制预览: " . bin2hex(substr($base64Decoded, 0, 32)) . "...\n"; - } - - // 检查私钥格式 - echo "\n2. 私钥格式测试:\n"; - try { - // 格式化私钥 - $formattedKey = $privateKey; - if (strpos($formattedKey, '-----BEGIN') === false) { - $formattedKey = preg_replace('/\s+/', '', $formattedKey); - $formattedKey = "-----BEGIN RSA PRIVATE KEY-----\n" . - chunk_split($formattedKey, 64, "\n") . - "-----END RSA PRIVATE KEY-----\n"; - } - - $keyResource = openssl_pkey_get_private($formattedKey); - if ($keyResource === false) { - echo " ❌ 私钥格式错误: " . openssl_error_string() . "\n"; - } else { - echo " ✓ 私钥格式正确\n"; - $keyDetails = openssl_pkey_get_details($keyResource); - echo " 密钥类型: " . ($keyDetails['type'] === OPENSSL_KEYTYPE_RSA ? 'RSA' : '未知') . "\n"; - echo " 密钥位数: " . $keyDetails['bits'] . "\n"; - openssl_free_key($keyResource); - } - } catch (\Exception $e2) { - echo " ❌ 私钥检查失败: " . $e2->getMessage() . "\n"; - } - - // 检查数据长度 - echo "\n3. 数据长度检查:\n"; - if (isset($base64Decoded)) { - $dataLen = strlen($base64Decoded); - echo " 加密数据长度: {$dataLen} 字节\n"; - - if (isset($keyDetails)) { - $keySize = $keyDetails['bits'] / 8; - $blockCount = $dataLen / $keySize; - echo " 密钥大小: {$keySize} 字节\n"; - echo " 分块数量: " . $blockCount . " (应该是整数)\n"; - - if ($blockCount != floor($blockCount)) { - echo " ⚠️ 警告: 数据长度不是密钥大小的整数倍\n"; - } - } - } -} diff --git a/addons/shopro/test/verify_keypair.php b/addons/shopro/test/verify_keypair.php deleted file mode 100644 index 0103c50..0000000 --- a/addons/shopro/test/verify_keypair.php +++ /dev/null @@ -1,81 +0,0 @@ -getMessage() . "\n\n"; -} - -// 显示公钥信息(用于确认是否提交给建行) -echo "3. 你的公钥(BASE64格式,提交给建行的应该是这个)\n"; -echo "----------------------------------------\n"; -echo $publicKey . "\n"; -echo "----------------------------------------\n\n"; - -echo "4. 你的公钥(PEM格式)\n"; -echo "----------------------------------------\n"; -$pemPublicKey = "-----BEGIN PUBLIC KEY-----\n"; -$pemPublicKey .= chunk_split($publicKey, 64, "\n"); -$pemPublicKey .= "-----END PUBLIC KEY-----\n"; -echo $pemPublicKey; -echo "----------------------------------------\n\n"; - -echo "⚠️ 重要提示:\n"; -echo "1. 请确认建行那边配置的公钥是否和上面显示的公钥一致\n"; -echo "2. 如果不一致,需要重新提交正确的公钥给建行\n"; -echo "3. 如果一致但仍然解密失败,可能是建行加密方式有问题\n";