mirror of
https://gitee.com/liuxioabin/fengketrade.git
synced 2026-04-17 21:03:17 +08:00
日志记录
This commit is contained in:
parent
2dc5ff423f
commit
8de5d88091
@ -108,7 +108,7 @@ return [
|
|||||||
|
|
||||||
// 商户信息
|
// 商户信息
|
||||||
'merchant' => [
|
'merchant' => [
|
||||||
'name' => Env::get('ccb.merchant_name', '商户名称'),
|
'name' => Env::get('ccb.merchant_name', '丰科贸易(荷西嘉园店)'),
|
||||||
'logo_url' => Env::get('ccb.merchant_logo', ''),
|
'logo_url' => Env::get('ccb.merchant_logo', ''),
|
||||||
'order_detail_url' => Env::get('app_url', 'http://fengketrade.test') . '/pages/order/detail?id=',
|
'order_detail_url' => Env::get('app_url', 'http://fengketrade.test') . '/pages/order/detail?id=',
|
||||||
],
|
],
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace addons\shopro\library\ccblife;
|
namespace addons\shopro\library\ccblife;
|
||||||
|
|
||||||
|
use think\Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 建行生活HTTP客户端
|
* 建行生活HTTP客户端
|
||||||
* 处理与建行API的通信,包括加密、签名、发送请求和解密响应
|
* 处理与建行API的通信,包括加密、签名、发送请求和解密响应
|
||||||
@ -48,6 +50,10 @@ class CcbHttpClient
|
|||||||
// 构建请求报文
|
// 构建请求报文
|
||||||
$message = $this->buildMessage($txCode, $body, $txSeq);
|
$message = $this->buildMessage($txCode, $body, $txSeq);
|
||||||
|
|
||||||
|
// 📝 记录原始请求报文(加密前)
|
||||||
|
Log::info('建行生活API原始请求报文 [txCode=' . $txCode . '] [txSeq=' . $txSeq . ']');
|
||||||
|
Log::info('原始报文内容: ' . $message);
|
||||||
|
|
||||||
// 加密报文
|
// 加密报文
|
||||||
$encryptedMessage = CcbRSA::encryptForCcb($message, $this->config['public_key']);
|
$encryptedMessage = CcbRSA::encryptForCcb($message, $this->config['public_key']);
|
||||||
|
|
||||||
@ -99,6 +105,9 @@ class CcbHttpClient
|
|||||||
*/
|
*/
|
||||||
private function sendHttpRequest($txCode, $cnt, $mac)
|
private function sendHttpRequest($txCode, $cnt, $mac)
|
||||||
{
|
{
|
||||||
|
// 记录请求开始时间
|
||||||
|
$startTime = microtime(true);
|
||||||
|
|
||||||
// 构建完整的API URL(基础URL + ?txcode=交易代码)
|
// 构建完整的API URL(基础URL + ?txcode=交易代码)
|
||||||
$apiUrl = $this->config['api_base_url'] . '?txcode=' . $txCode;
|
$apiUrl = $this->config['api_base_url'] . '?txcode=' . $txCode;
|
||||||
|
|
||||||
@ -108,6 +117,11 @@ class CcbHttpClient
|
|||||||
'mac' => $mac
|
'mac' => $mac
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 📝 记录请求参数(加密后的完整内容)
|
||||||
|
Log::info('建行生活API加密请求参数 [txCode=' . $txCode . '] [url=' . $apiUrl . '] [timeout=' . self::DEFAULT_TIMEOUT . 's]');
|
||||||
|
Log::info('加密参数 cnt: ' . $cnt);
|
||||||
|
Log::info('加密参数 mac: ' . $mac);
|
||||||
|
|
||||||
// 初始化CURL
|
// 初始化CURL
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
|
|
||||||
@ -130,17 +144,32 @@ class CcbHttpClient
|
|||||||
$response = curl_exec($ch);
|
$response = curl_exec($ch);
|
||||||
$error = curl_error($ch);
|
$error = curl_error($ch);
|
||||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curlInfo = curl_getinfo($ch);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
|
// 计算请求耗时
|
||||||
|
$costTime = round((microtime(true) - $startTime) * 1000, 2); // 毫秒
|
||||||
|
|
||||||
// 检查错误
|
// 检查错误
|
||||||
if ($error) {
|
if ($error) {
|
||||||
|
// 📝 记录错误日志
|
||||||
|
Log::error('建行生活API请求失败 [txCode=' . $txCode . '] [url=' . $apiUrl . '] [error=' . $error . '] [cost_time=' . $costTime . 'ms] [total_time=' . ($curlInfo['total_time'] ?? 0) . 's] [connect_time=' . ($curlInfo['connect_time'] ?? 0) . 's]');
|
||||||
throw new \Exception('HTTP请求失败: ' . $error);
|
throw new \Exception('HTTP请求失败: ' . $error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($httpCode !== 200) {
|
if ($httpCode !== 200) {
|
||||||
|
// 📝 记录HTTP状态码异常日志
|
||||||
|
$responsePreview = mb_substr($response, 0, 300);
|
||||||
|
Log::error('建行生活API响应状态码异常 [txCode=' . $txCode . '] [url=' . $apiUrl . '] [http_code=' . $httpCode . '] [cost_time=' . $costTime . 'ms] [response=' . $responsePreview . ']');
|
||||||
throw new \Exception('HTTP状态码异常: ' . $httpCode . ', 响应内容: ' . $response);
|
throw new \Exception('HTTP状态码异常: ' . $httpCode . ', 响应内容: ' . $response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 📝 记录成功响应日志
|
||||||
|
$totalTime = round(($curlInfo['total_time'] ?? 0) * 1000, 2);
|
||||||
|
$connectTime = round(($curlInfo['connect_time'] ?? 0) * 1000, 2);
|
||||||
|
$responseLength = mb_strlen($response);
|
||||||
|
Log::info('建行生活API请求成功 [txCode=' . $txCode . '] [url=' . $apiUrl . '] [http_code=' . $httpCode . '] [response_length=' . $responseLength . '] [cost_time=' . $costTime . 'ms] [total_time=' . $totalTime . 'ms] [connect_time=' . $connectTime . 'ms]');
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +182,9 @@ class CcbHttpClient
|
|||||||
*/
|
*/
|
||||||
private function handleResponse($response)
|
private function handleResponse($response)
|
||||||
{
|
{
|
||||||
|
// 📝 记录原始响应内容
|
||||||
|
Log::info('建行生活API原始响应内容: ' . $response);
|
||||||
|
|
||||||
// 解析JSON响应
|
// 解析JSON响应
|
||||||
$responseData = json_decode($response, true);
|
$responseData = json_decode($response, true);
|
||||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
@ -164,15 +196,25 @@ class CcbHttpClient
|
|||||||
throw new \Exception('响应格式错误,缺少cnt或mac字段');
|
throw new \Exception('响应格式错误,缺少cnt或mac字段');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 📝 记录加密响应参数
|
||||||
|
Log::info('加密响应参数 cnt: ' . $responseData['cnt']);
|
||||||
|
Log::info('加密响应参数 mac: ' . $responseData['mac']);
|
||||||
|
|
||||||
// 解密响应内容
|
// 解密响应内容
|
||||||
$decryptedContent = CcbRSA::decryptFromCcb($responseData['cnt'], $this->config['private_key']);
|
$decryptedContent = CcbRSA::decryptFromCcb($responseData['cnt'], $this->config['private_key']);
|
||||||
|
|
||||||
|
// 📝 记录解密后的响应内容
|
||||||
|
Log::info('解密后响应内容: ' . $decryptedContent);
|
||||||
|
|
||||||
// 验证签名
|
// 验证签名
|
||||||
$isValid = CcbMD5::verifyApiSignature($decryptedContent, $responseData['mac'], $this->config['private_key']);
|
$isValid = CcbMD5::verifyApiSignature($decryptedContent, $responseData['mac'], $this->config['private_key']);
|
||||||
if (!$isValid) {
|
if (!$isValid) {
|
||||||
|
Log::error('响应签名验证失败 [expected_mac=' . $responseData['mac'] . ']');
|
||||||
throw new \Exception('响应签名验证失败');
|
throw new \Exception('响应签名验证失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log::info('响应签名验证成功');
|
||||||
|
|
||||||
// 解析解密后的内容
|
// 解析解密后的内容
|
||||||
$decryptedData = json_decode($decryptedContent, true);
|
$decryptedData = json_decode($decryptedContent, true);
|
||||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
|||||||
@ -272,7 +272,7 @@ class CcbOrderService
|
|||||||
/**
|
/**
|
||||||
* 构建符合建行 A3341TP01 接口规范的订单数据
|
* 构建符合建行 A3341TP01 接口规范的订单数据
|
||||||
*
|
*
|
||||||
* 📋 建行生活订单推送接口规范说明:
|
* 📋 建行生活订单推送接口规范说明(v1.1.6):
|
||||||
*
|
*
|
||||||
* 必填字段(11个):
|
* 必填字段(11个):
|
||||||
* - USER_ID: 客户编号(建行用户ID)
|
* - USER_ID: 客户编号(建行用户ID)
|
||||||
@ -280,27 +280,30 @@ class CcbOrderService
|
|||||||
* - ORDER_DT: 订单日期(yyyyMMddHHmmss格式)
|
* - ORDER_DT: 订单日期(yyyyMMddHHmmss格式)
|
||||||
* - TOTAL_AMT: 订单原金额
|
* - TOTAL_AMT: 订单原金额
|
||||||
* - ORDER_STATUS: 订单状态
|
* - ORDER_STATUS: 订单状态
|
||||||
|
* - REFUND_STATUS: 退款状态
|
||||||
* - MCT_NM: 商户名称
|
* - MCT_NM: 商户名称
|
||||||
* - CUS_ORDER_URL: 订单详情链接
|
* - CUS_ORDER_URL: 订单详情链接
|
||||||
* - PAY_FLOW_ID: 支付流水号(从 shopro_pay.pay_sn 获取)
|
* - PAY_FLOW_ID: 支付流水号
|
||||||
* - PRPFTL_MRCH_ID: 门店商户号(使用 merchant_id)
|
* - PAY_MRCH_ID: 支付商户号
|
||||||
* - PAY_MRCH_ID: 支付商户号(使用 merchant_id)
|
|
||||||
* - SKU_LIST: 商品信息JSON字符串
|
* - SKU_LIST: 商品信息JSON字符串
|
||||||
*
|
*
|
||||||
* 非必填但推荐字段:
|
* 重要可选字段(建议必填):
|
||||||
* - PAY_AMT: 订单实际支付金额
|
* - PAY_AMT: 订单实际支付金额(文档要求:如为空必须在状态变更时推送)
|
||||||
* - DISCOUNT_AMT: 第三方平台优惠金额
|
* - DISCOUNT_AMT: 第三方平台优惠金额(文档要求:如为空必须在状态变更时推送)
|
||||||
* - DISCOUNT_AMT_DESC: 第三方平台优惠说明
|
* - DISCOUNT_AMT_DESC: 第三方平台优惠说明
|
||||||
* - REFUND_STATUS: 退款状态
|
* - INV_DT: 订单过期日期
|
||||||
* - PAY_MODE: 支付方式
|
* - GOODS_NM: 商品名称
|
||||||
|
* - PREFTL_MRCH_ID: 门店商户号
|
||||||
|
* - PLAT_MCT_ID: 服务商门店编号
|
||||||
* - PLAT_ORDER_TYPE: 服务方订单类型
|
* - PLAT_ORDER_TYPE: 服务方订单类型
|
||||||
* - COUPON_AMT: 优惠券金额
|
* - PLATFORM: 下单场景
|
||||||
*
|
*
|
||||||
* ⚠️ 注意:Shopro字段映射
|
* ⚠️ 注意:Shopro字段映射
|
||||||
* - pay_fee → PAY_AMT(实际支付金额)
|
* - pay_fee → PAY_AMT(实际支付金额)
|
||||||
* - order_amount → TOTAL_AMT(订单总金额)
|
* - order_amount → TOTAL_AMT(订单总金额)
|
||||||
* - total_discount_fee → DISCOUNT_AMT(优惠总金额)
|
* - total_discount_fee → DISCOUNT_AMT(优惠总金额)
|
||||||
* - createtime → ORDER_DT(毫秒时间戳需除以1000)
|
* - createtime → ORDER_DT(毫秒时间戳需除以1000)
|
||||||
|
* - expiry_time → INV_DT(过期时间)
|
||||||
*
|
*
|
||||||
* @param array $order 订单数组
|
* @param array $order 订单数组
|
||||||
* @param array $orderItems 订单商品列表
|
* @param array $orderItems 订单商品列表
|
||||||
@ -309,10 +312,21 @@ class CcbOrderService
|
|||||||
*/
|
*/
|
||||||
private function buildOrderData($order, $orderItems, $ccbUserId)
|
private function buildOrderData($order, $orderItems, $ccbUserId)
|
||||||
{
|
{
|
||||||
|
// ⚠️ 验证必填字段:PAY_FLOW_ID(支付流水号)
|
||||||
|
// 这个字段在 createPayment 时设置,推送订单前必须存在
|
||||||
|
if (empty($order['ccb_pay_flow_id'])) {
|
||||||
|
throw new \Exception('订单支付流水号(ccb_pay_flow_id)不存在,请先调用createPayment生成支付串');
|
||||||
|
}
|
||||||
|
|
||||||
// 构建SKU商品列表(JSON字符串格式)
|
// 构建SKU商品列表(JSON字符串格式)
|
||||||
$skuList = $this->buildSkuList($orderItems);
|
$skuList = $this->buildSkuList($orderItems);
|
||||||
|
|
||||||
// 计算各项金额(保留2位小数)
|
// 计算各项金额(保留2位小数)
|
||||||
$totalAmount = number_format($order['order_amount'] ?? 0, 2, '.', '');
|
$totalAmount = number_format($order['order_amount'] ?? 0, 2, '.', '');
|
||||||
|
$payAmount = number_format($order['pay_fee'] ?? $order['order_amount'] ?? 0, 2, '.', '');
|
||||||
|
$discountAmount = number_format($order['total_discount_fee'] ?? 0, 2, '.', '');
|
||||||
|
$totalRefundAmount = number_format($order['refund_fee'] ?? 0, 2, '.', '');
|
||||||
|
|
||||||
// 处理订单时间(Shopro的createtime是毫秒时间戳,需要除以1000)
|
// 处理订单时间(Shopro的createtime是毫秒时间戳,需要除以1000)
|
||||||
$createTimeValue = $order['createtime'] ?? null;
|
$createTimeValue = $order['createtime'] ?? null;
|
||||||
if (empty($createTimeValue) || !is_numeric($createTimeValue)) {
|
if (empty($createTimeValue) || !is_numeric($createTimeValue)) {
|
||||||
@ -320,8 +334,42 @@ class CcbOrderService
|
|||||||
}
|
}
|
||||||
$orderDt = date('YmdHis', intval($createTimeValue / 1000));
|
$orderDt = date('YmdHis', intval($createTimeValue / 1000));
|
||||||
|
|
||||||
|
// 处理订单过期时间
|
||||||
|
$invDt = '';
|
||||||
|
if (!empty($order['expiry_time'])) {
|
||||||
|
// Shopro 的 expiry_time 可能是时间戳或日期字符串
|
||||||
|
if (is_numeric($order['expiry_time'])) {
|
||||||
|
// 如果是毫秒时间戳,需要除以1000
|
||||||
|
$timestamp = intval($order['expiry_time']);
|
||||||
|
if ($timestamp > 9999999999) {
|
||||||
|
$timestamp = intval($timestamp / 1000);
|
||||||
|
}
|
||||||
|
$invDt = date('YmdHis', $timestamp);
|
||||||
|
} else {
|
||||||
|
$invDt = date('YmdHis', strtotime($order['expiry_time']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取商品名称(取第一个商品)
|
||||||
|
$goodsName = '';
|
||||||
|
if (!empty($orderItems)) {
|
||||||
|
$goodsName = $orderItems[0]['goods_title'] ?? '';
|
||||||
|
// 如果有多个商品,可以拼接
|
||||||
|
if (count($orderItems) > 1) {
|
||||||
|
$goodsName .= ' 等' . count($orderItems) . '件商品';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建优惠说明(如果有优惠金额)
|
||||||
|
$discountAmtDesc = '';
|
||||||
|
if ($discountAmount > 0) {
|
||||||
|
// 格式:名称=金额|@|名称=金额
|
||||||
|
// 这里简化处理,实际应该根据具体优惠券信息构建
|
||||||
|
$discountAmtDesc = '平台优惠=' . $discountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
// 构建符合A3341TP01接口规范的订单数据
|
// 构建符合A3341TP01接口规范的订单数据
|
||||||
return [
|
$orderData = [
|
||||||
// ========== 必填字段 ==========
|
// ========== 必填字段 ==========
|
||||||
'USER_ID' => $ccbUserId, // 客户编号
|
'USER_ID' => $ccbUserId, // 客户编号
|
||||||
'ORDER_ID' => $order['order_sn'], // 订单号
|
'ORDER_ID' => $order['order_sn'], // 订单号
|
||||||
@ -330,13 +378,40 @@ class CcbOrderService
|
|||||||
'ORDER_STATUS' => $this->mapOrderStatus($order['status']), // 订单状态
|
'ORDER_STATUS' => $this->mapOrderStatus($order['status']), // 订单状态
|
||||||
'REFUND_STATUS' => $this->mapRefundStatus($order['refund_status'] ?? 0), // 退款状态
|
'REFUND_STATUS' => $this->mapRefundStatus($order['refund_status'] ?? 0), // 退款状态
|
||||||
'MCT_NM' => $this->config['merchant']['name'] ?? '商户名称', // 商户名称
|
'MCT_NM' => $this->config['merchant']['name'] ?? '商户名称', // 商户名称
|
||||||
'CUS_ORDER_URL' => $this->config['merchant']['order_detail_url'] . $order['id'], // 订单详情链接
|
'PAY_FLOW_ID' => $order['ccb_pay_flow_id'], // 支付流水号(必填!已在上方验证)
|
||||||
'PAY_FLOW_ID' => $order['ccb_pay_flow_id'], // 支付流水号(必填!)
|
'PAY_MRCH_ID' => $this->config['merchant_id'], // 支付商户号(必填!)
|
||||||
'PAY_MRCH_ID' => $this->config['merchant_id'], // 支付商户号(必填!)
|
|
||||||
'SKU_LIST' => $skuList, // 商品信息JSON字符串(必填!)
|
'SKU_LIST' => $skuList, // 商品信息JSON字符串(必填!)
|
||||||
// ========== 非必填字段 ==========
|
|
||||||
'PLAT_ORDER_TYPE' => "T0000", // 服务方订单类型
|
// ========== 重要可选字段(强烈建议填写) ==========
|
||||||
|
'PAY_AMT' => $payAmount, // 订单实际支付金额
|
||||||
|
'DISCOUNT_AMT' => $discountAmount, // 第三方平台优惠金额
|
||||||
|
'PLAT_ORDER_TYPE' => 'T0000', // 服务方订单类型(T0000-普通类型)
|
||||||
|
'PLATFORM' => '99', // 下单场景(99-建行生活APP)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// ========== 条件可选字段(有值才添加) ==========
|
||||||
|
|
||||||
|
// 优惠说明
|
||||||
|
if (!empty($discountAmtDesc)) {
|
||||||
|
$orderData['DISCOUNT_AMT_DESC'] = $discountAmtDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单过期时间
|
||||||
|
if (!empty($invDt)) {
|
||||||
|
$orderData['INV_DT'] = $invDt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 商品名称
|
||||||
|
if (!empty($goodsName)) {
|
||||||
|
$orderData['GOODS_NM'] = mb_substr($goodsName, 0, 200); // 限制长度200字符
|
||||||
|
}
|
||||||
|
|
||||||
|
// 累计退款金额(如果有退款)
|
||||||
|
if ($totalRefundAmount > 0) {
|
||||||
|
$orderData['TOTAL_REFUND_AMT'] = $totalRefundAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $orderData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user