2025-10-17 16:32:16 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace addons\shopro\controller;
|
|
|
|
|
|
|
|
|
|
|
|
use addons\shopro\controller\Common;
|
|
|
|
|
|
use addons\shopro\library\ccblife\CcbPaymentService;
|
|
|
|
|
|
use addons\shopro\library\ccblife\CcbOrderService;
|
|
|
|
|
|
use app\admin\model\shopro\order\Order as OrderModel;
|
|
|
|
|
|
use think\Db;
|
|
|
|
|
|
use think\Exception;
|
|
|
|
|
|
use think\Log;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 建行支付控制器
|
|
|
|
|
|
*
|
|
|
|
|
|
* 功能:
|
|
|
|
|
|
* - 生成支付串
|
|
|
|
|
|
* - 处理支付回调
|
|
|
|
|
|
* - 验证支付结果
|
|
|
|
|
|
*
|
|
|
|
|
|
* @author Billy
|
|
|
|
|
|
* @date 2025-01-16
|
|
|
|
|
|
*/
|
|
|
|
|
|
class Ccbpayment extends Common
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 不需要登录的方法 (支付回调不需要登录)
|
|
|
|
|
|
* @var array
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected $noNeedLogin = ['callback', 'notify'];
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 不需要权限的方法
|
|
|
|
|
|
* @var array
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected $noNeedRight = ['*'];
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 支付服务
|
|
|
|
|
|
* @var CcbPaymentService
|
|
|
|
|
|
*/
|
|
|
|
|
|
private $paymentService;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 订单服务
|
|
|
|
|
|
* @var CcbOrderService
|
|
|
|
|
|
*/
|
|
|
|
|
|
private $orderService;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function _initialize()
|
|
|
|
|
|
{
|
|
|
|
|
|
parent::_initialize();
|
|
|
|
|
|
|
|
|
|
|
|
$this->paymentService = new CcbPaymentService();
|
|
|
|
|
|
$this->orderService = new CcbOrderService();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成支付串
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return void
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function createPayment()
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 获取订单ID
|
|
|
|
|
|
$orderId = $this->request->post('order_id', 0);
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($orderId)) {
|
|
|
|
|
|
$this->error('订单ID不能为空');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 查询订单
|
|
|
|
|
|
$order = OrderModel::where('id', $orderId)
|
|
|
|
|
|
->where('user_id', $this->auth->id)
|
|
|
|
|
|
->find();
|
|
|
|
|
|
|
|
|
|
|
|
if (!$order) {
|
|
|
|
|
|
$this->error('订单不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 检查订单状态
|
|
|
|
|
|
if ($order['status'] != 'unpaid') {
|
|
|
|
|
|
$this->error('订单已支付或已关闭');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 生成支付串
|
2025-10-17 17:18:15 +08:00
|
|
|
|
$result = $this->paymentService->generatePaymentString($orderId);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
if (!$result['status']) {
|
|
|
|
|
|
$this->error('支付串生成失败: ' . $result['message']);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 保存支付流水号到订单
|
2025-10-18 01:16:25 +08:00
|
|
|
|
$order->ccb_pay_flow_id = $result['data']['pay_flow_id']; // ✅ 使用真实的支付流水号
|
2025-10-17 16:32:16 +08:00
|
|
|
|
$order->save();
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 记录支付日志
|
2025-10-18 01:16:25 +08:00
|
|
|
|
$this->savePaymentLog($order, $result['data']['payment_string'], $result['data']['pay_flow_id']);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
// 7. 返回支付串
|
|
|
|
|
|
$this->success('支付串生成成功', [
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'payment_string' => $result['data']['payment_string'],
|
|
|
|
|
|
'payment_url' => $result['data']['payment_url'],
|
|
|
|
|
|
'mac' => $result['data']['mac'],
|
2025-10-17 16:32:16 +08:00
|
|
|
|
'order_id' => $order->id,
|
|
|
|
|
|
'order_sn' => $order->order_sn,
|
2025-10-18 01:16:25 +08:00
|
|
|
|
'pay_flow_id' => $result['data']['pay_flow_id'], // ✅ 返回真实的支付流水号
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'amount' => $result['data']['amount'],
|
2025-10-17 16:32:16 +08:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
Log::error('[建行支付] 生成支付串失败 order_id:' . ($orderId ?? 0) . ' error:' . $e->getMessage());
|
|
|
|
|
|
|
|
|
|
|
|
$this->error('生成支付串失败: ' . $e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 支付回调 (前端调用)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 说明:
|
|
|
|
|
|
* 前端调起支付后,建行App会跳转回H5页面
|
|
|
|
|
|
* H5页面需要调用此接口通知后端支付成功
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return void
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function callback()
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 获取参数
|
|
|
|
|
|
$orderId = $this->request->post('order_id', 0);
|
|
|
|
|
|
$transId = $this->request->post('trans_id', '');
|
|
|
|
|
|
$payTime = $this->request->post('pay_time', '');
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($orderId)) {
|
|
|
|
|
|
$this->error('订单ID不能为空');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 查询订单
|
|
|
|
|
|
$order = OrderModel::where('id', $orderId)->find();
|
|
|
|
|
|
|
|
|
|
|
|
if (!$order) {
|
|
|
|
|
|
$this->error('订单不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 检查订单状态
|
|
|
|
|
|
if ($order['status'] == 'paid' || $order['status'] == 'completed') {
|
|
|
|
|
|
$this->success('订单已支付', [
|
|
|
|
|
|
'order_id' => $order->id,
|
|
|
|
|
|
'order_sn' => $order->order_sn,
|
|
|
|
|
|
'status' => $order['status'],
|
|
|
|
|
|
]);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 验证支付结果 (调用建行查询接口)
|
|
|
|
|
|
$verifyResult = $this->paymentService->verifyPayment($order->order_sn);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$verifyResult) {
|
|
|
|
|
|
$this->error('支付验证失败,请稍后再试');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 更新订单状态
|
|
|
|
|
|
Db::startTrans();
|
|
|
|
|
|
try {
|
|
|
|
|
|
$order->status = 'paid';
|
|
|
|
|
|
$order->paid_time = time() * 1000; // Shopro使用毫秒时间戳
|
|
|
|
|
|
$order->save();
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 推送订单状态到建行
|
|
|
|
|
|
$this->pushOrderToCcb($order);
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 更新支付日志
|
|
|
|
|
|
$this->updatePaymentLog($order->ccb_pay_flow_id, [
|
|
|
|
|
|
'status' => 1,
|
|
|
|
|
|
'pay_time' => time(),
|
|
|
|
|
|
'trans_id' => $transId,
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
Db::commit();
|
|
|
|
|
|
|
|
|
|
|
|
Log::info('[建行支付] 支付成功 order_id:' . $order->id . ' order_sn:' . $order->order_sn . ' trans_id:' . $transId);
|
|
|
|
|
|
|
|
|
|
|
|
$this->success('支付成功', [
|
|
|
|
|
|
'order_id' => $order->id,
|
|
|
|
|
|
'order_sn' => $order->order_sn,
|
|
|
|
|
|
'status' => 'paid',
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
Db::rollback();
|
|
|
|
|
|
throw $e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
Log::error('[建行支付] 支付回调失败 order_id:' . ($orderId ?? 0) . ' error:' . $e->getMessage());
|
|
|
|
|
|
|
|
|
|
|
|
$this->error('支付处理失败: ' . $e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 建行支付通知 (建行服务器回调)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 说明:
|
|
|
|
|
|
* 建行支付成功后,会向notify_url发送支付通知
|
|
|
|
|
|
* 这是服务器到服务器的回调,需要验签
|
|
|
|
|
|
*
|
2025-10-18 01:16:25 +08:00
|
|
|
|
* ⚠️ 重要:此接口为建行服务器异步回调,必须返回纯文本 'SUCCESS' 或 'FAIL'
|
|
|
|
|
|
*
|
2025-10-17 16:32:16 +08:00
|
|
|
|
* @return void
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function notify()
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
2025-10-18 01:16:25 +08:00
|
|
|
|
// 1. 获取原始请求数据
|
|
|
|
|
|
$rawData = file_get_contents('php://input');
|
|
|
|
|
|
Log::info('[建行通知] 收到异步通知: ' . $rawData);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 解析POST参数
|
|
|
|
|
|
$params = $this->request->post();
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 如果POST为空,尝试解析原始数据
|
|
|
|
|
|
if (empty($params) && $rawData) {
|
|
|
|
|
|
parse_str($rawData, $params);
|
|
|
|
|
|
}
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-18 01:16:25 +08:00
|
|
|
|
// 4. 记录参数
|
|
|
|
|
|
Log::info('[建行通知] 解析参数: ' . json_encode($params, JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 验证必需参数
|
|
|
|
|
|
if (empty($params['ORDERID'])) {
|
|
|
|
|
|
Log::error('[建行通知] 缺少ORDERID参数');
|
|
|
|
|
|
echo 'FAIL';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 调用支付服务处理通知
|
|
|
|
|
|
$result = $this->paymentService->handleNotify($params);
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 返回处理结果
|
|
|
|
|
|
echo $result; // 'SUCCESS' 或 'FAIL'
|
|
|
|
|
|
|
|
|
|
|
|
Log::info('[建行通知] 处理完成: ' . $result);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
Log::error('[建行通知] 处理失败 error:' . $e->getMessage());
|
2025-10-18 01:16:25 +08:00
|
|
|
|
echo 'FAIL';
|
2025-10-17 16:32:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 推送订单到建行
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param object $order 订单对象
|
|
|
|
|
|
* @return void
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function pushOrderToCcb($order)
|
|
|
|
|
|
{
|
2025-10-17 17:18:15 +08:00
|
|
|
|
// 获取订单商品列表
|
|
|
|
|
|
$orderItems = Db::name('shopro_order_item')
|
|
|
|
|
|
->where('order_id', $order->id)
|
|
|
|
|
|
->field('goods_id, goods_sku_text, goods_title, goods_price, goods_num, discount_fee')
|
|
|
|
|
|
->select();
|
|
|
|
|
|
|
|
|
|
|
|
$goodsList = [];
|
|
|
|
|
|
foreach ($orderItems as $item) {
|
|
|
|
|
|
$goodsList[] = [
|
|
|
|
|
|
'goods_id' => $item['goods_id'],
|
|
|
|
|
|
'goods_name' => $item['goods_title'],
|
|
|
|
|
|
'goods_sku' => $item['goods_sku_text'],
|
|
|
|
|
|
'goods_price' => $item['goods_price'],
|
|
|
|
|
|
'goods_num' => $item['goods_num'],
|
|
|
|
|
|
'discount_amount' => $item['discount_fee'] ?? 0
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户的建行用户ID
|
|
|
|
|
|
$user = Db::name('user')->where('id', $order->user_id)->field('ccb_user_id')->find();
|
|
|
|
|
|
|
2025-10-17 16:32:16 +08:00
|
|
|
|
// 构造订单数据 (使用Shopro实际字段名)
|
|
|
|
|
|
$orderData = [
|
|
|
|
|
|
'id' => $order->id,
|
|
|
|
|
|
'order_sn' => $order->order_sn,
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'ccb_user_id' => $user['ccb_user_id'] ?? '',
|
|
|
|
|
|
'total_amount' => $order->total_amount, // 订单总金额
|
|
|
|
|
|
'pay_amount' => $order->total_fee, // 实际支付金额
|
|
|
|
|
|
'discount_amount' => $order->discount_fee, // 优惠金额
|
|
|
|
|
|
'status' => $order->status, // Shopro使用status枚举
|
|
|
|
|
|
'refund_status' => $order->aftersale_status ?? 0, // 售后状态
|
|
|
|
|
|
'create_time' => $order->createtime, // Shopro使用秒级时间戳
|
|
|
|
|
|
'paid_time' => $order->paid_time, // 支付时间
|
2025-10-17 16:32:16 +08:00
|
|
|
|
'ccb_pay_flow_id' => $order->ccb_pay_flow_id,
|
2025-10-17 17:18:15 +08:00
|
|
|
|
'goods_list' => $goodsList,
|
2025-10-17 16:32:16 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 推送到建行
|
2025-10-17 17:18:15 +08:00
|
|
|
|
$result = $this->orderService->pushOrder($order->id);
|
2025-10-17 16:32:16 +08:00
|
|
|
|
|
2025-10-17 17:18:15 +08:00
|
|
|
|
if (!$result['status']) {
|
|
|
|
|
|
Log::warning('[建行推送] 订单推送失败 order_id:' . $order->id . ' error:' . ($result['message'] ?? ''));
|
2025-10-17 16:32:16 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 更新同步状态
|
|
|
|
|
|
$order->ccb_sync_status = 1;
|
|
|
|
|
|
$order->ccb_sync_time = time();
|
|
|
|
|
|
$order->save();
|
|
|
|
|
|
|
|
|
|
|
|
Log::info('[建行推送] 订单推送成功 order_id:' . $order->id . ' order_sn:' . $order->order_sn);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 保存支付日志
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param object $order 订单对象
|
|
|
|
|
|
* @param string $paymentString 支付串
|
|
|
|
|
|
* @param string $payFlowId 支付流水号
|
|
|
|
|
|
* @return void
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function savePaymentLog($order, $paymentString, $payFlowId)
|
|
|
|
|
|
{
|
|
|
|
|
|
Db::name('ccb_payment_log')->insert([
|
|
|
|
|
|
'order_id' => $order->id,
|
|
|
|
|
|
'order_sn' => $order->order_sn,
|
|
|
|
|
|
'pay_flow_id' => $payFlowId,
|
|
|
|
|
|
'payment_string' => $paymentString,
|
|
|
|
|
|
'user_id' => $order->user_id,
|
|
|
|
|
|
'ccb_user_id' => $order->ccb_user_id,
|
|
|
|
|
|
'amount' => $order->pay_amount,
|
|
|
|
|
|
'status' => 0, // 待支付
|
|
|
|
|
|
'create_time' => time(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新支付日志
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $payFlowId 支付流水号
|
|
|
|
|
|
* @param array $data 更新数据
|
|
|
|
|
|
* @return void
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function updatePaymentLog($payFlowId, $data)
|
|
|
|
|
|
{
|
|
|
|
|
|
Db::name('ccb_payment_log')
|
|
|
|
|
|
->where('pay_flow_id', $payFlowId)
|
|
|
|
|
|
->update($data);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|