2026-09-17 16:25:31 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace app\common\service;
|
|
|
|
|
|
|
|
|
|
|
|
use think\Cache;
|
|
|
|
|
|
use think\Db;
|
|
|
|
|
|
use think\Log;
|
2026-09-23 14:46:56 +08:00
|
|
|
|
use EasyWeChat\Pay\Message as PayMessage;
|
2026-09-17 16:25:31 +08:00
|
|
|
|
use app\common\model\Orders;
|
|
|
|
|
|
use app\common\traits\ServiceTrait;
|
|
|
|
|
|
use think\Exception;
|
|
|
|
|
|
|
|
|
|
|
|
class OrderService
|
|
|
|
|
|
{
|
|
|
|
|
|
use ServiceTrait;
|
|
|
|
|
|
|
|
|
|
|
|
/** @var Orders */
|
|
|
|
|
|
public static $Model = Orders::class;
|
|
|
|
|
|
|
|
|
|
|
|
const LOCK_ORDER_PREFIX = 'lock_order_cache:'; //重复创建订单锁
|
|
|
|
|
|
|
2026-09-23 15:03:52 +08:00
|
|
|
|
const DEFAULT_SAAS_ID = '1000'; //默认商家ID(fa_orders.saas_id 为 NOT NULL 且无默认值,商品无归属时兜底)
|
|
|
|
|
|
|
2026-09-17 16:25:31 +08:00
|
|
|
|
//订单状态
|
|
|
|
|
|
const ORDER_STATUS_NO = 0; //关闭
|
|
|
|
|
|
const ORDER_STATUS_WAIT_PAY = 10; //待付款
|
|
|
|
|
|
const ORDER_STATUS_WAIT_SHIP = 20; //已支付(待发货)
|
|
|
|
|
|
const ORDER_STATUS_SHIPPED = 30; //已发货(待收货)
|
|
|
|
|
|
const ORDER_STATUS_FAILED = 31; //交易失败
|
|
|
|
|
|
const ORDER_STATUS_TIMEOUT = 33; //订单超时
|
|
|
|
|
|
const ORDER_STATUS_FINISHED = 40; //已完成
|
|
|
|
|
|
const ORDER_STATUS_CANCEL = 50; //已取消
|
|
|
|
|
|
const ORDER_STATUS_REFUNDED = 60; //已退款
|
|
|
|
|
|
|
|
|
|
|
|
const ORDER_STATUS_MAP = [
|
|
|
|
|
|
self::ORDER_STATUS_NO => '订单关闭',
|
|
|
|
|
|
self::ORDER_STATUS_WAIT_PAY => '待支付',
|
|
|
|
|
|
self::ORDER_STATUS_WAIT_SHIP => '待发货',
|
|
|
|
|
|
self::ORDER_STATUS_SHIPPED => '已发货',
|
|
|
|
|
|
self::ORDER_STATUS_FAILED => '交易失败',
|
|
|
|
|
|
self::ORDER_STATUS_TIMEOUT => '订单超时',
|
|
|
|
|
|
self::ORDER_STATUS_CANCEL => '用户取消订单',
|
|
|
|
|
|
self::ORDER_STATUS_FINISHED => '交易完成',
|
|
|
|
|
|
self::ORDER_STATUS_REFUNDED => '已退款',
|
|
|
|
|
|
];
|
|
|
|
|
|
// 订单操作按钮
|
|
|
|
|
|
const BUTTON_PAY = 1; // 付款
|
|
|
|
|
|
const BUTTON_CANCEL = 2; // 取消订单
|
|
|
|
|
|
const BUTTON_CONFIRM = 3; // 确认收货
|
|
|
|
|
|
const BUTTON_APPLY_REFUND = 4; // 申请售后
|
|
|
|
|
|
const BUTTON_VIEW_REFUND = 5; // 查看退款
|
|
|
|
|
|
const BUTTON_COMMENT = 6; // 评价
|
|
|
|
|
|
const BUTTON_DELETE = 7; // 删除订单
|
|
|
|
|
|
const BUTTON_DELIVERY = 8; // 查看物流
|
|
|
|
|
|
const BUTTON_REBUY = 9; // 再次购买
|
|
|
|
|
|
const BUTTON_INVITE_GROUPON = 11; //邀请好友拼团
|
|
|
|
|
|
|
|
|
|
|
|
const BUTTON_MAP = [
|
|
|
|
|
|
self::BUTTON_PAY => '付款', // 付款
|
|
|
|
|
|
self::BUTTON_CANCEL => '取消订单', // 取消订单
|
|
|
|
|
|
self::BUTTON_CONFIRM => '确认收货', // 确认收货
|
|
|
|
|
|
self::BUTTON_APPLY_REFUND => '申请售后', // 申请售后
|
|
|
|
|
|
self::BUTTON_VIEW_REFUND => '查看退款', // 查看退款
|
|
|
|
|
|
self::BUTTON_COMMENT => '评价', // 评价
|
|
|
|
|
|
self::BUTTON_DELETE => '删除订单', // 删除订单
|
|
|
|
|
|
self::BUTTON_DELIVERY => '查看物流', // 查看物流
|
|
|
|
|
|
self::BUTTON_REBUY => '再次购买', // 再次购买
|
|
|
|
|
|
self::BUTTON_INVITE_GROUPON => '邀请好友拼团', //邀请好友拼团
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const ORDER_TYPE_SOFTWARE = 0;//软件产品;
|
|
|
|
|
|
const ORDER_TYPE_FIRST_PAYMENT = 1;//首付款款订单;
|
|
|
|
|
|
const ORDER_TYPE_FINAL_PAYMENT = 2; //尾款订单;
|
|
|
|
|
|
|
|
|
|
|
|
const ORDER_TYPE_MAP = [
|
|
|
|
|
|
self::ORDER_TYPE_SOFTWARE => '软件产品',
|
|
|
|
|
|
self::ORDER_TYPE_FIRST_PAYMENT => '需求首付款',
|
|
|
|
|
|
self::ORDER_TYPE_FINAL_PAYMENT => '需求尾款',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成订单
|
|
|
|
|
|
* @param $requestData
|
|
|
|
|
|
* @return array
|
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function createOrder($requestData)
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
$user = $requestData['userInfo'] ?? [];
|
|
|
|
|
|
$userId = $user['id'] ?? 0;
|
|
|
|
|
|
$openid = $user['openid'] ?? '';
|
|
|
|
|
|
Log::info('创建订单数据-request:' . json_encode($requestData, JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
if (empty($requestData['goodsRequestList'])) {
|
|
|
|
|
|
throw new \Exception('订单数据错误');
|
|
|
|
|
|
}
|
|
|
|
|
|
//如果30秒内有重复数据,直接返回缓存订单,不再创建
|
|
|
|
|
|
$lockOrderKey = self::getLockOrderKey($userId, $requestData);
|
|
|
|
|
|
if ($lockOrderRes = Cache::get($lockOrderKey)) {
|
|
|
|
|
|
return json_decode($lockOrderRes, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
$goodsRequestList = $requestData['goodsRequestList'];
|
|
|
|
|
|
$count = count($goodsRequestList);
|
|
|
|
|
|
if ($count == 0) {
|
|
|
|
|
|
Log::error('订单创建失败:商品数量不能为0');
|
|
|
|
|
|
throw new \Exception('订单创建失败:商品数量不能为0');
|
|
|
|
|
|
}
|
|
|
|
|
|
$total_amount = 0;
|
|
|
|
|
|
$total_quantity = 0;
|
|
|
|
|
|
$goodsDataList = [];
|
|
|
|
|
|
foreach ($goodsRequestList as $item) {
|
|
|
|
|
|
$quantity = max(1, $item['quantity']);
|
|
|
|
|
|
$total_quantity += $quantity;
|
|
|
|
|
|
$total_amount += $quantity * $item['price'];
|
|
|
|
|
|
$goodsDataList[] = [
|
|
|
|
|
|
'spu_id' => $item['spuId'],
|
|
|
|
|
|
'sku_id' => $item['sku_id'] ?? '',
|
|
|
|
|
|
'goods_name' => $item['goodsName'],
|
|
|
|
|
|
'goods_picture_url' => $item['primaryImage'],
|
|
|
|
|
|
'origin_price' => $item['price'],
|
|
|
|
|
|
'actual_price' => $item['price'],
|
|
|
|
|
|
'buy_quantity' => $quantity,
|
|
|
|
|
|
'item_total_amount' => $item['price'] * $quantity,
|
|
|
|
|
|
'item_discount_amount' => 0,
|
|
|
|
|
|
'item_payment_amount' => $item['price'] * $quantity,
|
|
|
|
|
|
'goods_payment_price' => $item['price'],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
2026-09-23 15:03:52 +08:00
|
|
|
|
// saas_id 取商品归属商家,取不到时用默认值(fa_orders.saas_id 不允许为空且无默认值)
|
|
|
|
|
|
$goodsInfo = GoodsService::getOne(['spu_id' => $goodsRequestList[0]['spuId'] ?? '']);
|
|
|
|
|
|
$saasId = ($goodsInfo && !empty($goodsInfo->saas_id)) ? $goodsInfo->saas_id : self::DEFAULT_SAAS_ID;
|
|
|
|
|
|
$storeInfo = $requestData['storeInfoList'][0] ?? [];
|
2026-09-17 16:25:31 +08:00
|
|
|
|
$orderData = [
|
|
|
|
|
|
'user_id' => $userId,
|
2026-09-23 15:03:52 +08:00
|
|
|
|
'saas_id' => $saasId,
|
|
|
|
|
|
'store_id' => $storeInfo['storeId'] ?? ($goodsRequestList[0]['storeId'] ?? 1),
|
|
|
|
|
|
'store_name' => $storeInfo['storeName'] ?? '',
|
|
|
|
|
|
'parent_order_no' => '', // 单订单无父子关系,稍后回填为自身订单号
|
2026-09-17 16:25:31 +08:00
|
|
|
|
'order_no' => generate_order_sn(),
|
|
|
|
|
|
'order_status' => OrderService::ORDER_STATUS_WAIT_PAY,
|
|
|
|
|
|
'order_type' => 0,
|
|
|
|
|
|
'total_amount' => $total_amount,
|
|
|
|
|
|
'payment_amount' => $total_amount,
|
2026-09-23 15:03:52 +08:00
|
|
|
|
'goods_amount' => $total_amount,
|
2026-09-17 16:25:31 +08:00
|
|
|
|
'discount_amount' => 0,
|
2026-09-23 15:03:52 +08:00
|
|
|
|
'remark' => $storeInfo['remark'] ?? '',
|
2026-09-17 16:25:31 +08:00
|
|
|
|
];
|
2026-09-23 15:03:52 +08:00
|
|
|
|
$orderData['parent_order_no'] = $orderData['order_no'];
|
2026-09-17 16:25:31 +08:00
|
|
|
|
$existed = OrderService::getOne(['order_no' => $orderData['order_no']]);
|
|
|
|
|
|
if (!$existed) {
|
|
|
|
|
|
//创建订单及订单商品
|
|
|
|
|
|
$orderRes = self::$Model::create($orderData);
|
|
|
|
|
|
Log::info('创建订单数据:' . json_encode($orderData, JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
if (!$orderRes) {
|
|
|
|
|
|
Log::error('创建订单创建失败:' . json_encode($orderData, JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
throw new \Exception('订单创建失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
$goodsRes = $orderRes->orderGoods()->saveAll($goodsDataList);
|
|
|
|
|
|
if (!$goodsRes) {
|
|
|
|
|
|
Log::error('创建订单产品失败:' . json_encode($orderData, JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
throw new \Exception('订单创建数据失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
$orderData += [
|
|
|
|
|
|
'channel' => 'wechat',
|
|
|
|
|
|
'tradeNo' => $orderData['order_no'],
|
|
|
|
|
|
'interactId' => 0,
|
|
|
|
|
|
'transactionId' => 0,
|
|
|
|
|
|
'payInfo' => self::pay($orderData['order_no'], $total_amount, $openid), //统一下单接口
|
|
|
|
|
|
];
|
|
|
|
|
|
//存入缓存
|
|
|
|
|
|
Cache::set($lockOrderKey, json_encode($orderData), 30);
|
|
|
|
|
|
|
|
|
|
|
|
return $orderData;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
throw new \Exception('订单数据保存失败:' . $e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成需求订单
|
|
|
|
|
|
* @param $requestData
|
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function createDemandOrder($requestData)
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
$demandId = $requestData['demandId'] ?? 0;
|
|
|
|
|
|
$formData = $requestData['formData'] ?? [];
|
|
|
|
|
|
$totalCost = $formData['totalCost'] ?? 0;
|
|
|
|
|
|
Log::info('创建需求订单数据-request:' . json_encode($requestData, JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
$demand = DemandService::getOne(['id' => $demandId]);
|
|
|
|
|
|
if (!$demand) {
|
|
|
|
|
|
throw new \Exception('该需求不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
$winBid = DemandBiddingService::getOne(['demand_id' => $demandId, 'bidding_status' => DemandBiddingService::STATUS_BIDDING_WIN]);
|
|
|
|
|
|
$orderInfo = self::where(['parent_order_no' => $demand->parent_order_no])->find();
|
|
|
|
|
|
$ratio = 0.5; //分期比率50%
|
|
|
|
|
|
if (!$orderInfo) {
|
|
|
|
|
|
$installments = 2;//分期数
|
|
|
|
|
|
for ($i = 1; $i <= $installments; $i++) {
|
|
|
|
|
|
$total_amount = $i == 1 ? $totalCost * $ratio : $totalCost * (1 - $ratio);
|
|
|
|
|
|
$goodsDataList = [[
|
|
|
|
|
|
'spu_id' => 0,
|
|
|
|
|
|
'sku_id' => 0,
|
|
|
|
|
|
'goods_name' => '需求签约-' . ($i == 1 ? '首付款' : '尾款') . $demand->parent_order_no,
|
|
|
|
|
|
'goods_picture_url' => 'https://soft-market.oss-cn-shenzhen.aliyuncs.com/images/contract.png',
|
|
|
|
|
|
'origin_price' => $total_amount,
|
|
|
|
|
|
'actual_price' => $total_amount,
|
|
|
|
|
|
'buy_quantity' => 1,
|
|
|
|
|
|
'item_total_amount' => $total_amount,
|
|
|
|
|
|
'item_discount_amount' => 0,
|
|
|
|
|
|
'item_payment_amount' => $total_amount,
|
|
|
|
|
|
'goods_payment_price' => $total_amount,
|
|
|
|
|
|
]];
|
|
|
|
|
|
$orderData = [
|
|
|
|
|
|
'user_id' => $demand->user_id,
|
2026-09-23 15:03:52 +08:00
|
|
|
|
'saas_id' => self::DEFAULT_SAAS_ID,
|
2026-09-17 16:25:31 +08:00
|
|
|
|
'store_id' => 1,
|
2026-09-23 15:03:52 +08:00
|
|
|
|
'store_name' => '',
|
2026-09-17 16:25:31 +08:00
|
|
|
|
'parent_order_no' => $demand->parent_order_no,
|
|
|
|
|
|
'order_no' => generate_order_sn(),
|
|
|
|
|
|
'order_status' => OrderService::ORDER_STATUS_WAIT_PAY,
|
|
|
|
|
|
'order_type' => $i,
|
|
|
|
|
|
'total_amount' => $total_amount,
|
|
|
|
|
|
'payment_amount' => $total_amount,
|
|
|
|
|
|
'goods_amount' => $total_amount,
|
|
|
|
|
|
'discount_amount' => 0,
|
|
|
|
|
|
'remark' => '',
|
|
|
|
|
|
];
|
|
|
|
|
|
$existed = OrderService::getOne(['order_no' => $orderData['order_no']]);
|
|
|
|
|
|
if (!$existed) {
|
|
|
|
|
|
//创建订单及订单商品
|
|
|
|
|
|
$orderRes = self::$Model::create($orderData);
|
|
|
|
|
|
Log::info('创建需求订单数据:' . json_encode($orderData, JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
if (!$orderRes) {
|
|
|
|
|
|
Log::error('创建需求订单创建失败:' . json_encode($orderData, JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
throw new \Exception('需求订单创建失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
$goodsRes = $orderRes->orderGoods()->saveAll($goodsDataList);
|
|
|
|
|
|
if (!$goodsRes) {
|
|
|
|
|
|
Log::error('创建需求订单产品失败:' . json_encode($orderData, JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
throw new \Exception('需求订单创建数据失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//查找签约记录,没有则创建
|
|
|
|
|
|
$contractInfo = ContractService::getOne(['demand_id' => $demand, 'bidding_id' => $winBid->id ?? 0]);
|
|
|
|
|
|
if (!$contractInfo) {
|
|
|
|
|
|
//创建合同数据
|
|
|
|
|
|
$firstContract = ContractService::getFirstContract($demandId);
|
|
|
|
|
|
$depositAmount = $firstContract ? $firstContract->deposit_amount : $formData['totalCost'] * $ratio;
|
|
|
|
|
|
ContractService::createData($demandId, $winBid->id ?? 0, $depositAmount, $formData);
|
|
|
|
|
|
}
|
|
|
|
|
|
return $contractInfo;
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
throw new \Exception('需求订单数据保存失败:' . $e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function getDemandOrder($parentOrderNo, $contractType)
|
|
|
|
|
|
{
|
|
|
|
|
|
$res = self::getOne(['parent_order_no' => $parentOrderNo, 'order_type' => $contractType], ['orderGoods', 'orderPayment']);
|
|
|
|
|
|
|
|
|
|
|
|
return $res;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function orderPay($requestData)
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
$user = $requestData['userInfo'] ?? [];
|
|
|
|
|
|
$userId = $user['id'] ?? 0;
|
|
|
|
|
|
$openid = $user['openid'] ?? '';
|
|
|
|
|
|
$orderNo = $requestData['orderNo'] ?? '';
|
|
|
|
|
|
if (!$orderNo) {
|
|
|
|
|
|
throw new \Exception('订单号不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
$fields = 'user_id,store_id,order_no,order_status,order_type,total_amount,payment_amount,goods_amount,discount_amount,remark';
|
|
|
|
|
|
$orderInfo = OrderService::where(['order_no' => $orderNo, 'user_id' => $userId])->field($fields)->find();
|
|
|
|
|
|
if (!$orderInfo) {
|
|
|
|
|
|
throw new \Exception('该订单不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
$orderData = $orderInfo->toArray();
|
2026-09-23 14:46:56 +08:00
|
|
|
|
Log::info('微信下单[发起支付] ' . json_encode([
|
|
|
|
|
|
'order_no' => $orderNo,
|
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
|
'order_status' => $orderData['order_status'] ?? null,
|
|
|
|
|
|
'status_text' => self::ORDER_STATUS_MAP[$orderData['order_status'] ?? -1] ?? '未知',
|
|
|
|
|
|
'payment_amount' => $orderData['payment_amount'] ?? 0,
|
|
|
|
|
|
'total_amount' => $orderData['total_amount'] ?? 0,
|
|
|
|
|
|
], JSON_UNESCAPED_UNICODE));
|
2026-09-17 16:25:31 +08:00
|
|
|
|
$orderData += [
|
|
|
|
|
|
'channel' => 'wechat',
|
|
|
|
|
|
'tradeNo' => $orderData['order_no'],
|
|
|
|
|
|
'interactId' => 1,
|
|
|
|
|
|
'transactionId' => 0,
|
|
|
|
|
|
'payInfo' => self::pay($orderInfo->order_no, $orderInfo->total_amount, $openid), //统一下单接口
|
|
|
|
|
|
];
|
2026-09-23 14:46:56 +08:00
|
|
|
|
Log::info('微信下单[下单成功] ' . json_encode([
|
|
|
|
|
|
'order_no' => $orderNo,
|
|
|
|
|
|
'payInfo' => $orderData['payInfo'],
|
|
|
|
|
|
], JSON_UNESCAPED_UNICODE));
|
2026-09-17 16:25:31 +08:00
|
|
|
|
|
|
|
|
|
|
return $orderData;
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
throw new \Exception('订单支付异常:' . $e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 订单操作
|
|
|
|
|
|
* @param $request
|
|
|
|
|
|
* @return mixed
|
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function orderOperate($requestData, $userId)
|
|
|
|
|
|
{
|
|
|
|
|
|
$goodsInfo = $requestData['goodsInfo'] ?? [];
|
|
|
|
|
|
$type = $requestData['type'] ?? '';
|
|
|
|
|
|
switch ($type) {
|
|
|
|
|
|
case self::BUTTON_CONFIRM:
|
|
|
|
|
|
$res = self::confirmOrder($goodsInfo['id'] ?? 0, $userId);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case self::BUTTON_CANCEL:
|
|
|
|
|
|
$res = self::cancelOrder($goodsInfo['id'] ?? 0, $userId);
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new \Exception('非法操作');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $res;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 取消订单
|
|
|
|
|
|
* @param $order_id
|
|
|
|
|
|
* @param $userId
|
|
|
|
|
|
* @return mixed
|
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function cancelOrder($orderId, $userId = 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
$where = ['id' => $orderId, 'user_id' => $userId];
|
|
|
|
|
|
$order = self::getOne($where);
|
|
|
|
|
|
if (!$order) {
|
|
|
|
|
|
throw new \Exception('该订单不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
Db::startTrans();
|
|
|
|
|
|
if (!in_array($order->order_status, [OrderService::ORDER_STATUS_WAIT_PAY, OrderService::ORDER_STATUS_WAIT_SHIP])) {
|
|
|
|
|
|
throw new \Exception('该订单为' . OrderService::ORDER_STATUS_MAP[$order->order_status] . ',不能取消');
|
|
|
|
|
|
}
|
|
|
|
|
|
//修改订单状态
|
|
|
|
|
|
$upRes = self::where($where)->update(['order_status' => OrderService::ORDER_STATUS_CANCEL]);
|
|
|
|
|
|
if (!$upRes) {
|
|
|
|
|
|
throw new \Exception('更新订单状态失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
//退款原路返回
|
|
|
|
|
|
// OrderRefund::dispatch(['order_sn' => $order->order_sn, 'actual_amount' => $order->actual_amount]);
|
|
|
|
|
|
//给用户发送出票失败结果通知
|
|
|
|
|
|
// SendMsg::dispatch(['order_id' => $order->id, 'user_id' => $order->user_id, 'template_id' => SubscribeMessageService::MSG_TEMP_ORDER_FAILED, 'desc' => SubscribeMessageService::ORDER_MSG_MAP['failed'], 'desc2' => SubscribeMessageService::ORDER_MSG_MAP['refund']]);
|
|
|
|
|
|
|
|
|
|
|
|
Db::commit();
|
|
|
|
|
|
$info = OrderService::getOne(['id' => $orderId]);
|
|
|
|
|
|
|
|
|
|
|
|
return $info;
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Db::rollback();
|
|
|
|
|
|
throw $e;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 确认收货
|
|
|
|
|
|
* @param $orderId
|
|
|
|
|
|
* @param $userId
|
|
|
|
|
|
* @return mixed
|
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function confirmOrder($orderId, $userId = 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
$where = ['id' => $orderId, 'user_id' => $userId];
|
|
|
|
|
|
$order = self::getOne($where);
|
|
|
|
|
|
if (!$order) {
|
|
|
|
|
|
throw new \Exception('该订单不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
Db::startTrans();
|
|
|
|
|
|
if (!in_array($order->order_status, [OrderService::ORDER_STATUS_SHIPPED])) {
|
|
|
|
|
|
throw new \Exception('该订单为' . OrderService::ORDER_STATUS_MAP[$order->order_status] . ',不能确认收货');
|
|
|
|
|
|
}
|
|
|
|
|
|
//修改订单状态
|
|
|
|
|
|
$upRes = self::where($where)->update(['order_status' => OrderService::ORDER_STATUS_FINISHED]);
|
|
|
|
|
|
if (!$upRes) {
|
|
|
|
|
|
throw new \Exception('更新订单状态失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
Db::commit();
|
|
|
|
|
|
$info = OrderService::getOne(['id' => $orderId]);
|
|
|
|
|
|
|
|
|
|
|
|
return $info;
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
|
Db::rollback();
|
|
|
|
|
|
throw $e;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-09-21 14:57:50 +08:00
|
|
|
|
* 微信支付回调(EasyWeChat 6.x / 支付 v3: 自动验签+解密)
|
|
|
|
|
|
* @return \Psr\Http\Message\ResponseInterface
|
2026-09-17 16:25:31 +08:00
|
|
|
|
* @throws \Exception
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function payNotify()
|
|
|
|
|
|
{
|
2026-09-21 14:57:50 +08:00
|
|
|
|
$service = new EasyWeChatService();
|
2026-09-17 16:25:31 +08:00
|
|
|
|
|
2026-09-23 14:46:56 +08:00
|
|
|
|
return $service->handlePayNotify(function (PayMessage $message, $traceId = '') {
|
|
|
|
|
|
$prefix = '微信支付回调[业务] trace=' . $traceId . ' ';
|
2026-09-21 14:57:50 +08:00
|
|
|
|
$order_no = $message->out_trade_no ?? '';
|
2026-09-23 14:46:56 +08:00
|
|
|
|
$total = $message->amount['total'] ?? 0;
|
|
|
|
|
|
|
|
|
|
|
|
Log::info($prefix . '支付成功通知: ' . json_encode([
|
|
|
|
|
|
'order_no' => $order_no,
|
|
|
|
|
|
'transaction_id' => $message->transaction_id ?? '',
|
|
|
|
|
|
'trade_state' => $message->trade_state ?? '',
|
|
|
|
|
|
'trade_type' => $message->trade_type ?? '',
|
|
|
|
|
|
'amount_fen' => $total,
|
|
|
|
|
|
'amount_yuan' => sprintf('%.2f', $total / 100),
|
|
|
|
|
|
'success_time' => $message->success_time ?? '',
|
|
|
|
|
|
], JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
|
2026-09-21 14:57:50 +08:00
|
|
|
|
$orderInfo = OrderService::getOne(['order_no' => $order_no]);
|
|
|
|
|
|
if (!$orderInfo) {
|
2026-09-23 14:46:56 +08:00
|
|
|
|
Log::error($prefix . '订单不存在: ' . json_encode(['order_no' => $order_no], JSON_UNESCAPED_UNICODE));
|
2026-09-21 14:57:50 +08:00
|
|
|
|
throw new \Exception('获取订单数据失败');
|
2026-09-17 16:25:31 +08:00
|
|
|
|
}
|
2026-09-23 14:46:56 +08:00
|
|
|
|
|
|
|
|
|
|
Log::info($prefix . '订单当前状态: ' . json_encode([
|
|
|
|
|
|
'order_id' => $orderInfo->id,
|
|
|
|
|
|
'order_no' => $order_no,
|
|
|
|
|
|
'order_status' => $orderInfo->order_status,
|
|
|
|
|
|
'status_text' => self::ORDER_STATUS_MAP[$orderInfo->order_status] ?? '未知',
|
|
|
|
|
|
'order_type' => $orderInfo->order_type,
|
|
|
|
|
|
'parent_no' => $orderInfo->parent_order_no ?? '',
|
|
|
|
|
|
'payment_amount'=> $orderInfo->payment_amount,
|
|
|
|
|
|
], JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
|
2026-09-21 14:57:50 +08:00
|
|
|
|
//金额校验(v3 通知金额单位为分)
|
|
|
|
|
|
$checkMoney = sprintf('%.1f', $total / 100) == sprintf('%.1f', $orderInfo->payment_amount);
|
2026-09-17 16:25:31 +08:00
|
|
|
|
if (!$checkMoney) {
|
2026-09-23 14:46:56 +08:00
|
|
|
|
Log::error($prefix . '金额校验失败: ' . json_encode([
|
|
|
|
|
|
'notify_amount' => sprintf('%.2f', $total / 100),
|
|
|
|
|
|
'order_amount' => sprintf('%.2f', $orderInfo->payment_amount),
|
|
|
|
|
|
], JSON_UNESCAPED_UNICODE));
|
2026-09-21 14:57:50 +08:00
|
|
|
|
throw new \Exception('金额错误:' . sprintf('%.2f', $total / 100) . '----' . sprintf('%.2f', $orderInfo->payment_amount));
|
2026-09-17 16:25:31 +08:00
|
|
|
|
}
|
2026-09-21 14:57:50 +08:00
|
|
|
|
if (($message->trade_state ?? '') != 'SUCCESS') {
|
2026-09-23 14:46:56 +08:00
|
|
|
|
Log::error($prefix . '支付状态异常: trade_state=' . ($message->trade_state ?? ''));
|
2026-09-21 14:57:50 +08:00
|
|
|
|
throw new \Exception('支付失败:' . ($message->trade_state ?? ''));
|
2026-09-17 16:25:31 +08:00
|
|
|
|
}
|
2026-09-23 14:46:56 +08:00
|
|
|
|
|
2026-09-21 14:57:50 +08:00
|
|
|
|
//如果为待支付,才可更新为已支付。防止重复更新状态,导致重复发货。
|
2026-09-23 14:46:56 +08:00
|
|
|
|
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);
|
2026-09-21 14:57:50 +08:00
|
|
|
|
DemandService::customerContractFinishedDeal($orderInfo->parent_order_no);
|
2026-09-23 14:46:56 +08:00
|
|
|
|
} elseif ($orderInfo->order_type == self::ORDER_TYPE_FINAL_PAYMENT) {
|
|
|
|
|
|
Log::info($prefix . '需求尾款订单,处理验收结算: parent_order_no=' . $orderInfo->parent_order_no);
|
2026-09-21 14:57:50 +08:00
|
|
|
|
DemandService::customerWorkAcceptFinishedDeal($orderInfo->parent_order_no);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-09-23 14:46:56 +08:00
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
|
Log::error($prefix . '事务执行失败已回滚: msg=' . $e->getMessage()
|
|
|
|
|
|
. ' file=' . $e->getFile() . ':' . $e->getLine());
|
|
|
|
|
|
throw $e;
|
2026-09-21 14:57:50 +08:00
|
|
|
|
}
|
2026-09-23 14:46:56 +08:00
|
|
|
|
|
|
|
|
|
|
// 复核:确认状态与支付流水真的落库了
|
|
|
|
|
|
$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));
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
2026-09-21 14:57:50 +08:00
|
|
|
|
});
|
2026-09-17 16:25:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取缓存
|
|
|
|
|
|
* @param $userId
|
|
|
|
|
|
* @param $data array
|
|
|
|
|
|
* @return mixed
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function getLockOrderKey($userId, $data)
|
|
|
|
|
|
{
|
|
|
|
|
|
$key = self::LOCK_ORDER_PREFIX . md5($userId . json_encode($data));
|
|
|
|
|
|
|
|
|
|
|
|
return $key;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @param $order_sn
|
|
|
|
|
|
* @param $total_fee
|
|
|
|
|
|
* @param $openid
|
|
|
|
|
|
* @param string $body
|
|
|
|
|
|
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
|
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function pay($order_sn, $total_fee, $openid, string $body = '软件产品服务')
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!($body && $order_sn && $total_fee && $openid)) {
|
2026-09-23 14:46:56 +08:00
|
|
|
|
Log::error('微信下单[参数错误] ' . json_encode([
|
|
|
|
|
|
'body' => $body,
|
|
|
|
|
|
'order_sn' => $order_sn,
|
|
|
|
|
|
'total_fee' => $total_fee,
|
|
|
|
|
|
'openid' => $openid,
|
|
|
|
|
|
], JSON_UNESCAPED_UNICODE));
|
2026-09-17 16:25:31 +08:00
|
|
|
|
throw new \Exception('支付参数错误2');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$EasyWeChatService = new EasyWeChatService();
|
|
|
|
|
|
$payRes = $EasyWeChatService->unify($body, $order_sn, $total_fee, $openid);
|
2026-09-21 14:57:50 +08:00
|
|
|
|
if (!($payRes && !empty($payRes['prepay_id']))) {
|
2026-09-23 14:46:56 +08:00
|
|
|
|
Log::error('微信下单[统一下单失败] order_sn=' . $order_sn . ' res=' . json_encode($payRes, JSON_UNESCAPED_UNICODE));
|
2026-09-21 14:57:50 +08:00
|
|
|
|
throw new \Exception('统一下单接口调用失败:' . ($payRes['message'] ?? ($payRes['errmsg'] ?? '未知错误')));
|
2026-09-17 16:25:31 +08:00
|
|
|
|
}
|
2026-09-23 14:46:56 +08:00
|
|
|
|
Log::info('微信下单[prepay_id] order_sn=' . $order_sn . ' prepay_id=' . $payRes['prepay_id']);
|
2026-09-17 16:25:31 +08:00
|
|
|
|
$payData = $EasyWeChatService->sdkConfig($payRes['prepay_id']);
|
|
|
|
|
|
|
|
|
|
|
|
return $payData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取订单操作按钮
|
|
|
|
|
|
* @param $orderStatus
|
|
|
|
|
|
* @return array
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function getOrderButtons($orderStatus): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$buttons = [];
|
|
|
|
|
|
if ($orderStatus == self::ORDER_STATUS_WAIT_PAY) {
|
|
|
|
|
|
$buttons[] = ['name' => self::BUTTON_MAP[OrderService::BUTTON_CANCEL], 'primary' => false, 'type' => OrderService::BUTTON_CANCEL];
|
|
|
|
|
|
$buttons[] = ['name' => self::BUTTON_MAP[OrderService::BUTTON_PAY], 'primary' => true, 'type' => OrderService::BUTTON_PAY];
|
|
|
|
|
|
}
|
|
|
|
|
|
if ($orderStatus == self::ORDER_STATUS_SHIPPED) {
|
|
|
|
|
|
$buttons[] = ['name' => self::BUTTON_MAP[OrderService::BUTTON_CONFIRM], 'primary' => true, 'type' => OrderService::BUTTON_CONFIRM];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $buttons;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取订单统计
|
|
|
|
|
|
* @param $userId
|
|
|
|
|
|
* @return array[]
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function getOrderCount($userId): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$orderCountList = [
|
|
|
|
|
|
['orderNum' => 0, 'tabType' => OrderService::ORDER_STATUS_WAIT_PAY],
|
|
|
|
|
|
['orderNum' => 0, 'tabType' => OrderService::ORDER_STATUS_WAIT_SHIP],
|
|
|
|
|
|
['orderNum' => 0, 'tabType' => OrderService::ORDER_STATUS_SHIPPED],
|
|
|
|
|
|
['orderNum' => 0, 'tabType' => OrderService::ORDER_STATUS_FINISHED],
|
|
|
|
|
|
['orderNum' => 0, 'tabType' => OrderService::ORDER_STATUS_REFUNDED],
|
|
|
|
|
|
];
|
|
|
|
|
|
$countList = self::where(['user_id' => $userId, 'order_type' => self::ORDER_TYPE_SOFTWARE])->field('order_status, COUNT(*) as total')
|
|
|
|
|
|
->group('order_status')
|
|
|
|
|
|
->select();
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($orderCountList as &$value) {
|
|
|
|
|
|
if ($value['tabType'] == OrderService::ORDER_STATUS_FINISHED) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach ($countList as $item) {
|
|
|
|
|
|
if ($item->order_status == $value['tabType']) {
|
|
|
|
|
|
$value['orderNum'] = $item->total;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $orderCountList;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 订单发货
|
|
|
|
|
|
* @param $id
|
|
|
|
|
|
* @return void
|
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function orderShip($id)
|
|
|
|
|
|
{
|
|
|
|
|
|
Db::transaction(function () use ($id) {
|
|
|
|
|
|
$order = OrderService::getOne(['id' => $id], ['orderGoods']);
|
|
|
|
|
|
if ($order->order_status != OrderService::ORDER_STATUS_WAIT_SHIP) {
|
|
|
|
|
|
exception('订单状态异常,不能发货');
|
|
|
|
|
|
}
|
|
|
|
|
|
//查询激活码
|
|
|
|
|
|
$code = SoftCodeService::getAvailableCode();
|
|
|
|
|
|
//生成发货单
|
|
|
|
|
|
OrderShipService::createData($order->order_no, $order->orderGoods[0]->spu_id, $code);
|
|
|
|
|
|
$order->order_status = OrderService::ORDER_STATUS_SHIPPED;
|
|
|
|
|
|
//修改发货状态
|
|
|
|
|
|
$saveRes = $order->save();
|
|
|
|
|
|
if (!$saveRes) {
|
|
|
|
|
|
exception('更新发货状态失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
//发送订单发货通知
|
|
|
|
|
|
$msgData = [
|
|
|
|
|
|
'values' => [
|
|
|
|
|
|
$order->order_no,
|
|
|
|
|
|
mb_substr($order->orderGoods[0]->goods_name, 0, 20),
|
|
|
|
|
|
'已发货',
|
|
|
|
|
|
SubscribeMessageService::MSG_MAP[SubscribeMessageService::MSG_TEMP_ORDER_SHIPPED]['remark'],
|
|
|
|
|
|
]
|
|
|
|
|
|
];
|
|
|
|
|
|
(new EasyWeChatService())->sendMsg($order->user_id, SubscribeMessageService::MSG_TEMP_ORDER_SHIPPED, $msgData);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|