feat: 完善协议管理、师傅资质认证、分账配置并完成测试环境全量部署与同步
这个提交包含在:
@@ -2,7 +2,11 @@
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\library\CommissionService;
|
||||
use app\common\library\FinanceConfigService;
|
||||
use app\common\library\OrderStatus;
|
||||
use app\common\library\PaymentService;
|
||||
use app\common\library\TiandituService;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
@@ -21,7 +25,7 @@ class Order extends Base
|
||||
}
|
||||
|
||||
/**
|
||||
* 物流单号智能识别物流公司(支持线上快递100识别 + 本地单号特征规则库双保险)
|
||||
* 物流单号识别物流公司(依据 fa_express_company 精准前缀规则 + 快递100接口;不唯一识别时不瞎猜)
|
||||
*/
|
||||
public function detectExpress()
|
||||
{
|
||||
@@ -31,68 +35,32 @@ class Order extends Base
|
||||
$this->error('请输入正确的物流单号');
|
||||
}
|
||||
|
||||
$codeMap = [
|
||||
'shunfeng' => '顺丰速运',
|
||||
'debangwuliu' => '德邦快递',
|
||||
'debangkuaidi' => '德邦快递',
|
||||
'jd' => '京东快递',
|
||||
'kuayue' => '跨越速运',
|
||||
'shentong' => '申通快递',
|
||||
'yuantong' => '圆通速递',
|
||||
'zhongtong' => '中通快递',
|
||||
'yunda' => '韵达',
|
||||
'huitongkuaidi' => '百世快递',
|
||||
'baishiwuliu' => '百世快递',
|
||||
'tiantian' => '天天快递',
|
||||
'ems' => '邮政特快专递',
|
||||
'youzhengguonei' => '中国邮政',
|
||||
'youzhengbk' => '中国邮政',
|
||||
'zhaijisong' => '宅急送',
|
||||
'jtexpress' => '极兔速递',
|
||||
'annengwuliu' => '安能物流',
|
||||
];
|
||||
$expressRows = Db::name('express_company')
|
||||
->field('code,name,prefix_regex')
|
||||
->where('code', '<>', 'other')
|
||||
->order('weigh desc, id asc')
|
||||
->select();
|
||||
|
||||
$codeMap = [];
|
||||
$companyName = '';
|
||||
|
||||
// 1. 优先匹配高确定性的标准物流单号前缀规则
|
||||
if (strpos($no, 'SF') === 0) {
|
||||
$companyName = '顺丰速运';
|
||||
} elseif (strpos($no, 'DP') === 0 || strpos($no, 'DB') === 0) {
|
||||
$companyName = '德邦快递';
|
||||
} elseif (strpos($no, 'JD') === 0 || strpos($no, 'KK') === 0) {
|
||||
$companyName = '京东快递';
|
||||
} elseif (strpos($no, 'KY') === 0) {
|
||||
$companyName = '跨越速运';
|
||||
} elseif (strpos($no, 'YT') === 0) {
|
||||
$companyName = '圆通速递';
|
||||
} elseif (strpos($no, 'ST') === 0 || strpos($no, '77') === 0) {
|
||||
$companyName = '申通快递';
|
||||
} elseif (strpos($no, 'ZT') === 0 || preg_match('/^(73|75|76|78)\d{10,12}$/', $no)) {
|
||||
$companyName = '中通快递';
|
||||
} elseif (strpos($no, 'YD') === 0 || preg_match('/^(31|43|46)\d{11,13}$/', $no)) {
|
||||
$companyName = '韵达';
|
||||
} elseif (strpos($no, 'BS') === 0 || strpos($no, 'HT') === 0 || preg_match('/^55\d{11,13}$/', $no)) {
|
||||
$companyName = '百世快递';
|
||||
} elseif (strpos($no, 'TT') === 0 || preg_match('/^66\d{10,12}$/', $no)) {
|
||||
$companyName = '天天快递';
|
||||
} elseif (preg_match('/^(EMS|EA|EB|EC|ED|EE|EF|10|11)\d{7,11}/', $no)) {
|
||||
$companyName = '邮政特快专递';
|
||||
} elseif (preg_match('/^(98|99|YZ)\d{9,13}$/', $no)) {
|
||||
$companyName = '中国邮政';
|
||||
} elseif (strpos($no, 'ZJS') === 0) {
|
||||
$companyName = '宅急送';
|
||||
} elseif (strpos($no, 'JT') === 0) {
|
||||
$companyName = '极兔速递';
|
||||
} elseif (strpos($no, 'ANE') === 0) {
|
||||
$companyName = '安能物流';
|
||||
foreach ($expressRows as $er) {
|
||||
$eCode = strtolower(trim((string)($er['code'] ?? '')));
|
||||
$eName = trim((string)($er['name'] ?? ''));
|
||||
$regex = trim((string)($er['prefix_regex'] ?? ''));
|
||||
if ($eCode !== '' && $eName !== '') {
|
||||
$codeMap[$eCode] = $eName;
|
||||
}
|
||||
if ($companyName === '' && $regex !== '' && @preg_match($regex, $no) === 1) {
|
||||
$companyName = $eName;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 若前缀未命中,尝试快递100智能单号识别接口(1.5秒超时保护)
|
||||
// 若本地高确定性特征规则未唯一命中,尝试快递100智能单号识别接口(开启严格 TLS 校验与超时保护)
|
||||
if ($companyName === '') {
|
||||
try {
|
||||
$ctx = stream_context_create([
|
||||
'http' => ['timeout' => 1.5, 'ignore_errors' => true],
|
||||
'ssl' => ['verify_peer' => false, 'verify_peer_name' => false],
|
||||
'http' => ['timeout' => 2.0, 'ignore_errors' => true],
|
||||
'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
|
||||
]);
|
||||
$resp = @file_get_contents('https://www.kuaidi100.com/autonumber/autoComNum?text=' . urlencode($no), false, $ctx);
|
||||
if ($resp) {
|
||||
@@ -107,24 +75,18 @@ class Order extends Base
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
\think\Log::warning('[Order::detectExpress] ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 常见纯数字位数兜底识别
|
||||
if ($companyName === '') {
|
||||
if (preg_match('/^\d{15}$/', $no)) {
|
||||
$companyName = '德邦快递';
|
||||
} elseif (preg_match('/^\d{12}$/', $no)) {
|
||||
$companyName = '顺丰速运';
|
||||
} elseif (preg_match('/^\d{13}$/', $no)) {
|
||||
$companyName = '圆通速递';
|
||||
} elseif (preg_match('/^\d{14}$/', $no)) {
|
||||
$companyName = '中通快递';
|
||||
} else {
|
||||
$companyName = '德邦快递';
|
||||
}
|
||||
$this->success('未能唯一识别物流公司,请手动选择', [
|
||||
'express_no' => $no,
|
||||
'company_name' => '',
|
||||
'matched' => false,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->success('已识别物流公司', [
|
||||
@@ -157,10 +119,11 @@ class Order extends Base
|
||||
$reqServiceType = trim((string)$this->request->post('service_type', ''));
|
||||
$isCustom = (int)$this->request->post('is_custom', 0);
|
||||
$remark = trim((string)$this->request->post('remark'));
|
||||
$goodsImages = $this->request->post('goods_images', '');
|
||||
$rawGoodsImagesIn = $this->postMixed('goods_images', '');
|
||||
$goodsImages = is_array($rawGoodsImagesIn) ? json_encode($rawGoodsImagesIn, JSON_UNESCAPED_UNICODE) : (string)$rawGoodsImagesIn;
|
||||
|
||||
// 支持多商品组合 (goods_list / items)
|
||||
$goodsListRaw = $this->request->post('goods_list', $this->request->post('items', '', null), null);
|
||||
$goodsListRaw = $this->postMixed('goods_list', $this->postMixed('items', ''));
|
||||
$goodsList = [];
|
||||
if (!empty($goodsListRaw)) {
|
||||
if (is_string($goodsListRaw)) {
|
||||
@@ -225,14 +188,29 @@ class Order extends Base
|
||||
if (!$address) {
|
||||
$contact = trim((string)$this->request->post('contact'));
|
||||
$mobile = trim((string)$this->request->post('mobile'));
|
||||
$province = trim((string)$this->request->post('province', '贵州省'));
|
||||
$city = trim((string)$this->request->post('city', '贵阳市'));
|
||||
$province = trim((string)$this->request->post('province', ''));
|
||||
$city = trim((string)$this->request->post('city', ''));
|
||||
$district = trim((string)$this->request->post('district', ''));
|
||||
$detailAddr = trim((string)$this->request->post('address'));
|
||||
$floor = max(1, (int)$this->request->post('floor', 1));
|
||||
$hasElevator = (int)$this->request->post('has_elevator', 1);
|
||||
$lng = trim((string)$this->request->post('lng', ''));
|
||||
$lat = trim((string)$this->request->post('lat', ''));
|
||||
|
||||
if ($contact && $mobile && $detailAddr) {
|
||||
if ($province === '' || $city === '') {
|
||||
$this->error('请选择完整的服务地址省市区');
|
||||
}
|
||||
if ($lng === '' || $lat === '' || abs((float)$lng) < 0.0001 || abs((float)$lat) < 0.0001) {
|
||||
$geo = TiandituService::geocoder(trim($province . $city . $district . $detailAddr));
|
||||
if (!$geo && $city !== '') {
|
||||
$geo = TiandituService::geocoder(trim($city . $district . $detailAddr));
|
||||
}
|
||||
if ($geo) {
|
||||
$lng = (string)$geo['lng'];
|
||||
$lat = (string)$geo['lat'];
|
||||
}
|
||||
}
|
||||
$addressId = Db::name('address')->insertGetId([
|
||||
'user_id' => $this->uid(),
|
||||
'contact' => $contact,
|
||||
@@ -241,6 +219,8 @@ class Order extends Base
|
||||
'city' => $city,
|
||||
'district' => $district,
|
||||
'address' => $detailAddr,
|
||||
'lng' => $lng,
|
||||
'lat' => $lat,
|
||||
'floor' => $floor,
|
||||
'has_elevator' => $hasElevator,
|
||||
'createtime' => time(),
|
||||
@@ -253,13 +233,28 @@ class Order extends Base
|
||||
if (!$address) {
|
||||
$this->error('请选择或填写完整服务地址');
|
||||
}
|
||||
// 所选地址缺少经纬度时在事务开启前自动通过天地图补全并回填
|
||||
if (empty($address['lng']) || empty($address['lat']) || abs((float)$address['lng']) < 0.0001 || abs((float)$address['lat']) < 0.0001) {
|
||||
$geo = TiandituService::geocoder(trim(($address['province'] ?? '') . ($address['city'] ?? '') . ($address['district'] ?? '') . ($address['address'] ?? '')));
|
||||
if ($geo) {
|
||||
$address['lng'] = (string)$geo['lng'];
|
||||
$address['lat'] = (string)$geo['lat'];
|
||||
if (!empty($address['id'])) {
|
||||
Db::name('address')->where('id', (int)$address['id'])->update([
|
||||
'lng' => $address['lng'],
|
||||
'lat' => $address['lat'],
|
||||
'updatetime' => time(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($appointTime > 0 && $appointTime < time()) {
|
||||
$this->error('预约时间不能早于当前时间');
|
||||
}
|
||||
|
||||
// 多商品组合名称、总件数与图片汇总
|
||||
$serviceName = $service ? $service['name'] : '同城综合上门服务';
|
||||
$catId = $service ? (int)$service['cat_id'] : 1;
|
||||
$serviceName = $service ? $service['name'] : '自定义上门服务';
|
||||
$catId = $service ? (int)$service['cat_id'] : 0;
|
||||
$serviceImage = $service ? $service['image'] : '';
|
||||
if (!empty($goodsList) && is_array($goodsList)) {
|
||||
$firstG = reset($goodsList);
|
||||
@@ -373,6 +368,11 @@ class Order extends Base
|
||||
'range' => '按面积计价',
|
||||
];
|
||||
|
||||
// 事务外预加载当前生效的按订单维度抽佣规则与默认兜底配置
|
||||
$customerType = $this->currentMerchantTeam() ? 'merchant' : 'personal';
|
||||
$activeCommRules = CommissionService::getActiveRules();
|
||||
$commDefaults = FinanceConfigService::getCommissionDefaults();
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$lockedCouponId = 0;
|
||||
@@ -413,6 +413,14 @@ class Order extends Base
|
||||
$lockedCouponId = (int)$uc['id'];
|
||||
}
|
||||
|
||||
$commMatch = CommissionService::matchOrderSnapshot([
|
||||
'price_type' => $priceType,
|
||||
'customer_type' => $customerType,
|
||||
'cat_id' => (int)$catId,
|
||||
'urgent' => $urgent ? 1 : 0,
|
||||
'order_amount' => (float)$amount['pay_amount'] > 0 ? (float)$amount['pay_amount'] : (float)$amount['total_amount'],
|
||||
], $activeCommRules, $commDefaults);
|
||||
|
||||
$id = (int)Db::name('order')->insertGetId([
|
||||
'order_no' => $orderNo,
|
||||
'user_id' => $uid,
|
||||
@@ -452,9 +460,11 @@ class Order extends Base
|
||||
'floor_amount' => $amount['floor_amount'],
|
||||
'urgent_amount' => $amount['urgent_amount'],
|
||||
'discount_amount' => $amount['discount_amount'],
|
||||
'total_amount' => $amount['total_amount'],
|
||||
'pay_amount' => $amount['pay_amount'],
|
||||
'status' => $initStatus,
|
||||
'total_amount' => $amount['total_amount'],
|
||||
'pay_amount' => $amount['pay_amount'],
|
||||
'commission_rate' => $commMatch['commission_rate'],
|
||||
'commission_snapshot' => $commMatch['commission_snapshot'],
|
||||
'status' => $initStatus,
|
||||
'pay_status' => 0,
|
||||
'coupon_id' => $lockedCouponId,
|
||||
'verify_code' => $verifyCode,
|
||||
@@ -581,6 +591,13 @@ class Order extends Base
|
||||
}
|
||||
}
|
||||
|
||||
// 事务外静默沉淀本次订单中用户真实上传的商品图片到「历史商品上传图片库」(自动跳过系统默认图)
|
||||
try {
|
||||
$this->archiveOrderUploadedGoodsImages($uid, (int)$catId, (string)$serviceName, $goodsList, $goodsImages);
|
||||
} catch (\Exception $e) {
|
||||
\think\Log::error('[Order::create archiveGoodsGallery] ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->success('下单成功', [
|
||||
'order_id' => $id,
|
||||
'order_no' => $orderNo,
|
||||
@@ -589,175 +606,96 @@ class Order extends Base
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付(联调模拟支付,pay_type=mock;上线换微信支付回调)
|
||||
* 发起订单微信 JSAPI 支付(返回小程序 wx.requestPayment 参数包;0元抵扣单直接核销)
|
||||
* @ApiMethod (POST)
|
||||
*/
|
||||
public function pay()
|
||||
{
|
||||
$orderId = (int)$this->request->post('order_id');
|
||||
$payType = $this->request->post('pay_type', 'mock');
|
||||
$order = $this->ownOrder($orderId, 'user');
|
||||
$wxCode = trim((string)$this->request->post('wx_code', ''));
|
||||
$order = $this->ownOrder($orderId, 'user');
|
||||
|
||||
if ((int)$order['pay_status'] === 1) {
|
||||
$this->error('订单已支付,请勿重复支付');
|
||||
}
|
||||
if ((int)$order['status'] !== OrderStatus::PENDING_PAY) {
|
||||
$this->error('订单状态不允许支付');
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$nextStatus = OrderStatus::PENDING_ACCEPT;
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$lockedOrder = Db::name('order')->where('id', $orderId)->lock(true)->find();
|
||||
if (!$lockedOrder) {
|
||||
throw new \InvalidArgumentException('订单不存在');
|
||||
}
|
||||
if ((int)$lockedOrder['pay_status'] === 1) {
|
||||
throw new \InvalidArgumentException('订单已支付,请勿重复支付');
|
||||
}
|
||||
if ((int)$lockedOrder['status'] !== OrderStatus::PENDING_PAY) {
|
||||
throw new \InvalidArgumentException('订单状态不允许支付');
|
||||
}
|
||||
|
||||
// 兼容创单阶段已加锁核销的优惠券,防止重复报错或漏锁
|
||||
if ((int)$lockedOrder['coupon_id'] > 0) {
|
||||
$uc = Db::name('user_coupon')
|
||||
->where('id', (int)$lockedOrder['coupon_id'])
|
||||
->where('user_id', (int)$lockedOrder['user_id'])
|
||||
->lock(true)
|
||||
->find();
|
||||
if (!$uc) {
|
||||
throw new \InvalidArgumentException('订单关联的优惠券不存在');
|
||||
}
|
||||
if ($uc['status'] === 'used' && (int)$uc['order_id'] === $orderId) {
|
||||
// 创单已锁定本单卡券
|
||||
} elseif ($uc['status'] === 'unused') {
|
||||
$affectedUc = Db::name('user_coupon')
|
||||
->where('id', (int)$uc['id'])
|
||||
->where('user_id', (int)$lockedOrder['user_id'])
|
||||
->where('status', 'unused')
|
||||
->update([
|
||||
'status' => 'used',
|
||||
'order_id' => $orderId,
|
||||
'use_time' => $now,
|
||||
]);
|
||||
if (!$affectedUc) {
|
||||
throw new \InvalidArgumentException('优惠券状态异常,请重新下单');
|
||||
}
|
||||
} else {
|
||||
throw new \InvalidArgumentException('该优惠券已被其他订单使用或已过期');
|
||||
}
|
||||
}
|
||||
|
||||
Db::name('order')->where('id', $orderId)->update([
|
||||
'pay_status' => 1,
|
||||
'pay_type' => $payType,
|
||||
'pay_time' => $now,
|
||||
'transaction_id' => 'MOCK' . date('YmdHis', $now) . mt_rand(1000, 9999),
|
||||
'updatetime' => $now,
|
||||
]);
|
||||
$nextStatus = $lockedOrder['price_type'] === 'quote' ? OrderStatus::QUOTING : OrderStatus::PENDING_ACCEPT;
|
||||
$nextRemark = $nextStatus === OrderStatus::QUOTING
|
||||
? '支付成功,订单已进入报价大厅,等待师傅报价'
|
||||
: '支付成功,订单已进入接单大厅';
|
||||
$this->transit($lockedOrder, $nextStatus, $nextRemark, [], 'user', $this->uid());
|
||||
$this->addLog($orderId, $nextStatus, $nextStatus === OrderStatus::QUOTING ? '订单进入师傅报价大厅' : '订单进入师傅接单大厅', 'system', 0);
|
||||
|
||||
Db::commit();
|
||||
$res = PaymentService::createOrderJsapiPay($order, $this->uid(), 'order', 0, $wxCode);
|
||||
} catch (\think\exception\HttpResponseException $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
return;
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
} catch (\Throwable $e) {
|
||||
\think\Log::error('[Order::pay] ' . $e->getMessage());
|
||||
$this->error('支付失败:' . $e->getMessage());
|
||||
$this->error('发起微信支付失败:' . $e->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
// 订单广播通知:进入接单/报价大厅后,通知周边在线认证师傅(排除下单人本人)
|
||||
if (in_array($nextStatus, [OrderStatus::PENDING_ACCEPT, OrderStatus::QUOTING], true)) {
|
||||
try {
|
||||
$masters = Db::name('master')
|
||||
->where('verify_status', 'pass')
|
||||
->where('work_status', 'on')
|
||||
->where('user_id', '<>', (int)$order['user_id'])
|
||||
->limit(20)
|
||||
->select();
|
||||
foreach ($masters as $m) {
|
||||
$this->notify('master', (int)$m['id'], '新工单就绪通知', '同城有新工单【' . $order['order_no'] . '】进入接单大厅,请尽快前往抢单!', 'order', $orderId);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\think\Log::error('[Order::pay broadcast] ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->success('支付成功');
|
||||
$msg = !empty($res['paid']) ? '支付成功' : '请完成微信支付';
|
||||
$this->success($msg, $res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的订单列表
|
||||
* 小程序端完成 wx.requestPayment 后主动查单幂等核销(与异步回调双保险闭环)
|
||||
* @ApiMethod (POST)
|
||||
*/
|
||||
public function confirmPay()
|
||||
{
|
||||
$outTradeNo = trim((string)$this->request->post('out_trade_no', ''));
|
||||
$orderId = (int)$this->request->post('order_id', 0);
|
||||
|
||||
$payOrder = null;
|
||||
if ($outTradeNo !== '') {
|
||||
$payOrder = Db::name('pay_order')->where('out_trade_no', $outTradeNo)->find();
|
||||
} elseif ($orderId > 0) {
|
||||
$payOrder = Db::name('pay_order')->where('order_id', $orderId)->order('id desc')->find();
|
||||
}
|
||||
if (!$payOrder) {
|
||||
$this->error('未找到对应的支付交易单');
|
||||
}
|
||||
|
||||
// 强制校验订单归属权限,防止水平越权 IDOR
|
||||
$this->ownOrder((int)$payOrder['order_id'], 'user');
|
||||
|
||||
try {
|
||||
$res = PaymentService::confirmOrderPay((string)$payOrder['out_trade_no']);
|
||||
} catch (\think\exception\HttpResponseException $e) {
|
||||
throw $e;
|
||||
} catch (\Throwable $e) {
|
||||
\think\Log::error('[Order::confirmPay] ' . $e->getMessage());
|
||||
$this->error('查询支付结果失败:' . $e->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($res['paid'])) {
|
||||
$stateText = !empty($res['trade_state_text']) ? (string)$res['trade_state_text'] : '暂未确认到支付成功结果';
|
||||
$this->error('支付未完成:' . $stateText, $res);
|
||||
}
|
||||
|
||||
$this->success('支付成功', $res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户在线支付施工增项补差价
|
||||
* 用户在线微信支付施工增项补差价
|
||||
* @ApiMethod (POST)
|
||||
*/
|
||||
public function payDiff()
|
||||
{
|
||||
$orderId = (int)$this->request->post('order_id');
|
||||
$order = $this->ownOrder($orderId, 'user');
|
||||
$wxCode = trim((string)$this->request->post('wx_code', ''));
|
||||
$order = $this->ownOrder($orderId, 'user');
|
||||
|
||||
if ((int)$order['diff_status'] !== 1 || (float)$order['diff_amount'] <= 0) {
|
||||
$this->error('当前订单无待支付的增项补差价');
|
||||
}
|
||||
|
||||
$diffAmount = (float)$order['diff_amount'];
|
||||
$newPayAmount = round((float)$order['pay_amount'] + $diffAmount, 2);
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$lockedOrder = Db::name('order')->where('id', $orderId)->lock(true)->find();
|
||||
if (!$lockedOrder || (int)$lockedOrder['diff_status'] !== 1 || (float)$lockedOrder['diff_amount'] <= 0) {
|
||||
throw new \InvalidArgumentException('当前订单无待支付的增项补差价');
|
||||
}
|
||||
$diffAmount = (float)$lockedOrder['diff_amount'];
|
||||
$newPayAmount = round((float)$lockedOrder['pay_amount'] + $diffAmount, 2);
|
||||
|
||||
Db::name('order')->where('id', $orderId)->update([
|
||||
'diff_status' => 2,
|
||||
'pay_amount' => $newPayAmount,
|
||||
'updatetime' => time(),
|
||||
]);
|
||||
|
||||
$this->addLog($orderId, (int)$lockedOrder['status'], '用户在线支付施工增项补差价 ¥' . $diffAmount, 'user', $this->uid());
|
||||
Db::commit();
|
||||
$res = PaymentService::createOrderJsapiPay($order, $this->uid(), 'diff', 0, $wxCode);
|
||||
} catch (\think\exception\HttpResponseException $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
return;
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
} catch (\Throwable $e) {
|
||||
\think\Log::error('[Order::payDiff] ' . $e->getMessage());
|
||||
$this->error('支付失败:' . $e->getMessage());
|
||||
$this->error('发起补差价支付失败:' . $e->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if ((int)$order['master_id'] > 0) {
|
||||
$m = Db::name('master')->where('id', $order['master_id'])->find();
|
||||
if ($m) {
|
||||
$this->notify('master', (int)$m['id'], '客户已支付增项补差价', '工单 ' . $order['order_no'] . ' 客户已支付增项补差价 ¥' . $diffAmount, 'order', $orderId);
|
||||
}
|
||||
}
|
||||
|
||||
$this->success('增项补差价支付成功!', ['pay_amount' => $newPayAmount]);
|
||||
$this->success('请完成增项补差价微信支付', $res);
|
||||
}
|
||||
|
||||
public function index()
|
||||
@@ -1230,108 +1168,33 @@ class Order extends Base
|
||||
}
|
||||
|
||||
/**
|
||||
* 报价订单:用户选择师傅报价(选定后订单进入已接单)
|
||||
* 报价订单:用户选择师傅报价并发起微信支付托管(付款确认后订单自动锁定师傅并进入已接单)
|
||||
* @ApiMethod (POST)
|
||||
*/
|
||||
public function chooseQuote()
|
||||
{
|
||||
$orderId = (int)$this->request->post('order_id');
|
||||
$quoteId = (int)$this->request->post('quote_id');
|
||||
$order = $this->ownOrder($orderId, 'user');
|
||||
$wxCode = trim((string)$this->request->post('wx_code', ''));
|
||||
$order = $this->ownOrder($orderId, 'user');
|
||||
|
||||
if ($order['price_type'] !== 'quote') {
|
||||
$this->error('该订单不是师傅报价订单');
|
||||
}
|
||||
if ((int)$order['status'] !== OrderStatus::QUOTING) {
|
||||
$this->error('当前状态(' . OrderStatus::label((int)$order['status']) . ')不可选择报价');
|
||||
}
|
||||
if ((int)$order['master_id'] > 0) {
|
||||
$this->error('已选定师傅,请勿重复选择');
|
||||
}
|
||||
|
||||
$quote = Db::name('order_quote')->where('id', $quoteId)->where('order_id', $orderId)->find();
|
||||
if (!$quote) {
|
||||
$this->error('报价不存在');
|
||||
}
|
||||
if ($quote['status'] !== 'wait') {
|
||||
$this->error('该报价已失效,请选择其他师傅');
|
||||
}
|
||||
if ((int)$quote['expire_time'] > 0 && (int)$quote['expire_time'] < time()) {
|
||||
Db::name('order_quote')->where('id', $quoteId)->update(['status' => 'expired']);
|
||||
$this->error('该报价已过期,请选择其他师傅');
|
||||
}
|
||||
|
||||
$master = Db::name('master')->where('id', (int)$quote['master_id'])->find();
|
||||
if (!$master) {
|
||||
$this->error('师傅信息不存在');
|
||||
}
|
||||
|
||||
$quoteAmount = round((float)$quote['amount'], 2);
|
||||
$payAmount = max(0, round($quoteAmount - (float)$order['discount_amount'], 2));
|
||||
$alreadyPaid = (int)$order['pay_status'] === 1 ? (float)$order['pay_amount'] : 0.0;
|
||||
$needDiff = max(0, round($payAmount - $alreadyPaid, 2));
|
||||
|
||||
$payType = $this->request->post('pay_type', 'wechat');
|
||||
$remark = '客户已选择 ' . $master['realname'] . ' 的报价 ¥' . $quoteAmount;
|
||||
if ($needDiff > 0) {
|
||||
$remark .= '(补付差额 ¥' . $needDiff . ')';
|
||||
}
|
||||
$remark .= ',等待师傅联系';
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$lockedOrder = Db::name('order')->where('id', $orderId)->lock(true)->find();
|
||||
if (!$lockedOrder || (int)$lockedOrder['status'] !== OrderStatus::QUOTING || (int)$lockedOrder['master_id'] !== 0) {
|
||||
throw new \InvalidArgumentException('该工单已选定师傅或状态已变更,请刷新重试');
|
||||
}
|
||||
$quoteAmount = round((float)$quote['amount'], 2);
|
||||
$payAmount = max(0, round($quoteAmount - (float)$lockedOrder['discount_amount'], 2));
|
||||
$alreadyPaid = (int)$lockedOrder['pay_status'] === 1 ? (float)$lockedOrder['pay_amount'] : 0.0;
|
||||
$needDiff = max(0, round($payAmount - $alreadyPaid, 2));
|
||||
|
||||
$updateData = [
|
||||
'master_id' => (int)$quote['master_id'],
|
||||
'accept_time' => time(),
|
||||
'base_amount' => $quoteAmount,
|
||||
'floor_amount' => 0,
|
||||
'urgent_amount' => 0,
|
||||
'total_amount' => $quoteAmount,
|
||||
'pay_amount' => $payAmount,
|
||||
'pay_status' => 1,
|
||||
];
|
||||
if ($needDiff > 0) {
|
||||
$updateData['pay_type'] = $payType;
|
||||
$updateData['pay_time'] = time();
|
||||
}
|
||||
$this->transit($lockedOrder, OrderStatus::ACCEPTED, $remark, $updateData, 'user', $this->uid());
|
||||
|
||||
Db::name('order_quote')->where('id', $quoteId)->update(['status' => 'accept']);
|
||||
Db::name('order_quote')->where('order_id', $orderId)->where('id', '<>', $quoteId)
|
||||
->where('status', 'wait')->update(['status' => 'reject']);
|
||||
|
||||
$this->notify('user', (int)$lockedOrder['user_id'], '已选定服务师傅', $remark, 'order', $orderId);
|
||||
$this->notify('master', (int)$master['id'], '您的报价已被选中', '订单 ' . $lockedOrder['order_no'] . ' 客户已选择您的报价 ¥' . $quoteAmount . ',请尽快联系客户', 'order', $orderId);
|
||||
|
||||
Db::commit();
|
||||
$res = PaymentService::createOrderJsapiPay($order, $this->uid(), 'quote', $quoteId, $wxCode);
|
||||
} catch (\think\exception\HttpResponseException $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
return;
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
} catch (\Throwable $e) {
|
||||
\think\Log::error('[Order::chooseQuote] ' . $e->getMessage());
|
||||
$this->error('选择报价失败:' . $e->getMessage());
|
||||
$this->error('选择报价并发起支付失败:' . $e->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
$this->success('已选定师傅并完成支付托管,请等待师傅联系', [
|
||||
'pay_amount' => $payAmount,
|
||||
'need_diff' => $needDiff,
|
||||
'already_pay' => $alreadyPaid,
|
||||
]);
|
||||
$msg = !empty($res['paid'])
|
||||
? '已选定师傅并完成支付托管,请等待师傅联系'
|
||||
: '请完成报价托管微信支付';
|
||||
$this->success($msg, $res);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1377,7 +1240,7 @@ class Order extends Base
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订单(已支付则模拟退款)
|
||||
* 取消订单(已支付则发起微信原路退款)
|
||||
* @ApiMethod (POST)
|
||||
*/
|
||||
public function cancel()
|
||||
@@ -1396,6 +1259,18 @@ class Order extends Base
|
||||
$this->error('该订单已开具发票,如需取消退款请联系客服办理发票冲红');
|
||||
}
|
||||
|
||||
// 若订单已支付,先在数据库事务外部调用微信支付 APIv3 逐笔发起原路退款(无有效流水严格拦截,严禁静默跳过)
|
||||
$refundedAmount = 0.00;
|
||||
if ((int)$order['pay_status'] === 1) {
|
||||
try {
|
||||
$refundedAmount = PaymentService::refundOrder($orderId, '用户取消:' . $reason);
|
||||
} catch (\Throwable $re) {
|
||||
\think\Log::error('[Order::cancel refund] order_id=' . $orderId . ' reason=' . $re->getMessage());
|
||||
$this->error('申请微信原路退款失败:' . $re->getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$prevMasterId = 0;
|
||||
Db::startTrans();
|
||||
try {
|
||||
@@ -1407,7 +1282,7 @@ class Order extends Base
|
||||
$extra = ['cancel_reason' => mb_substr($reason, 0, 255)];
|
||||
if ((int)$lockedOrder['pay_status'] === 1) {
|
||||
$extra['pay_status'] = 2;
|
||||
$extra['refund_amount'] = $lockedOrder['pay_amount'];
|
||||
$extra['refund_amount'] = $refundedAmount > 0 ? $refundedAmount : (float)$lockedOrder['pay_amount'];
|
||||
}
|
||||
$this->transit($lockedOrder, OrderStatus::CANCELLED, '用户取消:' . $reason, $extra, 'user', $this->uid());
|
||||
|
||||
@@ -1467,9 +1342,10 @@ class Order extends Base
|
||||
|
||||
$masterId = (int)$order['master_id'];
|
||||
$payAmount = (float)$order['pay_amount'];
|
||||
$rate = 10.00;
|
||||
$commission = round($payAmount * $rate / 100, 2);
|
||||
$settleAmount = round($payAmount - $commission, 2);
|
||||
$settleCalc = CommissionService::calcByOrder($order);
|
||||
$rate = $settleCalc['commission_rate'];
|
||||
$commission = $settleCalc['commission'];
|
||||
$settleAmount = $settleCalc['settle_amount'];
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
@@ -1479,8 +1355,10 @@ class Order extends Base
|
||||
}
|
||||
$masterId = (int)$lockedOrder['master_id'];
|
||||
$payAmount = (float)$lockedOrder['pay_amount'];
|
||||
$commission = round($payAmount * $rate / 100, 2);
|
||||
$settleAmount = round($payAmount - $commission, 2);
|
||||
$settleCalc = CommissionService::calcByOrder($lockedOrder);
|
||||
$rate = $settleCalc['commission_rate'];
|
||||
$commission = $settleCalc['commission'];
|
||||
$settleAmount = $settleCalc['settle_amount'];
|
||||
|
||||
$this->transit($lockedOrder, OrderStatus::COMPLETED, '客户验收通过,订单完成', [
|
||||
'verify_time' => time(),
|
||||
@@ -1694,14 +1572,22 @@ class Order extends Base
|
||||
$count = 0;
|
||||
foreach ($rows as $order) {
|
||||
$orderId = (int)$order['id'];
|
||||
$masterId = (int)$order['master_id'];
|
||||
$payAmount = (float)$order['pay_amount'];
|
||||
$rate = 10.00;
|
||||
$commission = round($payAmount * $rate / 100, 2);
|
||||
$settleAmount = round($payAmount - $commission, 2);
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$lockedOrder = Db::name('order')->where('id', $orderId)->lock(true)->find();
|
||||
if (!$lockedOrder || (int)$lockedOrder['status'] !== OrderStatus::PENDING_VERIFY || (int)$lockedOrder['settle_status'] === 1) {
|
||||
Db::rollback();
|
||||
continue;
|
||||
}
|
||||
|
||||
$masterId = (int)$lockedOrder['master_id'];
|
||||
$payAmount = (float)$lockedOrder['pay_amount'];
|
||||
$settleCalc = CommissionService::calcByOrder($lockedOrder);
|
||||
$rate = $settleCalc['commission_rate'];
|
||||
$commission = $settleCalc['commission'];
|
||||
$settleAmount = $settleCalc['settle_amount'];
|
||||
|
||||
Db::name('order')->where('id', $orderId)->update([
|
||||
'status' => OrderStatus::COMPLETED,
|
||||
'verify_time' => time(),
|
||||
@@ -1798,6 +1684,7 @@ class Order extends Base
|
||||
{
|
||||
$uid = $this->uid();
|
||||
$catId = (int)$this->request->param('cat_id', 0);
|
||||
$preferCatId = (int)$this->request->param('prefer_cat_id', 0);
|
||||
$keyword = trim((string)$this->request->param('keyword', ''));
|
||||
|
||||
$cats = Db::name('service_cat')
|
||||
@@ -1824,7 +1711,7 @@ class Order extends Base
|
||||
$seenUrls = [];
|
||||
foreach ($libRows as $r) {
|
||||
$rawImg = trim((string)$r['image']);
|
||||
if ($rawImg === '') {
|
||||
if ($rawImg === '' || $this->isSystemDefaultImageUrl($rawImg)) {
|
||||
continue;
|
||||
}
|
||||
$fullUrl = strpos($rawImg, 'http') === 0 ? $rawImg : cdnurl($rawImg, true);
|
||||
@@ -1834,7 +1721,7 @@ class Order extends Base
|
||||
'id' => (int)$r['id'],
|
||||
'cat_id' => $cid,
|
||||
'cat_name' => isset($catNameMap[$cid]) ? $catNameMap[$cid] : '通用商品',
|
||||
'title' => (string)($r['title'] ?: '我的商品图'),
|
||||
'title' => (string)($r['title'] ?: '历史上传商品'),
|
||||
'image_url' => $fullUrl,
|
||||
'source' => 'library',
|
||||
'can_delete' => 1,
|
||||
@@ -1883,8 +1770,8 @@ class Order extends Base
|
||||
}
|
||||
foreach ($candidates as $cand) {
|
||||
$rawIm = $cand['img'];
|
||||
// 排除 SVG 图标与系统图库静态参考图,仅保留用户真实上传图
|
||||
if (substr(strtolower($rawIm), -4) === '.svg' || strpos($rawIm, '/gallery/') !== false || strpos($rawIm, '/goods/') !== false) {
|
||||
// 排除 SVG 图标与系统图库静态参考图,仅保留用户真实上传过的历史商品图
|
||||
if ($this->isSystemDefaultImageUrl($rawIm)) {
|
||||
continue;
|
||||
}
|
||||
if ($keyword !== '' && mb_strpos($cand['title'], $keyword) === false) {
|
||||
@@ -1902,19 +1789,120 @@ class Order extends Base
|
||||
'cat_name' => isset($catNameMap[$cid]) ? $catNameMap[$cid] : '历史订单',
|
||||
'title' => $cand['title'],
|
||||
'image_url' => $fullUrl,
|
||||
'source' => 'history',
|
||||
'source' => 'order',
|
||||
'can_delete' => 0,
|
||||
'time_text' => $ho['createtime'] ? date('Y-m-d', (int)$ho['createtime']) : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($catId === 0 && $preferCatId > 0 && count($list) > 1) {
|
||||
usort($list, function ($a, $b) use ($preferCatId) {
|
||||
$aMatch = ((int)$a['cat_id'] === $preferCatId) ? 1 : 0;
|
||||
$bMatch = ((int)$b['cat_id'] === $preferCatId) ? 1 : 0;
|
||||
if ($aMatch !== $bMatch) {
|
||||
return $bMatch <=> $aMatch;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
$this->success('', [
|
||||
'cats' => $cats,
|
||||
'list' => $list,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断图片是否为平台系统默认参考图(避免将系统默认图混入用户个人的历史商品上传图库)
|
||||
*/
|
||||
protected function isSystemDefaultImageUrl($url)
|
||||
{
|
||||
$u = strtolower(trim((string)$url));
|
||||
if ($u === '' || substr($u, -4) === '.svg') {
|
||||
return true;
|
||||
}
|
||||
if (strpos($u, '/gallery/') !== false || strpos($u, '/goods/') !== false || strpos($u, '/uploads/service/') !== false || strpos($u, '/uploads/services/') !== false) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单创建成功后将用户真实上传的商品图片自动沉淀到「历史商品上传图片库」
|
||||
*/
|
||||
protected function archiveOrderUploadedGoodsImages($uid, $defaultCatId, $defaultTitle, $goodsList, $rawGoodsImages)
|
||||
{
|
||||
$uid = (int)$uid;
|
||||
if ($uid <= 0) {
|
||||
return;
|
||||
}
|
||||
$entries = [];
|
||||
if (!empty($goodsList) && is_array($goodsList)) {
|
||||
foreach ($goodsList as $g) {
|
||||
if (!is_array($g)) {
|
||||
continue;
|
||||
}
|
||||
$gCatId = !empty($g['cat_id']) ? (int)$g['cat_id'] : (int)$defaultCatId;
|
||||
$gTitle = !empty($g['custom_name']) ? trim((string)$g['custom_name']) : (!empty($g['item_name']) ? trim((string)$g['item_name']) : trim((string)$defaultTitle));
|
||||
if (!empty($g['spec_name']) && $g['spec_name'] !== $gTitle) {
|
||||
$gTitle .= '(' . trim((string)$g['spec_name']) . ')';
|
||||
}
|
||||
$imgs = isset($g['images']) && is_array($g['images']) ? $g['images'] : [];
|
||||
foreach ($imgs as $im) {
|
||||
if (is_string($im) && !$this->isSystemDefaultImageUrl($im)) {
|
||||
$entries[] = ['cat_id' => $gCatId, 'title' => mb_substr($gTitle ?: '历史上传商品', 0, 80), 'url' => trim($im)];
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (!empty($rawGoodsImages)) {
|
||||
$arr = is_array($rawGoodsImages) ? $rawGoodsImages : (json_decode((string)$rawGoodsImages, true) ?: explode(',', (string)$rawGoodsImages));
|
||||
foreach ($arr as $im) {
|
||||
if (is_string($im) && !$this->isSystemDefaultImageUrl($im)) {
|
||||
$entries[] = ['cat_id' => (int)$defaultCatId, 'title' => mb_substr(trim((string)$defaultTitle) ?: '历史上传商品', 0, 80), 'url' => trim($im)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($entries)) {
|
||||
return;
|
||||
}
|
||||
$now = time();
|
||||
foreach ($entries as $ent) {
|
||||
$rawU = $ent['url'];
|
||||
if ($rawU === '' || strlen($rawU) > 255) {
|
||||
continue;
|
||||
}
|
||||
$fullUrl = strpos($rawU, 'http') === 0 ? $rawU : cdnurl($rawU, true);
|
||||
$exists = Db::name('user_goods_gallery')
|
||||
->where('user_id', $uid)
|
||||
->where('image', 'in', [$rawU, $fullUrl])
|
||||
->value('id');
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
Db::name('user_goods_gallery')->insert([
|
||||
'user_id' => $uid,
|
||||
'cat_id' => max(0, (int)$ent['cat_id']),
|
||||
'title' => $ent['title'],
|
||||
'image' => $fullUrl,
|
||||
'createtime' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全读取兼容 JSON 数组与字符串的 POST 参数(规避 ThinkPHP 5 无 /a 修饰符时对数组抛类型异常)
|
||||
*/
|
||||
protected function postMixed($key, $default = null)
|
||||
{
|
||||
$all = $this->request->post(false);
|
||||
if (is_array($all) && array_key_exists($key, $all)) {
|
||||
return $all[$key];
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传保存商品图至个人专属商品图库
|
||||
* @ApiMethod (POST)
|
||||
@@ -1925,7 +1913,7 @@ class Order extends Base
|
||||
$catId = max(0, (int)$this->request->post('cat_id', 0));
|
||||
$title = mb_substr(trim((string)$this->request->post('title', '')), 0, 80);
|
||||
$singleImg = trim((string)$this->request->post('image', ''));
|
||||
$rawImages = $this->request->post('images', '', null);
|
||||
$rawImages = $this->postMixed('images', '');
|
||||
|
||||
$imgList = [];
|
||||
if ($singleImg !== '') {
|
||||
@@ -1944,13 +1932,17 @@ class Order extends Base
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($imgList)) {
|
||||
$this->error('请上传有效的商品图片');
|
||||
}
|
||||
|
||||
$validUrls = [];
|
||||
foreach ($imgList as $u) {
|
||||
if (!is_string($u)) {
|
||||
continue;
|
||||
}
|
||||
$u = trim($u);
|
||||
if ($u === '' || strlen($u) > 255 || substr(strtolower($u), -4) === '.svg') {
|
||||
if ($u === '' || strlen($u) > 255 || $this->isSystemDefaultImageUrl($u)) {
|
||||
continue;
|
||||
}
|
||||
if (strpos($u, 'http://') === 0 || strpos($u, 'https://') === 0 || strpos($u, '/') === 0) {
|
||||
@@ -1959,7 +1951,7 @@ class Order extends Base
|
||||
}
|
||||
$validUrls = array_values(array_unique($validUrls));
|
||||
if (empty($validUrls)) {
|
||||
$this->error('请上传有效的商品图片');
|
||||
$this->success('系统默认图片无需重复保存到历史图库', ['added' => 0, 'list' => []]);
|
||||
}
|
||||
|
||||
if ($title === '' && $catId > 0) {
|
||||
@@ -1971,6 +1963,7 @@ class Order extends Base
|
||||
|
||||
$now = time();
|
||||
$saved = [];
|
||||
$addedCount = 0;
|
||||
foreach ($validUrls as $u) {
|
||||
$fullUrl = strpos($u, 'http') === 0 ? $u : cdnurl($u, true);
|
||||
$exists = Db::name('user_goods_gallery')
|
||||
@@ -1995,6 +1988,7 @@ class Order extends Base
|
||||
'image' => $fullUrl,
|
||||
'createtime' => $now,
|
||||
]);
|
||||
$addedCount++;
|
||||
$saved[] = [
|
||||
'id' => $newId,
|
||||
'cat_id' => $catId,
|
||||
@@ -2005,7 +1999,7 @@ class Order extends Base
|
||||
];
|
||||
}
|
||||
|
||||
$this->success('已存入商品图库', ['list' => $saved]);
|
||||
$this->success('已存入商品图库', ['added' => $addedCount, 'list' => $saved]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2016,7 +2010,7 @@ class Order extends Base
|
||||
{
|
||||
$uid = $this->uid();
|
||||
$singleId = (int)$this->request->post('id', 0);
|
||||
$rawIds = $this->request->post('ids', '', null);
|
||||
$rawIds = $this->postMixed('ids', '');
|
||||
|
||||
$idArr = [];
|
||||
if ($singleId > 0) {
|
||||
@@ -2249,7 +2243,7 @@ class Order extends Base
|
||||
$tplName = $itemName . ($specName !== '' && $specName !== $itemName ? (' · ' . $specName) : '');
|
||||
}
|
||||
|
||||
$rawExtras = $this->request->post('extras', '', null);
|
||||
$rawExtras = $this->postMixed('extras', '');
|
||||
$extrasArr = [];
|
||||
if (!empty($rawExtras)) {
|
||||
$decEx = is_array($rawExtras) ? $rawExtras : json_decode(htmlspecialchars_decode((string)$rawExtras), true);
|
||||
@@ -2268,7 +2262,7 @@ class Order extends Base
|
||||
}
|
||||
}
|
||||
|
||||
$rawImages = $this->request->post('images', '', null);
|
||||
$rawImages = $this->postMixed('images', '');
|
||||
$imagesArr = [];
|
||||
if (!empty($rawImages)) {
|
||||
$decIm = is_array($rawImages) ? $rawImages : json_decode(htmlspecialchars_decode((string)$rawImages), true);
|
||||
|
||||
在新工单中引用
屏蔽一个用户