fengketrade/addons/shopro/test/ccblife_test.php

611 lines
21 KiB
PHP
Raw Normal View History

2025-10-18 15:47:25 +08:00
<?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";
}