添加虚拟支付

This commit is contained in:
Dev
2026-10-27 16:19:46 +08:00
parent b9122fd6c8
commit ed5c1e8c48
9 changed files with 805 additions and 114 deletions
@@ -168,8 +168,11 @@ class OrderService
'tradeNo' => $orderData['order_no'],
'interactId' => 0,
'transactionId' => 0,
'payInfo' => self::pay($orderData['order_no'], $total_amount, $openid), //统一下单接口
'payInfo' => self::pay($orderData['order_no'], $total_amount, $openid),
];
if (!empty($orderData['payInfo']['paid'])) {
$orderData['order_status'] = self::ORDER_STATUS_WAIT_SHIP;
}
//存入缓存
Cache::set($lockOrderKey, json_encode($orderData), 30);
@@ -300,11 +303,15 @@ class OrderService
'tradeNo' => $orderData['order_no'],
'interactId' => 1,
'transactionId' => 0,
'payInfo' => self::pay($orderInfo->order_no, $orderInfo->total_amount, $openid), //统一下单接口
'payInfo' => self::pay($orderInfo->order_no, $orderInfo->payment_amount, $openid),
];
Log::info('微信下单[下单成功] ' . json_encode([
if (!empty($orderData['payInfo']['paid'])) {
$orderData['order_status'] = self::ORDER_STATUS_WAIT_SHIP;
}
Log::info('虚拟支付下单[下单成功] ' . json_encode([
'order_no' => $orderNo,
'payInfo' => $orderData['payInfo'],
'paid' => $orderData['payInfo']['paid'] ?? 0,
'mode' => $orderData['payInfo']['mode'] ?? '',
], JSON_UNESCAPED_UNICODE));
return $orderData;
@@ -471,84 +478,18 @@ class OrderService
'payment_amount'=> $orderInfo->payment_amount,
], JSON_UNESCAPED_UNICODE));
//金额校验(v3 通知金额单位为分)
$checkMoney = sprintf('%.1f', $total / 100) == sprintf('%.1f', $orderInfo->payment_amount);
if (!$checkMoney) {
Log::error($prefix . '金额校验失败: ' . json_encode([
'notify_amount' => sprintf('%.2f', $total / 100),
'order_amount' => sprintf('%.2f', $orderInfo->payment_amount),
], JSON_UNESCAPED_UNICODE));
throw new \Exception('金额错误:' . sprintf('%.2f', $total / 100) . '----' . sprintf('%.2f', $orderInfo->payment_amount));
}
if (($message->trade_state ?? '') != 'SUCCESS') {
Log::error($prefix . '支付状态异常: trade_state=' . ($message->trade_state ?? ''));
throw new \Exception('支付失败:' . ($message->trade_state ?? ''));
}
//如果为待支付,才可更新为已支付。防止重复更新状态,导致重复发货。
if ($orderInfo->order_status != self::ORDER_STATUS_WAIT_PAY) {
Log::info($prefix . '订单已处理过,跳过状态更新(幂等): ' . json_encode([
'order_no' => $order_no,
'order_status' => $orderInfo->order_status,
'status_text' => self::ORDER_STATUS_MAP[$orderInfo->order_status] ?? '未知',
], JSON_UNESCAPED_UNICODE));
return true;
}
$paymentData = [
'order_no' => $order_no,
'pay_status' => 'SUCCESS',
'amount' => $total / 100,
'currency' => $message->amount['currency'] ?? 'CNY',
'pay_type' => '1', // 1微信 2支付宝
'pay_way' => $message->trade_type ?? 'JSAPI',
'trace_no' => $message->transaction_id ?? '',
'pay_time' => time(),
'pay_success_time' => strtotime((string)($message->success_time ?? '')) ?: time(),
];
Log::info($prefix . '开始更新订单状态: ' . json_encode($paymentData, JSON_UNESCAPED_UNICODE));
try {
Db::transaction(function () use ($order_no, $paymentData, $orderInfo, $prefix) {
$updateRows = self::where('order_no', $order_no)->update(['order_status' => self::ORDER_STATUS_WAIT_SHIP]);
Log::info($prefix . '订单状态更新: order_no=' . $order_no
. ' 10(待支付) -> ' . self::ORDER_STATUS_WAIT_SHIP . '(待发货) 影响行数=' . $updateRows);
$payment = OrderPaymentService::create($paymentData);
Log::info($prefix . '支付流水写入: ' . json_encode([
'payment_id' => $payment->id ?? 0,
'order_no' => $payment->order_no ?? '',
'pay_status' => $payment->pay_status ?? '',
'amount' => $payment->amount ?? 0,
'trace_no' => $payment->trace_no ?? '',
], JSON_UNESCAPED_UNICODE));
if ($orderInfo->order_type == self::ORDER_TYPE_FIRST_PAYMENT) {
Log::info($prefix . '需求首付款订单,处理合同成交: parent_order_no=' . $orderInfo->parent_order_no);
DemandService::customerContractFinishedDeal($orderInfo->parent_order_no);
} elseif ($orderInfo->order_type == self::ORDER_TYPE_FINAL_PAYMENT) {
Log::info($prefix . '需求尾款订单,处理验收结算: parent_order_no=' . $orderInfo->parent_order_no);
DemandService::customerWorkAcceptFinishedDeal($orderInfo->parent_order_no);
}
});
} catch (\Throwable $e) {
Log::error($prefix . '事务执行失败已回滚: msg=' . $e->getMessage()
. ' file=' . $e->getFile() . ':' . $e->getLine());
throw $e;
}
// 复核:确认状态与支付流水真的落库了
$newOrder = OrderService::getOne(['order_no' => $order_no]);
$payment = OrderPaymentService::getOne(['order_no' => $order_no]);
Log::info($prefix . '回调处理成功: ' . json_encode([
'order_no' => $order_no,
'new_order_status'=> $newOrder->order_status ?? null,
'new_status_text' => self::ORDER_STATUS_MAP[$newOrder->order_status ?? -1] ?? '未知',
'payment_id' => $payment->id ?? 0,
'payment_status' => $payment->pay_status ?? '',
'trace_no' => $payment->trace_no ?? '',
], JSON_UNESCAPED_UNICODE));
self::finishPaid(
$order_no,
(int)$total,
(string)($message->transaction_id ?? ''),
(string)($message->trade_type ?? 'JSAPI'),
strtotime((string)($message->success_time ?? '')) ?: time(),
$traceId
);
return true;
});
@@ -568,35 +509,131 @@ class OrderService
}
/**
* @param $order_sn
* @param $total_fee
* @param $openid
* 支付成功入账。虚拟支付确认和微信支付回调共用,重复通知直接返回
* @param string $orderNo
* @param int $amountFen 实付金额,单位分
* @param string $transactionId
* @param string $payWay
* @param int $paidTime
* @param string $traceId
* @return bool
* @throws \Exception
*/
public static function finishPaid($orderNo, $amountFen, $transactionId, $payWay, $paidTime, $traceId = '')
{
$prefix = '订单支付入账 trace=' . $traceId . ' ';
try {
Db::transaction(function () use ($orderNo, $amountFen, $transactionId, $payWay, $paidTime, $prefix) {
$orderInfo = self::where('order_no', $orderNo)->lock(true)->find();
if (!$orderInfo) {
Log::error($prefix . '订单不存在: ' . $orderNo);
throw new \Exception('获取订单数据失败');
}
if ((int)$orderInfo->order_status !== self::ORDER_STATUS_WAIT_PAY) {
Log::info($prefix . '订单已处理过,跳过状态更新: ' . json_encode([
'order_no' => $orderNo,
'order_status' => $orderInfo->order_status,
], JSON_UNESCAPED_UNICODE));
return;
}
$checkMoney = sprintf('%.1f', $amountFen / 100) == sprintf('%.1f', $orderInfo->payment_amount);
if (!$checkMoney) {
Log::error($prefix . '金额校验失败: ' . json_encode([
'notify_amount' => sprintf('%.2f', $amountFen / 100),
'order_amount' => sprintf('%.2f', $orderInfo->payment_amount),
], JSON_UNESCAPED_UNICODE));
throw new \Exception('金额错误:' . sprintf('%.2f', $amountFen / 100) . '----' . sprintf('%.2f', $orderInfo->payment_amount));
}
$paymentData = [
'order_no' => $orderNo,
'pay_status' => 'SUCCESS',
'amount' => $amountFen / 100,
'currency' => 'CNY',
'pay_type' => '1',
'pay_way' => $payWay ?: 'XPAY',
'trace_no' => $transactionId,
'pay_time' => time(),
'pay_success_time' => $paidTime > 0 ? $paidTime : time(),
];
self::where('order_no', $orderNo)->update(['order_status' => self::ORDER_STATUS_WAIT_SHIP]);
OrderPaymentService::create($paymentData);
if ($orderInfo->order_type == self::ORDER_TYPE_FIRST_PAYMENT) {
DemandService::customerContractFinishedDeal($orderInfo->parent_order_no);
} elseif ($orderInfo->order_type == self::ORDER_TYPE_FINAL_PAYMENT) {
DemandService::customerWorkAcceptFinishedDeal($orderInfo->parent_order_no);
}
Log::info($prefix . '入账成功 ' . json_encode($paymentData, JSON_UNESCAPED_UNICODE));
});
} catch (\Exception $e) {
Log::error($prefix . '入账失败: ' . $e->getMessage());
throw $e;
}
return true;
}
/**
* 虚拟支付下单。上一笔现金单已经支付成功时直接入账,不再拉起收银台
* @param string $order_sn
* @param float|string $total_fee
* @param string $openid
* @param string $body
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
* @return array
* @throws \Exception
*/
public static function pay($order_sn, $total_fee, $openid, string $body = '软件产品服务')
{
if (!($body && $order_sn && $total_fee && $openid)) {
Log::error('微信下单[参数错误] ' . json_encode([
'body' => $body,
'order_sn' => $order_sn,
Log::error('虚拟支付下单[参数错误] ' . json_encode([
'body' => $body,
'order_sn' => $order_sn,
'total_fee' => $total_fee,
'openid' => $openid,
'openid' => $openid,
], JSON_UNESCAPED_UNICODE));
throw new \Exception('支付参数错误2');
}
$EasyWeChatService = new EasyWeChatService();
$payRes = $EasyWeChatService->unify($body, $order_sn, $total_fee, $openid);
if (!($payRes && !empty($payRes['prepay_id']))) {
Log::error('微信下单[统一下单失败] order_sn=' . $order_sn . ' res=' . json_encode($payRes, JSON_UNESCAPED_UNICODE));
throw new \Exception('统一下单接口调用失败:' . ($payRes['message'] ?? ($payRes['errmsg'] ?? '未知错误')));
$current = self::getOne(['order_no' => $order_sn]);
if ($current && !in_array((int)$current->order_status, [
self::ORDER_STATUS_WAIT_PAY,
self::ORDER_STATUS_WAIT_SHIP,
self::ORDER_STATUS_SHIPPED,
self::ORDER_STATUS_FINISHED,
], true)) {
throw new \Exception('订单状态不可支付');
}
if (VirtualPayService::settleIfPaid($order_sn, $openid)) {
return ['paid' => 1];
}
Log::info('微信下单[prepay_id] order_sn=' . $order_sn . ' prepay_id=' . $payRes['prepay_id']);
$payData = $EasyWeChatService->sdkConfig($payRes['prepay_id']);
return $payData;
return VirtualPayService::createClientPay($order_sn, $total_fee, $openid);
}
/**
* 客户端支付成功后确认:查现金单、扣代币、更新订单
* @param string $orderNo
* @param int $userId
* @return array
* @throws \Exception
*/
public static function confirmVirtualPay($orderNo, $userId)
{
$order = self::getOne(['order_no' => $orderNo, 'user_id' => $userId]);
if (!$order) {
throw new \Exception('订单不存在');
}
if ((int)$order->order_status !== self::ORDER_STATUS_WAIT_PAY) {
return ['order_status' => (int)$order->order_status];
}
$openid = \app\common\model\User::where('id', $userId)->value('openid');
if (!$openid) {
throw new \Exception('用户未绑定微信');
}
if (!VirtualPayService::settleIfPaid($orderNo, $openid)) {
throw new \Exception('支付结果确认中,请稍后在订单里再次点击支付,不会重复扣款');
}
$fresh = self::getOne(['order_no' => $orderNo]);
return ['order_status' => (int)($fresh->order_status ?? self::ORDER_STATUS_WAIT_SHIP)];
}
/**