'已下单', self::STATUS_MAKEING => '制作中', self::STATUS_SHIPPED => '已发货', self::STATUS_ARRIVED => '已送达', self::STATUS_COMPLETED => '已完成', self::STATUS_CANCEL => '已取消', ]; } /** * 获取订单列表 * * @param int $accountId * @param array $fields * @return array * @throws RepositoryException */ public function listByUid(int $accountId, array $fields = []): array { return $this->findList(['account_id' => $accountId], $fields); } /** * 获取指定sku库存 * * @param array $codingIds * @return array */ public function stock(array $codingIds): array { return Sku::where('coding', 'in', $codingIds)->column('stock', 'coding'); } /** * 订单提交数据 校验+处理 * * @param array $data * @return array * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException * @throws RepositoryException */ public function validateOrderData(array $data): array { /** 订单提交的数据 必需按下列格式 * $data['sku_list'] = [ * [ * 'sku_coding' => '16261684919319972261', * 'group_id' => 1, * 'num' => 1, * ], * [ * 'sku_coding' => '16261684169515092561', * 'group_id' => 0, * 'num' => 1, * ], * [ * 'sku_coding' => '16261684169515092561', * 'group_id' => 1, * 'num' => 1, * ], * ]; * $data['total'] = 950; //订单应付总金额(前端计算的结果)单位:元 * $data['address_id'] = 26; //地址ID 0=自提 * $data['express_code'] = ''; //快递公司代号 * $data['pick_self'] = 0; //是否自提 0=否 1=是 * $data['pick_self_phone'] = '13541194066'; //自提时必填 自提人联系方式 * $data['original_total'] = 1000; //商品总价 单位元 * $data['freight'] = 0; //运费 单位元 * $data['remarks'] = '';//买家备注 */ if (!isset($data['sku_list']) || empty($data['sku_list'])) { throw new RepositoryException('商品不能为空'); } if (!isset($data['address']) || empty($data['address'])) { throw new RepositoryException('收货地址不能为空'); } if (!isset($data['phone']) || empty($data['phone'])||!preg_match("/^1[3456789]{1}\d{9}$/",$data['phone'])) { throw new RepositoryException('收货联系电话不能为空或格式不正确'); } if (!isset($data['contacts']) || empty($data['contacts'])) { throw new RepositoryException('收货联系人不能为空'); } // if (!isset($data['pick_self']) || // !isset($data['freight'])) { // throw new RepositoryException('订单参数错误'); // } // $data['coupon_id'] = $data['coupon_id'] ?? 0; // $data['coupon_price'] = $data['coupon_price'] ?? 0; // $data['is_score'] = $data['is_score'] ?? 0; // if ($data['pick_self'] == Order::COMMON_OFF && !isset($data['address_id'])) { // throw new RepositoryException('请选择收货地址'); // } // if ($data['pick_self'] == Order::COMMON_ON && !isset($data['pick_self_phone'])) { // throw new RepositoryException('自提时请填写联系人方式'); // } // if (isset($data['express_code']) && !empty($data['express_code'])) { // if (!$express = Express::where('code', $data['express_code'])->find()) { // throw new RepositoryException('快递公司不存在'); // } // $data['express_name'] = $express['name'] ?? ''; // } $skuList = $this->handleSku($data['sku_list']); //$groupList = $this->handleGroup($data['sku_list']); $data['sku_list'] = $skuList; //$data['group_list'] = $groupList; return $data; } /** * sku数据处理 * * @param array $data * @return array * @throws RepositoryException */ protected function handleSku(array $data): array { $skuList = []; foreach ($data as $sku) { if (!isset($sku['sku_coding']) || empty($sku['sku_coding'])) { throw new RepositoryException('SKU编码不能为空'); } //多个相同sku_coding合并 if (!empty($skuList[$sku['sku_coding']])) { $skuList[$sku['sku_coding']] += $sku['num'] ?? 1; } else { $skuList[$sku['sku_coding']] = $sku['num'] ?? 1; } } return $skuList; } /** * 拼团数据处理 * * @param array $data * @return array * @throws RepositoryException */ protected function handleGroup(array $data): array { $list = []; foreach ($data as $sku) { if (!isset($sku['sku_coding']) || empty($sku['sku_coding'])) { throw new RepositoryException('SKU编码不能为空'); } $list[$sku['sku_coding']] = $sku['group_id'] ?? 0; } return $list; } /** * 订单前置数据 校验+处理 * * @param array $data * @return array * @throws RepositoryException */ public function validatePrepareData(array $data): array { /** 订单前置数据 必需按下列格式 * $data['sku_list'] = [ * [ * 'sku_coding' => '16261684919319972261', * 'num' => 1, * ], * [ * 'sku_coding' => '16261684169515092561', * 'num' => 1, * ], * [ * 'sku_coding' => '16261684169515092561', * 'num' => 1, * ], * ]; */ if (!isset($data['sku_list']) || empty($data['sku_list'])) { throw new RepositoryException('商品不能为空1'); } $skuList = $this->handleSku($data['sku_list']); $data['sku_list'] = $skuList; return $data; } /** * 创建订单 流程:1、参数完整性校验 2、参数正确性校验[收货地址、商品列表、商品价格等] 3、对比前端计算结果 4、创建订单 * * @param int $accountId 用户ID * @param array $data 订单数据 * @return Model * @throws RepositoryException|GuzzleException */ public function createOrder(int $accountId, array $data): Model { // 启动事务 Db::startTrans(); try { $account = Account::where('id', $accountId)->lock(true)->find(); if (!$account) { throw new RepositoryException('用户不存在'); } $data = $this->validateOrderData($data); $dataSku = $data['sku_list'];//商品sku coding=>num 商品数量 $skuList = SpuRepository::getInstance()->listBySkuCoding(array_keys($dataSku), true); $skuPriceList = $skuList->column('original_price', 'coding'); // if (isset($data['is_only']) && $data['is_only'] > Spu::COMMON_OFF) { // $skuPriceList = $skuList->column('original_price', 'coding'); // } else { // $skuPriceList = $skuList->column('price', 'coding'); // } $skuScoreList = $skuList->column('score', 'coding');//商品规格=》积分 $skuStockList = $skuList->column('stock', 'coding'); $totalPrice = 0;//商品原总价 单位(元) 未做任何优惠 $totalScore = 0;//商品总积分 foreach ($dataSku as $coding => $num) { if (!isset($skuPriceList[$coding])) { throw new RepositoryException('部分商品不存在或已下架 规格编码:'.$coding); } $totalPrice += $skuPriceList[$coding] * $num; $totalScore += $skuScoreList[$coding] * $num; if ($skuStockList[$coding] < $num) { throw new RepositoryException('有商品库存不足'); } } $skuArr = $skuList->each(function ($item) use ($dataSku, $accountId) { $item->num = $dataSku[$item->coding] ?? 1; $item->account_id = $accountId; }); // 限购检查 包含活动商品和普通商品 $this->skuCheck($skuArr->toArray()); //商品减库存 $updateSkuStock = [];//规格库存 foreach ($skuList as $sku) { $arr = []; $arr['id'] = $sku['id']; $arr['stock'] = Db::raw('`stock` - '.($dataSku[$sku['coding']] ?? 1));; $updateSkuStock[] = $arr; } (new Sku())->saveAll($updateSkuStock); // $freight = 0;//运费(元) // // 邮寄方式 // if ($data['pick_self'] == Order::COMMON_OFF) { // // 快递公司默认运费 // if (isset($data['express_code']) && !empty($data['express_code'])) { // $freight = Express::getDefaultFreight($data['express_code']); // } // // if (!$address = AccountAddress::findById($data['address_id'])) { // throw new RepositoryException('收货地址不存在'); // } // // $addressInfo = sprintf("%s,%s,%s %s %s %s", $address['name'], $address['phone'], // $address['province_str'], $address['city_str'], $address['county_str'], $address['address']); // } else { // $addressInfo = '自提'; // } //$realTotalPrice = $totalPrice - $data['coupon_price'];//实际应支付=商品总价-优惠券金额 $realTotalPrice = $totalPrice;//实际应支付=商品总价-优惠券金额 $realTotalPrice = $realTotalPrice <= 0 ? 0 : $realTotalPrice; // $realTotalPrice += $freight;//加运费 $insert = []; $time = time(); $now = date('Y-m-d H:i:s', $time); // 付款有效期 $expired = date('Y-m-d H:i:s', $time + 15 * 60); $payType = Order::PAY_TYPE_WECHAT; // 积分支付 // if ($data['is_score'] > 0) { // if ($account['score'] < $totalScore) { // throw new RepositoryException('积分不足'); // } // $payType = Order::PAY_TYPE_SCORE; // } $create = [ 'coding' => generateCode(), 'account_id' => $accountId, 'original_price' => $totalPrice, 'price' => $realTotalPrice, 'pay_type' => $payType,//支付方式 'status' => self::STATUS_ORDER_PLACED, 'created_at' => $now, 'score' => $totalScore,//使用积分 'expired_at' => $expired, 'address_id' => $data['address_id'] ?? 0, //'address' => $addressInfo, 'remarks' => $data['remarks'] ?? '', 'express_code' => $data['express_code']??'', 'express_name' => $data['express_name'] ?? '', 'pick_self' => $data['pick_self'] ??0, 'coupon_id' => $data['coupon_id'] ?? 0, 'freight' => 0, 'coupon_price' => $data['coupon_price'] ?? 0, //'phone' => $address['phone'] ?? $data['pick_self_phone'], 'is_only' => isset($data['is_only']) && $data['is_only'] > 0 ? 1 : 0, 'is_score' => $data['is_score'] ?? 0, 'wedding_date' => $data['wedding_date'] ?? null, 'expected_delivery_date' => $data['expected_delivery_date'] ?? null, 'address' => $data['address'] ?? '', 'phone' => $data['phone'] ?? '', 'contacts' => $data['contacts'] ?? '', ]; if ($realTotalPrice == 0) { $create['status'] = self::STATUS_ORDER_PLACED; $create['paid_at'] = $now; } //创建订单 if (!$order = $this->create($create)) { throw new RepositoryException('订单创建失败,请稍后重试'); } // // 优惠券状态变更 // if ($data['coupon_id'] > 0 && $data['coupon_price'] > 0) { // AccountCoupon::use($accountId, $data['coupon_id'], $order['coding']); // } // 创建活动商品订单 // OrderRepository::getInstance()->createActivityOrder($accountId, $order->coding, $skuArr->toArray()); // if ($realTotalPrice > 0) { // //创建支付记录 // PaymentLog::create([ // 'account_id' => $accountId, // 'order_coding' => $order['coding'], // 'created_at' => $now, // 'type' => 'order', // 'amount' => $order['price'], // ]); // } // $order->needPay = $realTotalPrice > 0;//是否需要付款 $orderHasVirtual = 0;//是否拥有虚拟商品 $skuIds = [];//当前订单涉及到的skuID foreach ($skuList as $sku) { $skuIds[] = $sku->id; $arr = []; $arr['order_coding'] = $order->coding; $arr['coding'] = $sku->coding; $arr['spu_id'] = $sku->spu_id; $arr['spu_activity_id'] = $sku->spu_activity_id; $arr['sku_id'] = $sku->id; $arr['account_id'] = $accountId; $arr['price'] = (isset($data['is_only']) && $data['is_only'] > Spu::COMMON_OFF) ? $sku->original_price : $sku->price; $arr['score'] = $sku->score; $arr['num'] = $dataSku[$sku->coding]; $arr['status'] = 'normal'; $arr['created_at'] = $now; $arr['spu_cover'] = $sku->spu_cover; $arr['spu_name'] = $sku->spu_name; $arr['sku_cover'] = $sku->sku_cover; $arr['sku_name'] = $sku->sku_name; $arr['is_virtual'] = $sku->is_virtual; $arr['check_type'] = $sku->check_type; $arr['spec_text'] = $sku->spec_text ? json_encode($sku->spec_text, JSON_UNESCAPED_UNICODE) : ''; $arr['not_check_num'] = $dataSku[$sku->coding]; $arr['has_comment'] = OrderSku::HAS_COMMENT_NO; $arr['is_activity'] = $sku->is_activity; $arr['activity_type'] = $sku->activity_type; $arr['sku_unit'] = $sku->unit; $arr['subtotal'] = $sku->original_price * $dataSku[$sku->coding]; $insert[] = $arr; if ($sku->is_virtual > 0) { $orderHasVirtual = 1; } } if ($orderHasVirtual > 0) { $order->save(['has_virtual' => Order::COMMON_ON]); } // 删除购物车中相关SKU ShoppingCart::whereIn('sku_id', $skuIds)->where('account_id', $accountId)->delete(); //保存订单商品 if (!(new OrderSku())->saveAll($insert)) { throw new RepositoryException('订单商品保存失败'); } // if ($realTotalPrice > 0) { // //统一下单 // $res = $this->wechatMiniPay($order->coding, $account->openid, Math::yuan2Fen($realTotalPrice)); // $order->save(['prepay_id' => $res['prepay_id']]); // // //生成小程序支付请求的参数 // $order->payment_params = $this->miniPayReqParams($res['prepay_id']); // } // 积分订单 扣减积分并记录 // if ($totalScore > 0) { // AccountDataLog::log($accountId, // '积分商品下单', -$totalScore, // AccountDataLog::TYPE_SCORE, // AccountDataLog::ACTION_ORDER, // $account['score'] - $totalScore); // $account->score = Db::raw('`score` - '.$totalScore); // } $account->mobile = $data["phone"]; $account->address = $data["address"]; $account->contacts = $data["contacts"]; $account->save(); // if ($realTotalPrice == 0) { // // 设为已付款 // $this->afterPaid($accountId, $order['coding']); // } // 设为已付款 $this->afterPaid($accountId, $order['coding']); Db::commit(); return $order; } catch (RepositoryException $e) { Db::rollback(); throw new RepositoryException($e->getMessage(), $e->getCode()); } catch (Exception $e) { //回滚 Db::rollback(); self::log('订单创建失败', $e, 'error', 'order'); throw new RepositoryException('订单创建失败'.$e->getMessage()); } } /** * 根据统一下单返回的prepay_id 生成小程序支付请求参数 * * @param string $prepayId * @return array */ protected function miniPayReqParams(string $prepayId): array { CConfig::load('extra/wechat', 'wechat'); $config = config('wechat'); $params = [ 'appId' => $config['applets_appId'] ?? '', 'timeStamp' => (string) time(), 'nonceStr' => uniqid(), 'package' => 'prepay_id='.$prepayId, 'signType' => 'MD5' ]; $params['sign'] = generate_sign($params, $config['key']); return $params; } /** * 微信支付 * * @param string $orderId * @param string $openid * @param int $price * @return array|\EasyWeChat\Kernel\Support\Collection|object|ResponseInterface|string * @throws GuzzleException * @throws InvalidArgumentException * @throws InvalidConfigException * @throws RepositoryException */ public function wechatMiniPay(string $orderId, string $openid, int $price) { $res = WechatPay::getInstance()->order->unify([ 'body' => '商品购买', 'out_trade_no' => $orderId, 'total_fee' => $price, 'trade_type' => 'JSAPI', 'openid' => $openid, ]); $log = sprintf("[微信统一下单返回][订单号=%s]%s", $orderId, json_encode($res, JSON_UNESCAPED_UNICODE)); Log::info($log); //return_code仅是通信状态 非支付状态 if (isset($res['return_code']) && $res['return_code'] == 'SUCCESS') { if (isset($res['result_code']) && $res['result_code'] == 'SUCCESS') { return $res; } else { throw new RepositoryException('支付请求失败'); } } else { throw new RepositoryException('请求微信接口失败'); } } /** * 订单准备信息获取 * 即根据商品(sku)列表 收货地址 返回商品列表信息、运费、总价、可用分红额度等信息 * * @param int $accountId 用户ID * @param array $data 订单数据 * @return array * @throws RepositoryException */ public function prepareInfo(int $accountId, array $data): array { $data = $this->validateprepareData($data); $dataSku = $data['sku_list'];//商品sku coding=>num 商品数量 try { $res = [];//待返回数据 if (!$account = Account::findById($accountId, ['id', 'score'])) { throw new RepositoryException('读取用户信息失败'); } $skuList = SpuRepository::getInstance()->listBySkuCoding(array_keys($dataSku)); $skuPriceList = $skuList->column('price', 'coding'); $totalPrice = 0;//商品原总价 单位(元) 未做任何优惠 foreach ($dataSku as $coding => $num) { $totalPrice += $skuPriceList[$coding] * $num; } $skuList->each(function ($item) use ($dataSku, $data) { $item->num = $dataSku[$item->coding] ?? 1; if (!empty($item->spu_cover) && !isHttpUrl($item->spu_cover)) { $item->spu_cover = $data['domain'].$item->spu_cover; } if (!empty($item->sku_cover) && !isHttpUrl($item->sku_cover)) { $item->sku_cover = $data['domain'].$item->sku_cover; } }); $res['original_total'] = $totalPrice; $res['list'] = $skuList; $res['account'] = $account; $res['discount'] = 0; return $res; } catch (RepositoryException $e) { throw new RepositoryException($e->getMessage(), $e->getCode()); } catch (Exception $e) { self::log('订单准备信息获取失败', $e, 'error', 'order'); throw new RepositoryException('订单数据错误'); } } /** * 获取可以自动收货的订单列表 * * @param int $day * @return Collection * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException */ public function autoReceiptList(int $day = 20): Collection { //发货后day天 自动确认收货 $shipped_at = date('Y-m-d H:i:s', time() - $day * 86400); return $this->model->where('status', self::STATUS_SHIPPED) ->field('status,id,coding') ->where('shipped_at', '<', $shipped_at) ->select(); } /** * 支付过期的订单列表 * * @return Collection * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException */ public function expiredList(): Collection { //过期时间大于当前时间 return $this->model->where('status', self::STATUS_WAITING) ->field('status,id') ->where('expired_at', '<', date('Y-m-d H:i:s')) ->select(); } /** * 订单状态改为已过期 * * @param string $orderCoding * @return bool * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException * @throws RepositoryException */ public function setExpired(string $orderCoding): bool { return $this->setClosed($orderCoding, Order::STATUS_EXPIRED); } /** * 检测用户所有已过期的订单 * * @param int $accountId * @return bool */ public function checkAccountExpiredOrders(int $accountId = 0): bool { if ($accountId <= 0) { return false; } Db::startTrans(); try { if ($accountId > 0 && !Account::findById($accountId)) { throw new RepositoryException('用户不存在'); } $where = []; $where[] = ['status', '=', self::STATUS_WAITING]; if ($accountId > 0) { $where[] = ['account_id', '=', $accountId]; } $waitingOrders = $this->model->where($where)->select(); $toExpiredIds = []; $toExpiredOrderIds = []; $nowTime = time(); foreach ($waitingOrders as $order) { if (!empty($order['expired_at'])) { $expireTime = strtotime($order['expired_at']); if ($nowTime >= $expireTime) { $toExpiredIds[] = $order['id']; $toExpiredOrderIds[] = $order['coding']; } } } // 订单状态变更 if (count($toExpiredIds) > 0) { $this->model->whereIn('id', $toExpiredIds) ->update(['status' => OrderRepository::STATUS_EXPIRED]); } // 商品库存量恢复 if (count($toExpiredOrderIds) > 0) { $this->backStock($toExpiredOrderIds); } Db::commit(); } catch (RepositoryException | Exception $e) { Db::rollback(); self::log('订单已过期检测失败', $e, 'error', 'order'); return false; } return true; } /** * 退还订单库存 * * @param array $orderCoding 订单编号数组 * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException */ public function backStock(array $orderCoding) { $orderSkus = OrderSku::whereIn('coding', $orderCoding)->field('num, sku_id')->select(); if (!$orderSkus->isEmpty()) { $sku2Num = []; foreach ($orderSkus as $item) { if (isset($sku2Num[$item['sku_id']])) { $sku2Num[$item['sku_id']] += $item['num']; } else { $sku2Num[$item['sku_id']] = $item['num']; } } $skuList = Sku::where('id', 'in', array_keys($sku2Num))->field('id,stock')->lock(true)->select(); $skuList->each(function ($item) use ($sku2Num) { if (isset($sku2Num[$item->id])) { $item->stock += $sku2Num[$item->id]; } }); $backSkuStocks = $skuList->toArray(); (new Sku())->saveAll($backSkuStocks); } } /** * 订单状态改为已支付 * * @param string $orderCoding * @return bool * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException * @throws RepositoryException */ public function setPaid(string $orderCoding): bool { if (!$order = $this->findOneByWhere(['coding' => $orderCoding])) { throw new RepositoryException('订单不存在'); } if ($order['status'] == OrderRepository::STATUS_PAID) { $this->afterPaid($order['account_id'], $orderCoding); return true; } if ($order['status'] != OrderRepository::STATUS_PAID && $order['status'] != OrderRepository::STATUS_WAITING) { throw new RepositoryException('订单状态异常,若您已支付,请联系管理员'); } $now = date('Y-m-d H:i:s'); if ($order['status'] === OrderRepository::STATUS_WAITING) { Db::startTrans(); try { $payment = PaymentLog::findOne(['account_id' => $order['account_id'], 'coding' => $orderCoding]); if (!$payment) { PaymentLog::create([ 'account_id' => $order['account_id'], 'coding' => $orderCoding, 'created_at' => $order['created_at'], 'paid_at' => $now, 'type' => 'order', 'amount' => $order['price'], ]); } else { $payment->paid_at = $now; $payment->status = PaymentLog::COMMON_ON; $payment->save(); } $order->status = Order::STATUS_PAID; if ($order['pick_self'] == Order::COMMON_ON) { //自提 改为已发货 $order->status = Order::STATUS_SHIPPED; $order->shipped_at = $now; } $order->paid_at = $now; $res = $order->save(); $this->afterPaid($order['account_id'], $orderCoding); if ($res) { Db::commit(); } return $res; } catch (RepositoryException $e) { Db::rollback(); throw new RepositoryException($e->getMessage(), $e->getCode()); } catch (Exception $e) { //回滚 Db::rollback(); self::log('支付成功状态变更失败', $e, 'error', 'order'); throw new RepositoryException('操作失败'); } } return false; } /** * 支付后调用 * * @param int $accountId * @param string $orderCoding * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException */ public function afterPaid(int $accountId, string $orderCoding) { OrderSku::setPaid($orderCoding);//订单商品 //OrderActivity::setPaid($orderCoding);//活动订单 // OrderGroupList::setPaid($orderCoding);//订单拼团列表 // 付款后 所有商品销量添加 $this->updateAmount($orderCoding); //清理除购物车 OrderRepository::getInstance()->delShoppingCartByOrder($accountId, $orderCoding); } /** * 取消支付后调用 * * @param int $accountId * @param string $orderCoding * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException */ public function afterCancelPaid(int $accountId, string $orderCoding) { // OrderSku::setPaid($orderCoding);//订单商品 // OrderActivity::setPaid($orderCoding);//活动订单 // OrderGroupList::setPaid($orderCoding);//订单拼团列表 // 销量减少 $this->updateAmount($orderCoding, 'reduce'); } /** * 订单验收 - 确认收货 * * @param int $orderId * @param int $accountId * @throws RepositoryException */ public function orderAccepted(int $orderId, int $accountId = 0) { Db::startTrans(); try { $order = OrderRepository::getInstance()->findById($orderId, []); if (empty($order)) { throw new RepositoryException('没有相关的订单记录'); } $accountId = $accountId == 0 ? $order['account_id'] : $accountId; if (!in_array($order['status'] ,[self::STATUS_SHIPPED,self::STATUS_ARRIVED])) { throw new RepositoryException('当前订单状态不支持收货操作'); } $accountRepo = AccountRepository::getInstance(); $nowDateTime = date('Y-m-d H:i:s'); $account = $accountRepo->findById($accountId, []); if ($order['account_id'] !== $accountId || empty($account)) { throw new RepositoryException('用户不存在'); } $this->update([ 'status' => self::STATUS_COMPLETED, 'commission_give' => Order::COMMON_ON, 'accepted_at' => $nowDateTime ], ['id' => $orderId]); /* CConfig::load('extra/commission_withdrawal', 'commission'); $config = config('commission'); */ // 一级佣金发放 /* if ($account['inviter_account_id'] > 0 && isset($config['commission_first']) && $config['commission_first'] > 0) { $firstRate = Math::div($config['commission_first'], 100); $firstCommission = Math::mul($order['price'], $firstRate); $accountFirst = Account::findById($account['inviter_account_id']); if ($accountFirst && $firstCommission > 0) { AccountDataLog::log($account['inviter_account_id'], '订单确认收货-一级分销佣金发放', $firstCommission, AccountDataLog::TYPE_COMMISSION, AccountDataLog::ACTION_ORDER, Math::add($accountFirst['commission'], $firstCommission) ); $accountFirst->save([ 'commission' => Db::raw('`commission` + '.$firstCommission) ]); } } */ // 二级分销发放佣金 /* if ($account['inviter_parent_id'] > 0 && isset($config['commission_second']) && $config['commission_second'] > 0) { $secondRate = Math::div($config['commission_second'], 100); $secondCommission = Math::mul($order['price'], $secondRate); $accountSecond = Account::findById($account['inviter_parent_id']); if ($accountSecond && $secondCommission > 0) { AccountDataLog::log($account['inviter_parent_id'], '订单确认收货-二级分销佣金发放', $secondCommission, AccountDataLog::TYPE_COMMISSION, AccountDataLog::ACTION_ORDER, Math::add($accountSecond['commission'], $secondCommission) ); $accountSecond->save([ 'commission' => Db::raw('`commission` + '.$secondCommission) ]); } } */ Db::commit(); } catch (RepositoryException $e) { Db::rollback(); throw new RepositoryException($e->getMessage()); } catch (Exception $e) { Db::rollback(); self::log('订单确认收货失败', $e, 'error', 'order'); throw new RepositoryException('订单数据错误'); } } /** * 我的订单 * * @param int $accountId * @param string $tag all=全部[待付款 已付款 待发货 已发货 已完成 已取消] after_sale=售后[已发货 已完成等] * @param int $page * @param int $size * @return array|null * @throws RepositoryException */ public function mine(int $accountId, string $tag, int $page = 1, int $size = 20): ?array { $where = []; $where[] = ['account_id', '=', $accountId]; event('OrderCheck'); switch ($tag) { //待付款 已付款 已过期 待发货 待收货 case self::STATUS_ORDER_PLACED: case self::STATUS_MAKEING: case self::STATUS_SHIPPED: case self::STATUS_ARRIVED: case self::STATUS_COMPLETED: case self::STATUS_CANCEL: $status = [$tag]; break; // 待评价 已确认收货 未评价 /* case 'waiting_comment': $where[] = ['is_evaluate', '=', self::IS_EVALUATE_YES]; $status = [self::STATUS_COMPLETED]; break; */ // 待核验 /* case 'check': $where[] = ['has_virtual', '=', Order::COMMON_ON]; $where[] = ['virtual_check', '=', Order::COMMON_OFF]; $where[] = ['frontend_check', '=', Order::COMMON_OFF]; $status = [self::STATUS_PAID, self::STATUS_COMPLETED, self::STATUS_SHIPPED]; break; */ // 售后记录 /* case 'after_sale': $status = [self::STATUS_PAID, self::STATUS_SHIPPED, self::STATUS_COMPLETED]; $where[] = ['is_after_sale', '=', self::BOOL_TRUE]; break; */ default: $status = [ self::STATUS_ORDER_PLACED , self::STATUS_MAKEING , self::STATUS_SHIPPED , self::STATUS_ARRIVED , self::STATUS_COMPLETED , self::STATUS_CANCEL , ]; } if (!empty($status)) { $where[] = ['status', 'in', $status]; } $fields = []; return $this->findList($where, $fields, $page, $size, function ($q) use ($tag) { return $q->with(['skus'])->order('created_at', 'desc'); }); } /** * 立即免拼 单人开团 * 免拼逻辑 1. 直接将订单状态修改 2. 订单相关拼团列表的剩余数量 全部设为0 * is_only=0 is_group_make=1 open_one=1 open_one_success=0,group_make_end_at < 当前时间 订单列表才显示【立即免拼】,其他都不显示 * * @param int $accountId * @param string $coding * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException * @throws RepositoryException */ public function openOne(int $accountId, string $coding) { if (!$order = Order::findOne(['coding' => $coding])) { throw new RepositoryException('订单不存在'); } if ($order['is_group_make'] != Order::COMMON_ON) { throw new RepositoryException('不是拼团订单'); } if (in_array($order['status'], [Order::STATUS_WAITING, Order::STATUS_CLOSED, Order::STATUS_EXPIRED])) { throw new RepositoryException('订单状态错误'); } if ($order['account_id'] != $accountId) { throw new RepositoryException('不是您的订单'); } $order->save(['open_one_success' => Order::COMMON_ON]); OrderGroupList::where('coding', $coding)->save(['surplus' => 0]); } /** * 订单数量 * * @param int $accountId * @return array */ public function orderCount(int $accountId): array { $statusList = ['order_placed', 'makeing', 'shipped']; $data = Order::where('account_id', $accountId) ->whereIn('status', $statusList) ->group('status') ->column('count(`id`) as count', 'status'); $list = []; foreach ($statusList as $status) { $list[$status] = $data[$status] ?? 0; } return $list; } /** * 订单详情 * * @param int $orderId * @return mixed|null * @throws RepositoryException */ public function detail(int $orderId) { $fields = []; return $this->findById($orderId, $fields, function ($q) { return $q->with(['skus'])->order('created_at', 'desc'); }); } /** * 获取订单物流信息 * * @param string $orderCoding * @return array|Model * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException * @throws RepositoryException */ public function logistics(string $orderCoding) { $fields = ['id', 'status', 'coding', 'is_after_sale', 'prepay_id', 'original_price', 'price', 'express_number', 'express_code', 'express_name']; if (!$order = $this->model->with(['skus'])->field($fields)->where('coding', $orderCoding)->find()) { throw new RepositoryException('订单不存在'); } $expressInfo = []; if (!empty($order['express_code']) && !empty($order['express_number'])) { try { if ($order['status'] == self::STATUS_COMPLETED) { $exprLog = $this->findExpressLogByNu($order['coding'], $order['express_code'], $order['express_number']); if (!empty($exprLog)) { $expressInfo['status'] = 200; $expressInfo['state'] = $exprLog['state']; $expressInfo['state_desc'] = Kd100::state()[$exprLog['state']] ?? ''; $expressInfo['com'] = $exprLog['com']; $expressInfo['nu'] = $exprLog['nu']; $expressInfo['data'] = $exprLog['content']; } else { $expressInfo = $this->handleOrderExpressLog($order['coding'], $order['express_code'], $order['express_number']); } } else { $expressInfo = $this->handleOrderExpressLog($order['coding'], $order['express_code'], $order['express_number']); } } catch (Exception $e) { $expressInfo = []; } } $order->express_info = $expressInfo; return $order; } /** * 订单发货 * * @param int $orderId 订单ID * @param int $expressId 快递公司ID * @param string $expressNumber 快递单号 * @param string $businessRemarks 卖家备注 * @throws RepositoryException */ public function orderShipping(int $orderId, int $expressId, string $expressNumber = '', string $businessRemarks = '') { Db::startTrans(); try { $order = $this->findById($orderId, []); if (empty($order)) { throw new RepositoryException('没有相关的订单记录'); } if ($order['status'] !== self::STATUS_PAID) { throw new RepositoryException('当前订单状态不支持发货操作'); } $express = $this->expressInfo($expressId); if (empty($express)) { throw new RepositoryException('相关的快递公司不存在'); } if (empty($expressNumber) || strlen($expressNumber) > 100) { throw new RepositoryException('请填写正确的快递单号'); } $this->update([ 'shipped_at' => date('Y-m-d H:i:s'), 'business_remarks' => $businessRemarks, 'express_code' => $express['code'], 'express_name' => $express['name'], 'express_number' => $expressNumber, 'status' => self::STATUS_SHIPPED, ], ['id' => $orderId]); Db::commit(); } catch (RepositoryException $e) { Db::rollback(); throw new RepositoryException($e->getMessage()); } catch (Exception $e) { Db::rollback(); self::log('订单发货失败', $e, 'error', 'order'); throw new RepositoryException('订单发货失败'); } } /** * 更新商品库存 TODO * * @param array $spuIds * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException */ public function updateSpuStock(array $spuIds) { $skuList = Sku::whereIn('id', $spuIds)->field('id,spu_id,stock')->select()->toArray(); $updateSpu = []; foreach ($skuList as $sku) { $arr = []; // $arr['spu'] } } /** * 商品规格检查 1.限购检查 2.活动时间检查 3.团购、拼团数量检查 * 包含活动与普通商品 * 商品数量不多的情况下,循环内部执行检测 TODO 待优化 部分查询可拿到循环外部 * @param array $data * @return bool * @throws RepositoryException|Exception */ public function skuCheck(array $data): bool { $now = date('Y-m-d H:i:s'); foreach ($data as $item) { //限购检测 if ($item['limit_num'] > 0) { //存在限购数量 $where = []; $where[] = ['is_paid', '=', OrderSku::COMMON_ON]; if ($item['spu_id'] > 0) { $where[] = ['spu_id', '=', $item['spu_id']]; } if ($item['spu_activity_id'] > 0) { $where[] = ['spu_activity_id', '=', $item['spu_activity_id']]; } if ($item['limit_time'] > 0) { $endAt = date('Y-m-d 23:59:59');//结束时间 $day = ($item['limit_time'] - 1) ?: 0; $str = '-'.$day.' days'; $beginAt = date('Y-m-d 00:00:00', strtotime($str)); $where[] = ['paid_at', '>', $beginAt]; $where[] = ['paid_at', '<', $endAt]; $where[] = ['account_id', '=', $item['account_id']]; //存在限购时间 数字单位为天 //结合限购数量 生效规则:N天内 限购数量-已购买数量 >= 本次购买数量 否则无法购买 } $history = OrderSku::history($where, ['id', 'spu_name']); if ($item['limit_num'] - $history->count() < $item['num']) { throw new RepositoryException(sprintf("商品[%s]%s仅限购买%d件", $item['goods_name'], $item['limit_time'] > 0 ? $item['limit_time'].'天内' : '', $item['limit_num']).$item['coding']); } } // 活动时间检测 if (!empty($item['begin_at'])) { if ($now < $item['begin_at']) { throw new RepositoryException('商品['.$item['goods_name'].']商品尚未开始'); } } // 活动时间检测 if (!empty($item['end_at'])) { if ($now > $item['end_at']) { throw new RepositoryException('商品['.$item['goods_name'].']商品已结束'); } } } return true; } /** * 更新订单销量 * * @param string $orderCoding * @param string $type add=添加 reduce=减少 * @return bool * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException * @throws Exception */ public function updateAmount(string $orderCoding, string $type = 'add'): bool { $op = '+'; if ($type != 'add') { $op = '-'; } $list = OrderSku::where('coding', $orderCoding)->select(); if ($list->isEmpty()) { return true; } $spuList = $list->where('spu_id', '>', 0)->toArray(); $activityList = $list->where('spu_activity_id', '>', 0)->toArray(); $spuUpdate = []; foreach ($spuList as $spu) { if (!isset($spuUpdate[$spu['spu_id']])) { $spuUpdate[$spu['spu_id']] = []; $spuUpdate[$spu['spu_id']]['id'] = $spu['spu_id']; $spuUpdate[$spu['spu_id']]['amount'] = $spu['num']; } else { $spuUpdate[$spu['spu_id']]['amount'] += $spu['num']; } } foreach ($spuUpdate as &$spu) { $spu['amount'] = Db::raw('`amount` '.$op.' '.$spu['amount']); } $activityUpdate = []; foreach ($activityList as $activity) { if (!isset($spuUpdate[$activity['spu_activity_id']])) { $activityUpdate[$activity['spu_activity_id']] = []; $activityUpdate[$activity['spu_activity_id']]['id'] = $activity['spu_activity_id']; $activityUpdate[$activity['spu_activity_id']]['amount'] = $activity['num']; } else { $activityUpdate[$activity['spu_activity_id']]['amount'] += $activity['num']; } } foreach ($activityUpdate as &$activity) { $activity['amount'] = Db::raw('`amount` '.$op.' '.$activity['amount']); } if (!empty($spuUpdate)) { (new Spu())->saveAll($spuUpdate); } if (!empty($activityUpdate)) { (new SpuActivity())->saveAll($activityUpdate); } return true; } /** * 取消订单或设置过期 * * @param string $orderCoding * @param string $status 操作类型 默认为取消 * @param string $reason 取消原因 * @return bool * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException * @throws RepositoryException */ public function setClosed(string $orderCoding, string $status = Order::STATUS_CANCEL, string $reason = ''): bool { if (!$order = Order::findOne(['coding' => $orderCoding])) { throw new RepositoryException('订单不存在'); } if (!in_array($order['status'], [Order::STATUS_ORDER_PLACED])) { throw new RepositoryException('订单状态不允许此操作'); } // 启动事务 Db::startTrans(); try { //库存还原 $this->backStock([$orderCoding]); // 取消拼团 拼团数量未恢复,因拼团是绝对成功。即便此拼团名额满 也可以自行发起 // OrderGroupList::cancel($orderCoding); // 活动订单取消 OrderActivity::cancel($orderCoding); //$account = Account::findById($order['account_id']); $order->close_reason = $reason; //待付款订单 // if ($order['status'] == Order::STATUS_ORDER_PLACED) { $order->status = $status; $order->save(); //积分日志 /* if ($order['score'] > 0) { AccountDataLog::log($order['account_id'], '取消订单退回', $order['score'], AccountDataLog::TYPE_SCORE, AccountDataLog::ACTION_ORDER, $account['score'] + $order['score']); } */ //待付款时 积分才退回 /* $account->save([ 'score' => Db::raw('`score` + '.$order['score']) ]); */ // 优惠券退回 /* AccountCoupon::where('order_coding', $orderCoding)->where('account_id', $order['account_id']) ->where('coupon_id', $order['coupon_id']) ->where('status', AccountCoupon::STATUS_USED) ->update(['status' => AccountCoupon::STATUS_NORMAL]); */ Db::commit(); return true; //} /* if ($order['status'] == Order::STATUS_PAID && $status == Order::STATUS_CANCEL) { //已付款订单 取消 //若已有虚拟商品被核销 则订单不允许退回 积分不退回 // $hasCheckSku = OrderSku::where('coding', $orderCoding) // ->where('is_virtual', OrderSku::COMMON_ON) // ->whereRaw('`num` > `not_check_num`') // ->count(); // if ($hasCheckSku > 0) { // throw new RepositoryException('订单部分商品已核销,无法进行此操作'); // } $refundCoding = generateCode(); $now = date('Y-m-d H:i:s'); //售后记录 OrderAfterSale::create([ 'coding' => $orderCoding, 'account_id' => $order['account_id'], 'description' => '取消订单并退款', 'created_at' => $now, 'status' => OrderAfterSale::STATUS_DONE, ]); //积分日志 if ($order['score'] > 0) { AccountDataLog::log($order['account_id'], '取消订单退回', $order['score'], AccountDataLog::TYPE_SCORE, AccountDataLog::ACTION_ORDER, $account['score'] + $order['score']); } //待付款时 积分才退回 $account->save([ 'score' => Db::raw('`score` + '.$order['score']) ]); // 订单金额大于0 退回 if ($order['price'] > 0) { $order->is_after_sale = Order::COMMON_ON; $order->after_sale_at = $now; $order->after_sale_end_at = $now; $result = WechatPay::getInstance()->refund->byOutTradeNumber($orderCoding, $refundCoding, $order['price'] * 100, $order['price'] * 100, [ 'refund_desc' => '【'.$orderCoding.'】订单退款', ]); if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'FAIL') { //退款不成功 self::log('订单退款不成功['.$orderCoding.']'.$result['err_code_des'].'all_msg:'.json_encode($result), null); $order->need_refund = Order::COMMON_ON;//需要退款 但未成功 } if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') { self::log('订单退款成功['.$orderCoding.']'.'all_msg:'.json_encode($result), null); } } $order->refund_coding = $refundCoding; $order->status = self::STATUS_CLOSED; $order->save(); Db::commit(); return true; } */ } catch (RepositoryException $e) { Db::rollback(); throw $e; } catch (Exception $e) { Db::rollback(); throw new RepositoryException($e->getMessage()); } } /** * 购物车数量 * * @param string $type 类型 spu=商品购物车 score=积分商品购物车 * @param int $accountId 用户ID * @throws Exception */ public function shoppingCartCount(int $accountId, string $type = 'spu'): int { $isScore = (int) ($type == 'score'); event('CheckShoppingCart', $accountId); return ShoppingCart::where('is_score', $isScore)->where('account_id', $accountId)->count(); } /** * 订单付款 * * @param string $orderCoding 订单编号 * @param int $accountId 用户ID * @throws RepositoryException|Exception */ public function pay(string $orderCoding, int $accountId): array { $order = $this->findOneByWhere(['coding' => $orderCoding]); if (empty($order)) { throw new RepositoryException('没有相关的订单记录'); } if ($order['status'] !== self::STATUS_WAITING) { throw new RepositoryException('订单状态已非待付款'); } if ($order['account_id'] != $accountId) { throw new RepositoryException('非本人订单'); } //统一下单 // $res = $this->wechatMiniPay($order->coding, $account->openid, $order->price); $prepayId = $order['prepay_id']; //生成小程序支付请求的参数 $paymentParams = $this->miniPayReqParams($prepayId); return [ 'prepay_id' => $prepayId, 'payment_params' => $paymentParams, ]; } /** * 商品sku核验 线下核验 * * @param string $orderCoding 订单编号 * @param int $id 订单商品ID =0则核验该订单下所有虚拟商品 >0核验具体记录 * @param int $num 核验数量 仅当$id>0时生效 * @param int $checkBy 核验人account_id =0则后台无account用户 * @param string $checkType * @return bool * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException * @throws RepositoryException * @throws Exception */ public function check(string $orderCoding, int $id = 0, int $num = 1, int $checkBy = 0, string $checkType = Order::CHECK_TYPE_FRONTEND): bool { if (!$checkUser = Account::findById($checkBy)) { throw new RepositoryException('核验人信息不存在'); } if ($checkUser['is_staff'] != Account::COMMON_ON) { throw new RepositoryException('此操作仅限员工'); } $checkUserInfo = sprintf("ID:%d 昵称:%s", $checkUser['id'], $checkUser['nickname']); return $this->checkBase($orderCoding, $id, $num, $checkUserInfo, $checkType); } /** * 商品sku核验 * * @param string $orderCoding 订单编号 * @param int $id 订单商品ID =0则核验该订单下所有虚拟商品 >0核验具体记录 * @param int $num 核验数量 仅当$id>0时生效 * @param string $checkBy 核验人信息 * @param string $checkType * @return bool * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException * @throws RepositoryException * @throws Exception */ public function checkBase(string $orderCoding, int $id = 0, int $num = 1, string $checkBy = '', string $checkType = Order::CHECK_TYPE_FRONTEND): bool { if (!$order = Order::findOne(['coding' => $orderCoding])) { throw new RepositoryException('订单不存在'); } if (in_array($order['status'], [Order::STATUS_CLOSED, Order::STATUS_WAITING])) { // 订单关闭或未付款 throw new RepositoryException('当前订单状态无法核验'); } $where = []; $where[] = ['coding', '=', $orderCoding]; $where[] = ['check_type', '=', $checkType]; $where[] = ['is_virtual', '=', Order::COMMON_ON];//虚拟商品 才能核验 $where[] = ['is_check', '=', Order::COMMON_OFF];// 非核验完成商品 才能核验 $where[] = ['not_check_num', '>', Order::COMMON_OFF];// 未核验数量大于0 才能核验 if ($id > 0) { $where[] = ['id', '=', $id]; } $skuList = OrderSku::where($where)->select(); if ($skuList->isEmpty()) { throw new RepositoryException('当前订单无可核验商品'); } $update = []; foreach ($skuList as $sku) { $arr = []; $arr['id'] = $sku['id']; $arr['check_by'] = $checkBy; if ($id > 0) { // 指定订单商品核验 if ($id == $sku['id']) { if ($sku['not_check_num'] < $num) { throw new RepositoryException('核验商品数量不足'); } if ($sku['not_check_num'] == $num) { $arr['is_check'] = OrderSku::COMMON_ON; } $arr['not_check_num'] = Db::raw('`not_check_num` - '.$num); } else { unset($arr); } } else { //订单下所有商品全部核验完 $arr['is_check'] = OrderSku::COMMON_ON; $arr['not_check_num'] = OrderSku::COMMON_OFF; } if (isset($arr)) { $update[] = $arr; } } // 更新订单商品状态 (new OrderSku())->saveAll($update); // 检测订单是否已完成所有核验 $notCheckNumList = $this->checkAllVirtual($orderCoding); $orderUpdate = []; if ($notCheckNumList['total'] == 0) { $orderUpdate['virtual_check'] = Order::COMMON_ON; } if ($notCheckNumList['frontend'] == 0) { $orderUpdate['frontend_check'] = Order::COMMON_ON; } if ($notCheckNumList['backend'] == 0) { $orderUpdate['backend_check'] = Order::COMMON_ON; } if (!empty($orderUpdate)) { $order->save($orderUpdate); } return true; } /** * 商品sku核验检测 * 检测逻辑:打开商品二维码时,待核验的数量发生变更或为0时 标识本次核验成功 * * @param string $orderCoding 订单编号 * @param int $id 订单商品ID =0则核验该订单下所有虚拟商品 >0核验具体记录 * @param int $notCheckNum 未核验的数量 * @return bool * @throws DataNotFoundException * @throws DbException * @throws ModelNotFoundException * @throws RepositoryException */ public function checkResult(string $orderCoding, int $id, int $notCheckNum): bool { if (!$order = Order::findOne(['coding' => $orderCoding])) { throw new RepositoryException('订单不存在'); } if (in_array($order['status'], [Order::STATUS_CLOSED, Order::STATUS_WAITING])) { // 订单关闭或未付款 throw new RepositoryException('当前订单状态无法核验'); } if (!$item = OrderSku::findById($id)) { throw new RepositoryException('该商品不存在'); } if ($item['coding'] != $orderCoding) { throw new RepositoryException('该商品不属于此订单'); } if ($item['not_check_num'] == 0 || $item['not_check_num'] != $notCheckNum) { return true; } return false; } /** * 获取订单虚拟商品未核验数量 * * @param string $orderCoding * @return array ['total'=所有类型未核验的数量] */ public function checkAllVirtual(string $orderCoding): array { $where = []; $where[] = ['coding', '=', $orderCoding]; $where[] = ['is_virtual', '=', Order::COMMON_ON];// 虚拟商品 $where[] = ['is_check', '=', Order::COMMON_OFF];// 未核验完成 $where[] = ['not_check_num', '>', Order::COMMON_OFF];// 未核验数量大于0 $res = []; $res['total'] = 0; $countList = OrderSku::where($where)->group('check_type')->column('count(`id`) as count', 'check_type'); foreach (Order::checkTypeList() as $type) { $res[$type] = $countList[$type] ?? 0; $res['total'] += $res[$type]; } return $res; } public function userOrderList(int $accountId, array $where = [], int $page = 1, int $size = 0, array $statusArray = []) { return Order::when(!empty($statusArray), function ($q) use ($statusArray) { $q->whereIn('status', $statusArray); })->where($where) ->when($size > 0, function ($q) use ($page, $size) { $q->page($page, $size); }) ->order('id', 'desc') ->where('account_id', $accountId) ->select(); } /** * 获取订单的拼团ID * * @param string $orderCoding * @return mixed */ public function groupId(string $orderCoding) { return OrderGroupList::where('coding', $orderCoding) ->where('is_paid', OrderGroupList::COMMON_ON) ->value('group_id', 0); } public function getOrderSku($id) { return OrderSku::findById($id); } public function getOrderOriginalPrice($orderCodeing,$skuCoding) { return OrderSku::where("order_coding",$orderCodeing)->where("coding","<>",$skuCoding)->sum("subtotal"); } }