1、合作商余额查询
2、分油账户司机可用额度
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 模拟企油通 7.10「运单加油明细推送」 —— 联调 / 补推用(CLI)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 按企油通的真实口径推送:multipart/form-data,业务参数放在表单字段 params 里。
|
||||
// |
|
||||
// | 用法(在站点根目录执行):
|
||||
// | php deploy/qyt_consume_push.php --order=6 --amount=100
|
||||
// | php deploy/qyt_consume_push.php --code=HMCTEST20260914233556192 --amount=100
|
||||
// | php deploy/qyt_consume_push.php --order=6 --amount=30 --dry-run # 只打印报文不发送
|
||||
// |
|
||||
// | 常用参数:
|
||||
// | --order=订单ID 从数据库取运单号等信息(推荐)
|
||||
// | --code=运单唯一流水 不查库直接指定 waybill_no_code
|
||||
// | --amount=金额(元) 本次加油占用金额,默认取订单剩余未加油金额
|
||||
// | --order-code=消费单号 去重键,默认自动生成;想测【重复推送】就填上次用过的
|
||||
// | --station=加油站名 默认「模拟加油站」
|
||||
// | --url=回调地址 默认取 .env 的 QYT.CONSUME_NOTIFY_URL
|
||||
// | --dry-run 只打印,不实际发送
|
||||
// |
|
||||
// | ⚠️ 这会写入真实的加油流水、累计订单已加油金额、并触发给下单平台的加油通知,
|
||||
// | 只用于联调环境。联调结束请删除本文件。
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types=1);
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
http_response_code(403);
|
||||
exit('仅限命令行执行');
|
||||
}
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
|
||||
// ---------------- 参数解析 ----------------
|
||||
$opt = [];
|
||||
foreach (array_slice($argv, 1) as $arg) {
|
||||
if (preg_match('/^--([a-zA-Z0-9\-_]+)(?:=(.*))?$/', $arg, $m)) {
|
||||
$opt[str_replace('-', '_', $m[1])] = $m[2] ?? '1';
|
||||
}
|
||||
}
|
||||
$orderId = isset($opt['order']) ? (int) $opt['order'] : 0;
|
||||
$code = isset($opt['code']) ? trim((string) $opt['code']) : '';
|
||||
$dryRun = !empty($opt['dry_run']);
|
||||
$station = isset($opt['station']) ? (string) $opt['station'] : '模拟加油站';
|
||||
$orderCode = isset($opt['order_code']) ? trim((string) $opt['order_code']) : '';
|
||||
|
||||
if ($orderId <= 0 && $code === '') {
|
||||
exit("用法:php deploy/qyt_consume_push.php --order=<订单ID> [--amount=金额] [--dry-run]\n"
|
||||
. " php deploy/qyt_consume_push.php --code=<运单唯一流水> --amount=金额\n"
|
||||
. "详细参数见本文件头部注释。\n");
|
||||
}
|
||||
|
||||
// ---------------- 读取 .env ----------------
|
||||
$ini = @parse_ini_file($root . '/.env', true, INI_SCANNER_RAW) ?: [];
|
||||
$db = $ini['DATABASE'] ?? [];
|
||||
$qyt = $ini['QYT'] ?? [];
|
||||
$url = isset($opt['url']) ? (string) $opt['url'] : trim((string) ($qyt['CONSUME_NOTIFY_URL'] ?? ''));
|
||||
if ($url === '') {
|
||||
$url = 'http://127.0.0.1/qytconsume.php';
|
||||
}
|
||||
|
||||
// ---------------- 取订单(可选) ----------------
|
||||
$pdo = null;
|
||||
$order = null;
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
'mysql:host=' . ($db['HOSTNAME'] ?? '127.0.0.1') . ';port=' . ($db['HOSTPORT'] ?? '3306')
|
||||
. ';dbname=' . ($db['DATABASE'] ?? 'oil') . ';charset=' . ($db['CHARSET'] ?? 'utf8'),
|
||||
$db['USERNAME'] ?? 'root', $db['PASSWORD'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
echo "提示:连不上数据库({$e->getMessage()}),将只发送推送、不做前后对比\n\n";
|
||||
}
|
||||
|
||||
if ($pdo) {
|
||||
$sql = $orderId > 0
|
||||
? 'SELECT * FROM yoshop_partners_order WHERE order_id = ? LIMIT 1'
|
||||
: 'SELECT * FROM yoshop_partners_order WHERE waybill_no_code = ? LIMIT 1';
|
||||
$st = $pdo->prepare($sql);
|
||||
$st->execute([$orderId > 0 ? $orderId : $code]);
|
||||
$order = $st->fetch() ?: null;
|
||||
if (!$order && $orderId > 0) {
|
||||
exit("未找到订单 order_id={$orderId}\n");
|
||||
}
|
||||
}
|
||||
|
||||
if ($order) {
|
||||
$code = (string) $order['waybill_no_code'];
|
||||
if ($code === '') {
|
||||
exit("订单 {$order['order_id']} 还没有运单流水号(waybill_no_code 为空),说明 7.7 还没成功下发,不能模拟加油\n");
|
||||
}
|
||||
}
|
||||
if ($code === '') {
|
||||
exit("缺少 waybill_no_code:请用 --code 指定,或用 --order 指定订单\n");
|
||||
}
|
||||
|
||||
// ---------------- 金额 ----------------
|
||||
$remain = $order ? round((float) $order['total_price'] - (float) $order['used_amount'], 2) : 0.0;
|
||||
$amount = isset($opt['amount']) ? round((float) $opt['amount'], 2) : $remain;
|
||||
if ($amount <= 0) {
|
||||
exit("加油金额必须大于 0(订单剩余未加油金额为 " . number_format($remain, 2) . " 元,可用 --amount 指定)\n");
|
||||
}
|
||||
if ($order && $amount > $remain + 0.005) {
|
||||
echo "⚠️ 本次金额 {$amount} 元 超过订单剩余未加油金额 " . number_format($remain, 2) . " 元,"
|
||||
. "服务端会照常入账并写超额告警日志\n";
|
||||
}
|
||||
|
||||
// ---------------- 组装报文(字段与 7.10 文档一致) ----------------
|
||||
$fen = (int) round($amount * 100);
|
||||
$item = [
|
||||
'waybill_no' => $order['waybill_no'] ?? ('SIMW' . $code),
|
||||
'order_code' => $orderCode !== '' ? $orderCode : ('SIM' . date('YmdHis') . mt_rand(1000, 9999)),
|
||||
'waybill_no_code' => $code,
|
||||
'quota_id' => $order['qyt_order_code'] ?? ('SIMQ' . mt_rand(100000, 999999)),
|
||||
'passageway' => ($order['passageway'] ?? 'alipay') === 'pingan' ? 2 : 1,
|
||||
'station_code' => '',
|
||||
'station_name' => $station,
|
||||
'pay_fee' => $fen,
|
||||
'pay_time' => date('Y-m-d H:i:s'),
|
||||
'consume_amount' => $fen,
|
||||
'litter' => isset($opt['litter']) ? (string) $opt['litter'] : '',
|
||||
'oil_name' => isset($opt['oil_name']) ? (string) $opt['oil_name'] : '',
|
||||
'oil_price' => '',
|
||||
'actual_oil_price' => '',
|
||||
'scene' => 'oil',
|
||||
];
|
||||
$params = json_encode(['order_list' => [$item]], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
echo "回调地址:{$url}\n";
|
||||
if ($order) {
|
||||
printf("订单:order_id=%s 油卡号=%s 金额=%s 已加油=%s 剩余=%s 加油次数=%s\n",
|
||||
$order['order_id'], $order['oilCardNo'], $order['total_price'], $order['used_amount'],
|
||||
number_format($remain, 2), $order['used_num']);
|
||||
}
|
||||
echo "本次加油:{$amount} 元({$fen} 分) 消费单号(去重键):{$item['order_code']}\n";
|
||||
echo "params:{$params}\n\n";
|
||||
|
||||
if ($dryRun) {
|
||||
echo "--dry-run:未发送\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
// ---------------- 发送:multipart/form-data,params 放表单字段 ----------------
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_POST => true,
|
||||
// 传数组 => cURL 自动用 multipart/form-data 编码,与企油通口径一致
|
||||
CURLOPT_POSTFIELDS => ['params' => $params],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
$http = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
echo "HTTP {$http}\n响应:" . ($resp === false ? "cURL 失败:{$err}" : $resp) . "\n";
|
||||
$ok = is_string($resp) && strpos($resp, '"100"') !== false;
|
||||
echo $ok ? "接收端已受理(code=100)\n" : "⚠️ 接收端未返回 code=100,请查 runtime/storelog 下当天的 qyt_*.txt\n";
|
||||
|
||||
// ---------------- 等队列处理并对比结果 ----------------
|
||||
if (!$ok || !$pdo || !$order) {
|
||||
echo "\n提示:真正的落库与订单累计由 callback:queue 进程完成,请确认该进程在运行。\n";
|
||||
exit;
|
||||
}
|
||||
echo "\n等待 callback:queue 处理";
|
||||
$after = null;
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
sleep(1);
|
||||
echo '.';
|
||||
$st = $pdo->prepare('SELECT used_amount, used_num, use_status, refund_status FROM yoshop_partners_order WHERE order_id = ?');
|
||||
$st->execute([$order['order_id']]);
|
||||
$after = $st->fetch();
|
||||
if ($after && (float) $after['used_amount'] > (float) $order['used_amount']) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
if ($after && (float) $after['used_amount'] > (float) $order['used_amount']) {
|
||||
printf("✅ 已入账:已加油 %s -> %s,加油次数 %s -> %s,加油状态=%s\n",
|
||||
$order['used_amount'], $after['used_amount'], $order['used_num'], $after['used_num'],
|
||||
(int) $after['use_status'] === 1 ? '已加油' : '未加油');
|
||||
$st = $pdo->prepare('SELECT id, order_code, consume_amount, notify_status FROM yoshop_qyt_consume WHERE order_id = ? ORDER BY id DESC LIMIT 3');
|
||||
$st->execute([$order['order_id']]);
|
||||
echo "最近消费流水:\n";
|
||||
foreach ($st->fetchAll() as $r) {
|
||||
printf(" #%s %s %s元 通知状态=%s\n", $r['id'], $r['order_code'],
|
||||
number_format($r['consume_amount'] / 100, 2), $r['notify_status'] ? '已通知' : '待通知');
|
||||
}
|
||||
} else {
|
||||
echo "⚠️ 10 秒内订单没有变化,可能原因:\n"
|
||||
. " 1) callback:queue 进程没在跑(supervisorctl status 看一下)\n"
|
||||
. " 2) 该消费单号已存在,被唯一键判为重复推送(换一个 --order-code 再试)\n"
|
||||
. " 3) 处理异常,查 runtime/storelog 下当天的 " . date('YmdH') . ".txt\n";
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 补推企油通 7.10 加油明细(3 条真实推送原文,无需任何参数)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 用法(在站点根目录执行):
|
||||
// | php deploy/qyt_consume_replay.php
|
||||
// |
|
||||
// | 这 3 条是企油通实际推过、但因解析 bug 被我方静默忽略的报文。
|
||||
// | 报文内容原样保留(order_code / pay_time / quota_id 都是真实值),
|
||||
// | 因此重复执行会被 uk_consume 唯一键拦掉,不会重复累计金额。
|
||||
// |
|
||||
// | 推送口径与企油通一致:multipart/form-data,业务参数放表单字段 params。
|
||||
// |
|
||||
// | ⚠️ 会写入真实加油流水、累计订单已加油金额、并触发给下单平台的加油通知。
|
||||
// | 执行前请确认:① QytNotify.php 的解析修复已部署 ② callback:queue 进程在跑。
|
||||
// | 联调结束请删除本文件。
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types=1);
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
http_response_code(403);
|
||||
exit('仅限命令行执行');
|
||||
}
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
|
||||
// ---------------- 3 条推送原文(原样,未做任何改动) ----------------
|
||||
$payloads = [];
|
||||
|
||||
$payloads[] = <<<'JSON'
|
||||
{"order_list":[{"waybill_no":"HMWTEST20260914233556192","order_code":"2026091823001406181403169153","waybill_no_code":"HMCTEST20260914233556192","quota_id":"2026091000152611910010552563","passageway":1,"station_code":"","station_name":"中国石油北郊加油站","pay_fee":10000,"pay_time":"2026-09-18 14:25:02","consume_amount":10000,"litter":"","oil_name":"","oil_price":"","actual_oil_price":"","scene":"oil"}]}
|
||||
JSON;
|
||||
|
||||
$payloads[] = <<<'JSON'
|
||||
{"order_list":[{"waybill_no":"HMWTEST20260917171322418","order_code":"2026091823001490451444496549","waybill_no_code":"HMCTEST20260917171322418","quota_id":"2026091700152611350010049057","passageway":1,"station_code":"","station_name":"中国石化湾边加油站","pay_fee":10000,"pay_time":"2026-09-18 15:32:32","consume_amount":10000,"litter":"","oil_name":"","oil_price":"","actual_oil_price":"","scene":"oil"}]}
|
||||
JSON;
|
||||
|
||||
$payloads[] = <<<'JSON'
|
||||
{"order_list":[{"waybill_no":"HMWTEST20260917171328296","order_code":"2026091823001490451451416403","waybill_no_code":"HMCTEST20260917171328296","quota_id":"2026091700152611350010049057","passageway":1,"station_code":"","station_name":"中国石化湾边加油站","pay_fee":10000,"pay_time":"2026-09-18 15:32:53","consume_amount":10000,"litter":"","oil_name":"","oil_price":"","actual_oil_price":"","scene":"oil"}]}
|
||||
JSON;
|
||||
|
||||
// ---------------- 回调地址 & 数据库(都取 .env) ----------------
|
||||
$ini = @parse_ini_file($root . '/.env', true, INI_SCANNER_RAW) ?: [];
|
||||
$url = trim((string) ($ini['QYT']['CONSUME_NOTIFY_URL'] ?? ''));
|
||||
if ($url === '') {
|
||||
$url = 'http://127.0.0.1/qytconsume.php';
|
||||
}
|
||||
|
||||
$pdo = null;
|
||||
try {
|
||||
$db = $ini['DATABASE'] ?? [];
|
||||
$pdo = new PDO(
|
||||
'mysql:host=' . ($db['HOSTNAME'] ?? '127.0.0.1') . ';port=' . ($db['HOSTPORT'] ?? '3306')
|
||||
. ';dbname=' . ($db['DATABASE'] ?? 'oil') . ';charset=' . ($db['CHARSET'] ?? 'utf8'),
|
||||
$db['USERNAME'] ?? 'root', $db['PASSWORD'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
echo "提示:连不上数据库({$e->getMessage()}),只发送推送、不做前后对比\n";
|
||||
}
|
||||
|
||||
/** 按 waybill_no_code 取订单快照 */
|
||||
function snap(?PDO $pdo, string $code): ?array
|
||||
{
|
||||
if (!$pdo) {
|
||||
return null;
|
||||
}
|
||||
$st = $pdo->prepare('SELECT order_id, oilCardNo, total_price, used_amount, used_num, use_status
|
||||
FROM yoshop_partners_order WHERE waybill_no_code = ? LIMIT 1');
|
||||
$st->execute([$code]);
|
||||
return $st->fetch() ?: null;
|
||||
}
|
||||
|
||||
echo "回调地址:{$url}\n";
|
||||
echo "待补推:" . count($payloads) . " 条\n";
|
||||
echo str_repeat('=', 66) . "\n\n";
|
||||
|
||||
$okCount = $failCount = 0;
|
||||
|
||||
foreach ($payloads as $i => $params) {
|
||||
$no = $i + 1;
|
||||
$item = json_decode($params, true)['order_list'][0];
|
||||
$code = $item['waybill_no_code'];
|
||||
$yuan = number_format($item['consume_amount'] / 100, 2);
|
||||
|
||||
echo "【{$no}/" . count($payloads) . "】运单流水 {$code}\n";
|
||||
echo " 加油站:{$item['station_name']} 金额:{$yuan} 元 消费单号:{$item['order_code']}\n";
|
||||
|
||||
$before = snap($pdo, $code);
|
||||
if ($pdo && !$before) {
|
||||
echo " ⚠️ 本地找不到该运单流水对应的订单,推过去也会被判为「未匹配订单」,跳过\n\n";
|
||||
$failCount++;
|
||||
continue;
|
||||
}
|
||||
if ($before) {
|
||||
printf(" 订单:order_id=%s 油卡号=%s 金额=%s 已加油=%s(%s 次)\n",
|
||||
$before['order_id'], $before['oilCardNo'], $before['total_price'],
|
||||
$before['used_amount'], $before['used_num']);
|
||||
}
|
||||
|
||||
// 与企油通一致:multipart/form-data,params 放表单字段(cURL 传数组即为 multipart)
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => ['params' => $params],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
$http = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($resp === false) {
|
||||
echo " ❌ cURL 失败:{$err}\n\n";
|
||||
$failCount++;
|
||||
continue;
|
||||
}
|
||||
echo " HTTP {$http} 响应:{$resp}\n";
|
||||
if (strpos($resp, '"100"') === false) {
|
||||
echo " ⚠️ 未返回 code=100,查 runtime/storelog 下当天的 qyt_*.txt\n\n";
|
||||
$failCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 等 callback:queue 落库
|
||||
if (!$before) {
|
||||
echo " 已受理(无数据库连接,未做入账校验)\n\n";
|
||||
$okCount++;
|
||||
continue;
|
||||
}
|
||||
echo ' 等待 callback:queue 处理';
|
||||
$after = null;
|
||||
for ($t = 0; $t < 10; $t++) {
|
||||
sleep(1);
|
||||
echo '.';
|
||||
$after = snap($pdo, $code);
|
||||
if ($after && (float) $after['used_amount'] > (float) $before['used_amount']) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
if ($after && (float) $after['used_amount'] > (float) $before['used_amount']) {
|
||||
printf(" ✅ 已入账:已加油 %s -> %s,次数 %s -> %s,剩余未加油 %s 元\n\n",
|
||||
$before['used_amount'], $after['used_amount'], $before['used_num'], $after['used_num'],
|
||||
number_format(max(0, (float) $after['total_price'] - (float) $after['used_amount']), 2));
|
||||
$okCount++;
|
||||
} else {
|
||||
echo " ⚠️ 10 秒内订单没变化。可能原因:\n"
|
||||
. " 1) callback:queue 进程没在跑(supervisorctl status)\n"
|
||||
. " 2) 这条之前已经入账过,被唯一键判为重复推送(正常,不会重复累计)\n"
|
||||
. " 3) 处理异常,查 runtime/storelog 下当天的 " . date('YmdH') . ".txt\n\n";
|
||||
$failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
echo str_repeat('=', 66) . "\n";
|
||||
echo "完成:成功入账 {$okCount} 条,需关注 {$failCount} 条\n";
|
||||
if ($pdo) {
|
||||
echo "\n各订单当前状态:\n";
|
||||
foreach ($payloads as $params) {
|
||||
$code = json_decode($params, true)['order_list'][0]['waybill_no_code'];
|
||||
$row = snap($pdo, $code);
|
||||
if ($row) {
|
||||
printf(" %s order_id=%s 金额=%s 已加油=%s(%s 次)加油状态=%s\n",
|
||||
$code, $row['order_id'], $row['total_price'], $row['used_amount'], $row['used_num'],
|
||||
(int) $row['use_status'] === 1 ? '已加油' : '未加油');
|
||||
} else {
|
||||
printf(" %s 本地无对应订单\n", $code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | RSA 密钥自检 —— 排查下游报 errCode=0007「请求参数解析异常」(CLI)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 0007 的实际含义是:我方用【平台私钥】解不开下游加密的 AES 密钥。
|
||||
// | 报错里的「PEM routines:no start line」是 OpenSSL 错误队列的残留信息,
|
||||
// | 具有误导性,真正原因几乎都是:下游加密时用的公钥 ≠ 我方平台私钥对应的公钥。
|
||||
// |
|
||||
// | 用法(站点根目录执行):
|
||||
// | php deploy/rsa_check.php 自检密钥对 + 打印要发给下游的公钥
|
||||
// | php deploy/rsa_check.php <appKey> 再检查该合作商的公钥(用于验签其请求)
|
||||
// | php deploy/rsa_check.php <appKey> "<key密文>" 用请求里的 key 字段实测能否解开
|
||||
// |
|
||||
// | 其中「key密文」是下游请求体 {"key":"...","data":"..."} 里的 key 原值。
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types=1);
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
http_response_code(403);
|
||||
exit('仅限命令行执行');
|
||||
}
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
$appKey = $argv[1] ?? '';
|
||||
$encKey = $argv[2] ?? '';
|
||||
|
||||
function line(): void
|
||||
{
|
||||
echo str_repeat('-', 62) . "\n";
|
||||
}
|
||||
|
||||
/** 取密钥指纹,便于和对方核对是不是同一把 */
|
||||
function fingerprint($keyRes): string
|
||||
{
|
||||
$d = openssl_pkey_get_details($keyRes);
|
||||
return isset($d['rsa']['n']) ? strtoupper(substr(md5($d['rsa']['n']), 0, 16)) : '(取不到)';
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 1. 平台密钥对
|
||||
// ================================================================
|
||||
echo "【1】平台密钥对\n";
|
||||
line();
|
||||
|
||||
$privPath = $root . '/rsa/private_key.pem';
|
||||
$pubPath = $root . '/rsa/public_key.pem';
|
||||
|
||||
foreach ([$privPath, $pubPath] as $p) {
|
||||
printf(" %-28s %s\n", basename($p), is_file($p) ? '存在(' . filesize($p) . ' 字节)' : '❌ 不存在');
|
||||
}
|
||||
if (!is_file($privPath) || !is_file($pubPath)) {
|
||||
exit("\n❌ 密钥文件缺失,下游所有请求都会失败。\n");
|
||||
}
|
||||
|
||||
$priv = openssl_pkey_get_private(file_get_contents($privPath));
|
||||
$pub = openssl_pkey_get_public(file_get_contents($pubPath));
|
||||
printf(" 平台私钥加载%s\n", $priv ? '成功' : '❌ 失败:' . openssl_error_string());
|
||||
printf(" 平台公钥加载%s\n", $pub ? '成功' : '❌ 失败:' . openssl_error_string());
|
||||
if (!$priv || !$pub) {
|
||||
exit("\n❌ 密钥文件格式不对,请检查是否为完整 PEM(含 BEGIN/END 行)。\n");
|
||||
}
|
||||
|
||||
$dPriv = openssl_pkey_get_details($priv);
|
||||
$dPub = openssl_pkey_get_details($pub);
|
||||
printf(" 位数:私钥 %d / 公钥 %d\n", $dPriv['bits'], $dPub['bits']);
|
||||
$pairOk = $dPriv['key'] === $dPub['key'];
|
||||
printf(" 两者是否同一对:%s\n", $pairOk ? '是' : '❌ 否(公私钥不配套,必须换成同一对)');
|
||||
|
||||
// 实测闭环
|
||||
$aes = openssl_random_pseudo_bytes(32);
|
||||
openssl_public_encrypt($aes, $enc, $pub, OPENSSL_PKCS1_PADDING);
|
||||
$loopOk = openssl_private_decrypt($enc, $dec, $priv, OPENSSL_PKCS1_PADDING) && $dec === $aes;
|
||||
printf(" 加密→解密闭环:%s\n", $loopOk ? '通过' : '❌ 失败');
|
||||
printf(" 平台公钥指纹:%s\n", fingerprint($pub));
|
||||
|
||||
// ================================================================
|
||||
// 2. 发给下游的公钥(他们必须用这一把加密 AES 密钥)
|
||||
// ================================================================
|
||||
echo "\n【2】请把下面这把【平台公钥】发给下游,让他们核对\n";
|
||||
line();
|
||||
echo trim(file_get_contents($pubPath)) . "\n\n";
|
||||
echo "一行 base64(Java 可直接用):\n";
|
||||
echo preg_replace('/\s+/', '', str_replace(['-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----'], '', file_get_contents($pubPath))) . "\n";
|
||||
|
||||
// ================================================================
|
||||
// 3. 合作商公钥(我方用它验签下游请求)
|
||||
// ================================================================
|
||||
if ($appKey !== '') {
|
||||
echo "\n【3】合作商 {$appKey}\n";
|
||||
line();
|
||||
try {
|
||||
$ini = @parse_ini_file($root . '/.env', true, INI_SCANNER_RAW) ?: [];
|
||||
$db = $ini['DATABASE'] ?? [];
|
||||
$pdo = new PDO(
|
||||
'mysql:host=' . ($db['HOSTNAME'] ?? '127.0.0.1') . ';port=' . ($db['HOSTPORT'] ?? '3306')
|
||||
. ';dbname=' . ($db['DATABASE'] ?? 'oil') . ';charset=' . ($db['CHARSET'] ?? 'utf8'),
|
||||
$db['USERNAME'] ?? 'root', $db['PASSWORD'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
$st = $pdo->prepare('SELECT partners_id, name, orgNo, appKey, publicKey, privateKey FROM yoshop_partners WHERE appKey = ? LIMIT 1');
|
||||
$st->execute([$appKey]);
|
||||
$p = $st->fetch();
|
||||
if (!$p) {
|
||||
echo " ❌ 未找到该 appKey 的合作商\n";
|
||||
} else {
|
||||
printf(" 合作商:%s(partners_id=%s,orgNo=%s)\n", $p['name'], $p['partners_id'], $p['orgNo']);
|
||||
$ppub = openssl_pkey_get_public((string) $p['publicKey']);
|
||||
printf(" 合作商公钥:%s%s\n", $ppub ? '可加载' : '❌ 无法加载:' . openssl_error_string(),
|
||||
$ppub ? ' 指纹 ' . fingerprint($ppub) : '');
|
||||
echo " 用途:我方用它验签下游请求。下游必须用与之配套的私钥加签,否则报 0006。\n";
|
||||
if (!empty($p['privateKey'])) {
|
||||
$ppriv = openssl_pkey_get_private((string) $p['privateKey']);
|
||||
if ($ppub && $ppriv) {
|
||||
$dd = openssl_pkey_get_details($ppriv);
|
||||
printf(" 库里另存的合作商私钥与其公钥是否配套:%s\n",
|
||||
$dd['key'] === openssl_pkey_get_details($ppub)['key'] ? '是' : '❌ 否');
|
||||
echo " (这把私钥是发给下游用的,请确认下游手里就是它)\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
echo ' 查询失败:' . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 4. 用真实请求里的 key 密文实测
|
||||
// ================================================================
|
||||
if ($encKey !== '') {
|
||||
echo "\n【4】实测解密下游请求里的 key\n";
|
||||
line();
|
||||
$raw = base64_decode($encKey, true);
|
||||
if ($raw === false) {
|
||||
echo " ❌ 不是合法 base64\n";
|
||||
} else {
|
||||
printf(" 密文长度:%d 字节(%d 位密钥应为 %d 字节)\n", strlen($raw), $dPriv['bits'], $dPriv['bits'] / 8);
|
||||
while (openssl_error_string() !== false) {
|
||||
// 先清空 OpenSSL 错误队列,避免读到别处残留的 PEM 报错
|
||||
}
|
||||
$ok = openssl_private_decrypt($raw, $out, $priv, OPENSSL_PKCS1_PADDING);
|
||||
if ($ok) {
|
||||
printf(" ✅ 解密成功,AES 密钥长度 %d 字节(应为 32)\n", strlen($out));
|
||||
echo " 说明密钥是配套的,0007 的原因在别处,请把完整请求发来再看。\n";
|
||||
} else {
|
||||
echo " ❌ 解密失败:" . (openssl_error_string() ?: '无附加信息') . "\n";
|
||||
echo " 结论:下游加密用的公钥,与我方平台私钥不是一对。\n";
|
||||
echo " 让下游换成【2】里打印的那把平台公钥即可。\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
Reference in New Issue
Block a user