mirror of
https://gitee.com/liuxioabin/fengketrade.git
synced 2026-04-17 12:57:32 +08:00
回调
This commit is contained in:
parent
c30a43a733
commit
7dfce210ed
@ -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']);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录支付请求
|
||||
|
||||
@ -1,610 +0,0 @@
|
||||
<?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";
|
||||
}
|
||||
@ -1,152 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* 诊断建行加密方式
|
||||
* 尝试不同的解密方法
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../library/ccblife/CcbRSA.php';
|
||||
|
||||
use addons\shopro\library\ccblife\CcbRSA;
|
||||
|
||||
echo "========================================\n";
|
||||
echo " 建行加密方式诊断\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
// 从.env读取密钥
|
||||
$envFile = __DIR__ . '/../../../.env';
|
||||
$envLines = file($envFile, FILE_IGNORE_NEW_LINES);
|
||||
|
||||
$privateKey = '';
|
||||
$publicKey = '';
|
||||
|
||||
foreach ($envLines as $line) {
|
||||
if (stripos($line, 'private_key=') === 0) {
|
||||
$privateKey = trim(substr($line, 12));
|
||||
}
|
||||
if (stripos($line, 'public_key=') === 0) {
|
||||
$publicKey = trim(substr($line, 11));
|
||||
}
|
||||
}
|
||||
|
||||
echo "1. 密钥信息\n";
|
||||
echo " 私钥长度: " . strlen($privateKey) . "\n";
|
||||
echo " 公钥长度: " . strlen($publicKey) . "\n\n";
|
||||
|
||||
// 步骤1: 验证密钥对是否配对
|
||||
echo "2. 验证密钥对是否配对\n";
|
||||
try {
|
||||
$testData = "test=123&name=hello";
|
||||
$encrypted = CcbRSA::encrypt($testData, $publicKey);
|
||||
$decrypted = CcbRSA::decrypt($encrypted, $privateKey);
|
||||
|
||||
if ($decrypted === $testData) {
|
||||
echo " ✅ 密钥对配对正确\n\n";
|
||||
} else {
|
||||
echo " ❌ 密钥对不匹配\n\n";
|
||||
exit;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
echo " ❌ 密钥验证失败: " . $e->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";
|
||||
@ -1,147 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* 正确的建行解密流程(参考 Java demo)
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../library/ccblife/CcbRSA.php';
|
||||
|
||||
use addons\shopro\library\ccblife\CcbRSA;
|
||||
|
||||
echo "========================================\n";
|
||||
echo " 建行 ccbParamSJ 解密测试\n";
|
||||
echo " (参考 Java demo 实现)\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
// 从.env读取私钥
|
||||
$envFile = __DIR__ . '/../../../.env';
|
||||
$envLines = file($envFile, FILE_IGNORE_NEW_LINES);
|
||||
|
||||
$privateKey = '';
|
||||
|
||||
foreach ($envLines as $line) {
|
||||
if (stripos($line, 'private_key=') === 0) {
|
||||
$privateKey = trim(substr($line, 12));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($privateKey)) {
|
||||
die("错误: 无法读取私钥\n");
|
||||
}
|
||||
|
||||
echo "私钥长度: " . strlen($privateKey) . "\n\n";
|
||||
|
||||
// 建行传来的密文
|
||||
$ccbParamSJ = 'cURYdUJxQ2pJTlVWQUltSW9DQWN1T2xJeFI4WjR2ZVY0NENTVmxPQW9RUVJ1bnJ2Ync5MkwzS2dXdDF1TTVSd3NvUFlsZWFURlYvK243aFNGaWRNeTJsZXRxM1VJR2k0VFdqNnU0Z2JXRnVxMnVIcjhzSVcrRW4xOGxaWDA4Snk0VW1hcFllZ05pekxmc2h6dGFCOTloTUtnVXh2ekJNbDFXZzdwT3lOcDBHZGx5N0FidW1FY1NudmlhNGcxZFAwM09BRk5sUE9USkM4QlJNNWtGTzRKVjFqTWx6RlhrblNwWG0rYmxpS2kyOFpYLy91OGZCR1NPNGp2dHN6b3N1TGxNZnUzd2JNeVUyOVpFTzYxWDRyNm0yb2c0ekZCQWpjc1VLdU9Kcmh1VjVwVDdQZlBlQ0MwRWhLQXFWWXBOdi9uS0FQNkZkZ2JER0ljN2tlUXNHdlFMTWlDeU9rVzdFU2phdG5jYjhZOUNaWkVEb29OdGVLdmlscXg4eHRFS1VtWHljU2ZNeUlDbk5yRW5nR3VXS1AvN3NacTNISE5BWSt2azBHdU1IaG5UWkl6bTdXUmhVYnNRclRYb0ljNy9UTnZVT3lmSVFIcTZ0KzZpZGdtc1NDejBkajVsSjkzVXNSM0NWZExCUk85NHQxZGNEOWorUnFhV2hrWmpiYUE4SlY=';
|
||||
|
||||
echo "========================================\n";
|
||||
echo "开始解密...\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
try {
|
||||
// 步骤1: URLDecode
|
||||
echo "步骤1: URLDecode\n";
|
||||
$step1 = urldecode($ccbParamSJ);
|
||||
echo " 长度: " . strlen($step1) . "\n";
|
||||
echo " 是否改变: " . ($step1 === $ccbParamSJ ? '否' : '是') . "\n\n";
|
||||
|
||||
// 步骤2: 第一次 BASE64 解码
|
||||
echo "步骤2: 第一次 BASE64 解码\n";
|
||||
$step2 = base64_decode($step1);
|
||||
if ($step2 === false) {
|
||||
throw new \Exception('第一次 BASE64 解码失败');
|
||||
}
|
||||
echo " 解码后长度: " . strlen($step2) . "\n";
|
||||
echo " 内容预览: " . substr($step2, 0, 50) . "...\n";
|
||||
|
||||
// 检查是否还是 BASE64
|
||||
$isBase64 = preg_match('/^[A-Za-z0-9+\/=]+$/', $step2);
|
||||
echo " 是否仍为 BASE64: " . ($isBase64 ? '是(双重编码)' : '否') . "\n\n";
|
||||
|
||||
// 步骤3: 第二次 BASE64 解码(建行用了双重 BASE64!)
|
||||
echo "步骤3: 第二次 BASE64 解码\n";
|
||||
$step3 = base64_decode($step2);
|
||||
if ($step3 === false) {
|
||||
throw new \Exception('第二次 BASE64 解码失败');
|
||||
}
|
||||
echo " 解码后长度: " . strlen($step3) . "\n";
|
||||
echo " 十六进制预览: " . bin2hex(substr($step3, 0, 32)) . "...\n\n";
|
||||
|
||||
// 步骤4: 用私钥 RSA 解密
|
||||
echo "步骤4: RSA 私钥解密\n";
|
||||
|
||||
// 注意:这里不能再用 CcbRSA::decrypt(),因为它内部会再次 BASE64 解码
|
||||
// 我们需要直接调用 openssl_private_decrypt
|
||||
|
||||
// 格式化私钥
|
||||
$pemPrivateKey = $privateKey;
|
||||
if (strpos($pemPrivateKey, '-----BEGIN') === false) {
|
||||
$pemPrivateKey = preg_replace('/\s+/', '', $pemPrivateKey);
|
||||
$pemPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n" .
|
||||
chunk_split($pemPrivateKey, 64, "\n") .
|
||||
"-----END RSA PRIVATE KEY-----\n";
|
||||
}
|
||||
|
||||
$priKey = openssl_pkey_get_private($pemPrivateKey);
|
||||
if (!$priKey) {
|
||||
throw new \Exception('私钥格式错误: ' . openssl_error_string());
|
||||
}
|
||||
|
||||
// 获取密钥大小
|
||||
$keyDetails = openssl_pkey_get_details($priKey);
|
||||
$keySize = $keyDetails['bits'] / 8; // 128 字节 (1024 位)
|
||||
|
||||
echo " 密钥大小: {$keySize} 字节\n";
|
||||
echo " 加密数据长度: " . strlen($step3) . " 字节\n";
|
||||
|
||||
// 分块解密
|
||||
$blocks = str_split($step3, $keySize); // 使用 step3(第二次解码后的数据)
|
||||
$decrypted = '';
|
||||
|
||||
echo " 分块数: " . count($blocks) . "\n";
|
||||
|
||||
foreach ($blocks as $i => $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";
|
||||
}
|
||||
@ -1,152 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* 建行参数解密测试脚本
|
||||
*
|
||||
* 用法:
|
||||
* php addons/shopro/test/test_decrypt.php
|
||||
*/
|
||||
|
||||
// 手动加载phpseclib(避免触发FastAdmin/ThinkPHP初始化)
|
||||
spl_autoload_register(function ($class) {
|
||||
$prefix = 'phpseclib3\\';
|
||||
$base_dir = __DIR__ . '/../../../vendor/phpseclib/phpseclib/phpseclib/';
|
||||
|
||||
$len = strlen($prefix);
|
||||
if (strncmp($prefix, $class, $len) !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$relative_class = substr($class, $len);
|
||||
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
|
||||
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
}
|
||||
});
|
||||
|
||||
echo "========================================\n";
|
||||
echo " 建行参数解密测试工具\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
// 测试参数(来自你的URL)
|
||||
$ccbParamSJ = 'WmRMaDdYbUJ5MFl3Ymg5Wk8xL2xwL2k1WkxzMy9kRzdVbTZQMkNIVExudjAvZjRZcUQ1REVFR2JKalVKS3N2SDlzaEIrc3ljdkc3YnRVdE5WalRldlVQRWJSMEo0NndxSjZaMXVYYTlMMFhZRFJvdWcwb3lyL0t1dFEzcjdlOWwxaTBEamh5TS92SG5wOWN3S0Mzd1I2SXZPNnRZYS95bzlTZWVYT3BTVWJjU25JQ2JyUUp4WVdzam9zSXZnek5jKy9oVEwyN0wvQk1wUnJnRWtUdFM3NHdQd0txY3dNUXQ4SzRtMEhwTTc1UHRHcVpPTVRDRUpEVjR6RDdqUll2UURaN21yZnFDaG9od3RFUkhOcGNud2Nib2NjTDlsRjR4Rk44TWo4WmJFYzVqWWpYeHRENE1aMmJuMWY2RThuSVlVcVJBR096STllWm9JN1h1eG54RXhad2szalc3RWYvZHRkb1FZTEVRL3VzTHNkdWFBa0RVRURFaVRtSVVJY1R4dm5kODlYODVnV3JvR1pOY2N1aXhVdXBSYzJBYWlUSk81WG9EeGRkei9QVTVoMEJLNUZTK3MwNWRmTk9jbU5jcTB5SWdLWWMwbkxGem9nMVVscHc4bzVKRzNDWEFvTksyam1kT1k5Z04yQjRJc2RneVhhQ1V5QnNrSGJUSmt0VU8=';
|
||||
|
||||
echo "输入参数:\n";
|
||||
echo "ccbParamSJ 长度: " . strlen($ccbParamSJ) . "\n\n";
|
||||
|
||||
// 从.env文件读取Service ID
|
||||
$envFile = __DIR__ . '/../../../.env';
|
||||
$serviceId = 'YS44000009001853'; // 默认值
|
||||
|
||||
if (file_exists($envFile)) {
|
||||
$envContent = file_get_contents($envFile);
|
||||
if (preg_match('/service_id\s*=\s*(.+)/i', $envContent, $matches)) {
|
||||
$serviceId = trim($matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
echo "Service ID: {$serviceId}\n";
|
||||
echo "DES密钥(前8位): " . substr($serviceId, 0, 8) . "\n\n";
|
||||
|
||||
// 执行解密
|
||||
echo "========================================\n";
|
||||
echo "开始解密...\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
try {
|
||||
// 第一次 BASE64 解码
|
||||
echo "步骤1: 第一次BASE64解码\n";
|
||||
$firstDecode = base64_decode($ccbParamSJ);
|
||||
if ($firstDecode === false) {
|
||||
die("错误: 第一次BASE64解码失败\n");
|
||||
}
|
||||
echo " 解码成功,长度: " . strlen($firstDecode) . "\n";
|
||||
echo " 内容预览: " . substr($firstDecode, 0, 50) . "...\n\n";
|
||||
|
||||
// 第二次 BASE64 解码
|
||||
echo "步骤2: 第二次BASE64解码\n";
|
||||
$secondDecode = base64_decode($firstDecode);
|
||||
if ($secondDecode === false) {
|
||||
die("错误: 第二次BASE64解码失败\n");
|
||||
}
|
||||
echo " 解码成功,长度: " . strlen($secondDecode) . "\n";
|
||||
echo " 十六进制预览: " . bin2hex(substr($secondDecode, 0, 32)) . "...\n\n";
|
||||
|
||||
// DES 解密
|
||||
echo "步骤3: DES解密(尝试不同的密钥)\n";
|
||||
|
||||
// 使用phpseclib进行DES-ECB解密
|
||||
if (!class_exists('\phpseclib3\Crypt\DES')) {
|
||||
die("错误: phpseclib未安装,请运行: composer require phpseclib/phpseclib\n");
|
||||
}
|
||||
|
||||
// 尝试不同的密钥
|
||||
$possibleKeys = [
|
||||
substr($serviceId, 0, 8), // serviceId前8位
|
||||
substr($serviceId, -8), // serviceId后8位
|
||||
'YS440000', // 固定密钥
|
||||
'12345678', // 测试密钥
|
||||
];
|
||||
|
||||
$decrypted = false;
|
||||
$validKey = '';
|
||||
|
||||
foreach ($possibleKeys as $testKey) {
|
||||
echo " 尝试密钥: {$testKey}\n";
|
||||
|
||||
$cipher = new \phpseclib3\Crypt\DES('ecb');
|
||||
$cipher->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";
|
||||
}
|
||||
@ -1,136 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* RSA解密测试脚本
|
||||
* 测试建行ccbParamSJ参数解密
|
||||
*/
|
||||
|
||||
// 手动加载必要的类
|
||||
require_once __DIR__ . '/../library/ccblife/CcbRSA.php';
|
||||
|
||||
use addons\shopro\library\ccblife\CcbRSA;
|
||||
|
||||
echo "========================================\n";
|
||||
echo " 建行 RSA 解密测试\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
// 测试参数
|
||||
$ccbParamSJ = 'cURYdUJxQ2pJTlVWQUltSW9DQWN1T2xJeFI4WjR2ZVY0NENTVmxPQW9RUVJ1bnJ2Ync5MkwzS2dXdDF1TTVSd3NvUFlsZWFURlYvK243aFNGaWRNeTJsZXRxM1VJR2k0VFdqNnU0Z2JXRnVxMnVIcjhzSVcrRW4xOGxaWDA4Snk0VW1hcFllZ05pekxmc2h6dGFCOTloTUtnVXh2ekJNbDFXZzdwT3lOcDBHZGx5N0FidW1FY1NudmlhNGcxZFAwM09BRk5sUE9USkM4QlJNNWtGTzRKVjFqTWx6RlhrblNwWG0rYmxpS2kyOFpYLy91OGZCR1NPNGp2dHN6b3N1TGxNZnUzd2JNeVUyOVpFTzYxWDRyNm0yb2c0ekZCQWpjc1VLdU9Kcmh1VjVwVDdQZlBlQ0MwRWhLQXFWWXBOdi9uS0FQNkZkZ2JER0ljN2tlUXNHdlFMTWlDeU9rVzdFU2phdG5jYjhZOUNaWkVEb29OdGVLdmlscXg4eHRFS1VtWHljU2ZNeUlDbk5yRW5nR3VXS1AvN3NacTNISE5BWSt2azBHdU1IaG5UWkl6bTdXUmhVYnNRclRYb0ljNy9UTnZVT3lmSVFIcTZ0KzZpZGdtc1NDejBkajVsSjkzVXNSM0NWZExCUk85NHQxZGNEOWorUnFhV2hrWmpiYUE4SlY=';
|
||||
|
||||
echo "输入参数:\n";
|
||||
echo "ccbParamSJ 长度: " . strlen($ccbParamSJ) . "\n\n";
|
||||
|
||||
// 直接使用配置文件中的默认私钥(BASE64格式)
|
||||
// 这是配置文件 ccblife.php 中的默认值
|
||||
$privateKey = 'MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALrJmPmtQfP6mURtMxLEXqJHLldN3zYukoaRxG0lw2IdcC86H9C9brFz4YlJ+98z2mdELJaQWu8VWI4actSuPKgHTBr9MSpaii0QQpdINpwXJD9AglIrT7MxhMLYx3qAYDhjKUlC5hnWVYOg4sG32k/3dCebRHY8RDlrXUfHB2+VAgMBAAECgYArgn5R2pv8WymMmOtGudtZbb9LsuYF1v9mvVnGGv/SQQ060w1KMHYye83TjxpOueNsHqNMR0AHZS+Fmn+ZLyUNj9S77oQvUx5HQvY2/TDnsKbETzEMDybIWB+XdLsUkOrB3peVLTbk25i6oSNPOT2Fvd8TWbDqzBL9Ci27uJH72QJBAP/DfDLYoYx9OIRCykkxrDdQVFEkzhXj0wIkLa0Wnf8kP/JfBqvr0AGUPF8nEfh7fLVXYQlh5ab2FL5KvUifSL8CQQC69crW0fryyDHePp6OIVRUbw0T93h52vbGXnoQ6wdvKxZeL3MsfdNUvsJYeSxmtyY+LLgz1p3qOoEn6UpLvCirAkEA4N7qUvY+y3vJdhgXLNV8mkGJcLKQc5SUkJxogHeTQKGJi7ra7ctuXgUMM4jxduxz0CjcS1iEhxBzWn/x/mj1lwJBALgtv39VKLTXx1i7s5Ms/liXdfi/iC3zKbxOAk58WryHY+exMvMXmYMY0Xg7FySxNLl3cJeQy8ydifL5fbmSSTUCQQCj/YUbcTP8BQ6N0AgFdBwmXJyiNkB9zaDI5cEtpSCgq72m8lfn883GJ1MT7nKVXeX69/q5yDQUYiYPBXH4lCEC';
|
||||
|
||||
echo "使用默认私钥(来自配置文件)\n";
|
||||
echo "私钥长度: " . strlen($privateKey) . "\n\n";
|
||||
|
||||
// 执行解密
|
||||
echo "========================================\n";
|
||||
echo "开始解密...\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
try {
|
||||
// 步骤1: URLDecode
|
||||
echo "步骤1: URLDecode\n";
|
||||
$urlDecoded = urldecode($ccbParamSJ);
|
||||
echo " URLDecode 后长度: " . strlen($urlDecoded) . "\n";
|
||||
echo " 是否改变: " . ($urlDecoded === $ccbParamSJ ? '否' : '是') . "\n\n";
|
||||
|
||||
// 步骤2: 显示密钥信息
|
||||
echo "步骤2: 私钥信息\n";
|
||||
echo " 私钥长度: " . strlen($privateKey) . "\n";
|
||||
echo " 是否包含 PEM 头: " . (strpos($privateKey, '-----BEGIN') !== false ? '是' : '否') . "\n\n";
|
||||
|
||||
// 步骤3: 尝试 RSA 解密
|
||||
echo "步骤3: RSA 解密\n";
|
||||
|
||||
// 直接调用 CcbRSA::decrypt
|
||||
$decrypted = CcbRSA::decrypt($urlDecoded, $privateKey);
|
||||
|
||||
echo " 解密成功!\n";
|
||||
echo " 解密后长度: " . strlen($decrypted) . "\n";
|
||||
echo " 解密内容: " . $decrypted . "\n\n";
|
||||
|
||||
// 步骤4: 解析参数
|
||||
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";
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* 验证 RSA 密钥对是否匹配
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../library/ccblife/CcbRSA.php';
|
||||
|
||||
use addons\shopro\library\ccblife\CcbRSA;
|
||||
|
||||
echo "========================================\n";
|
||||
echo " RSA 密钥对验证\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
// 从 .env 读取密钥
|
||||
$envFile = __DIR__ . '/../../../.env';
|
||||
$envLines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
|
||||
$privateKey = '';
|
||||
$publicKey = '';
|
||||
|
||||
foreach ($envLines as $line) {
|
||||
if (stripos($line, 'private_key=') !== false) {
|
||||
$privateKey = trim(substr($line, strpos($line, '=') + 1));
|
||||
}
|
||||
if (stripos($line, 'public_key=') !== false) {
|
||||
$publicKey = trim(substr($line, strpos($line, '=') + 1));
|
||||
}
|
||||
}
|
||||
|
||||
echo "1. 密钥信息\n";
|
||||
echo " 私钥长度: " . strlen($privateKey) . "\n";
|
||||
echo " 公钥长度: " . strlen($publicKey) . "\n\n";
|
||||
|
||||
// 验证密钥对
|
||||
echo "2. 验证密钥对是否匹配\n";
|
||||
|
||||
try {
|
||||
// 测试数据
|
||||
$testData = "userid=test123&mobile=13800138000&openid=testOpenId";
|
||||
|
||||
echo " 原始数据: {$testData}\n";
|
||||
|
||||
// 用公钥加密
|
||||
$encrypted = CcbRSA::encrypt($testData, $publicKey);
|
||||
echo " ✓ 公钥加密成功\n";
|
||||
echo " 加密后长度: " . strlen($encrypted) . "\n";
|
||||
|
||||
// 用私钥解密
|
||||
$decrypted = CcbRSA::decrypt($encrypted, $privateKey);
|
||||
echo " ✓ 私钥解密成功\n";
|
||||
echo " 解密结果: {$decrypted}\n";
|
||||
|
||||
// 验证
|
||||
if ($decrypted === $testData) {
|
||||
echo "\n ✅ 密钥对匹配!公钥和私钥配对正确。\n\n";
|
||||
} else {
|
||||
echo "\n ❌ 密钥对不匹配!解密结果与原始数据不一致。\n\n";
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo " ❌ 错误: " . $e->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";
|
||||
Loading…
x
Reference in New Issue
Block a user