fengketrade/addons/shopro/test/ccblife_test.php
2025-10-18 15:47:25 +08:00

611 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* 建行生活H5商城对接 - 完整测试脚本
*
* 功能说明:
* - 不依赖建行真实环境
* - 模拟完整支付流程
* - 验证核心业务逻辑
* - 自动输出测试报告
*
* 使用方法:
* php /path/to/ccblife_test.php
*
* @author Billy
* @date 2025-01-18
*/
// 设置错误显示
error_reporting(E_ALL);
ini_set('display_errors', 1);
// 引入FastAdmin框架
// 设置常量
define('DS', DIRECTORY_SEPARATOR);
define('ROOT_PATH', __DIR__ . '/../../../');
define('APP_PATH', ROOT_PATH . 'application/');
// 切换到项目根目录
chdir(ROOT_PATH);
// 加载基础文件
require_once ROOT_PATH . 'thinkphp/base.php';
// 支持事先使用静态方法设置Request对象和Config对象
// 初始化应用
\think\App::initCommon();
use app\admin\model\shopro\order\Order;
use app\admin\model\shopro\goods\Goods;
use app\admin\model\User;
use think\Db;
use think\Log;
/**
* 测试类
*/
class CcbLifeTest
{
private $testResults = [];
private $testUserId;
private $testOrderId;
private $payFlowId;
private $startTime;
public function __construct()
{
$this->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";
}