1791 lines
		
	
	
		
			65 KiB
		
	
	
	
		
			PHP
		
	
	
			
		
		
	
	
			1791 lines
		
	
	
		
			65 KiB
		
	
	
	
		
			PHP
		
	
	
| <?php
 | ||
| 
 | ||
| namespace app\repository;
 | ||
| 
 | ||
| use app\exception\RepositoryException;
 | ||
| use app\model\Account;
 | ||
| use app\model\AccountAddress;
 | ||
| use app\model\AccountCoupon;
 | ||
| use app\model\AccountDataLog;
 | ||
| use app\model\Express;
 | ||
| use app\model\Order;
 | ||
| use app\model\OrderActivity;
 | ||
| use app\model\OrderAfterSale;
 | ||
| use app\model\OrderGroupList;
 | ||
| use app\model\OrderSku;
 | ||
| use app\model\PaymentLog;
 | ||
| use app\model\ShoppingCart;
 | ||
| use app\model\Sku;
 | ||
| use app\model\Spu;
 | ||
| use app\model\SpuActivity;
 | ||
| use app\service\Kd100;
 | ||
| use app\service\Math;
 | ||
| use app\service\Repository;
 | ||
| use app\service\wx\WechatPay;
 | ||
| use app\traits\order\ActivityTrait;
 | ||
| use app\traits\order\AfterSaleTrait;
 | ||
| use app\traits\order\ExpressLogTrait;
 | ||
| use app\traits\order\ExpressTrait;
 | ||
| use app\traits\order\ShoppingCartTrait;
 | ||
| use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
 | ||
| use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
 | ||
| use Exception;
 | ||
| use GuzzleHttp\Exception\GuzzleException;
 | ||
| use Psr\Http\Message\ResponseInterface;
 | ||
| use think\Collection;
 | ||
| use think\db\exception\DataNotFoundException;
 | ||
| use think\db\exception\DbException;
 | ||
| use think\db\exception\ModelNotFoundException;
 | ||
| use think\facade\Config as CConfig;
 | ||
| use think\facade\Db;
 | ||
| use think\facade\Log;
 | ||
| use think\Model;
 | ||
| use function EasyWeChat\Kernel\Support\generate_sign;
 | ||
| 
 | ||
| /**
 | ||
|  * 订单域 相关操作
 | ||
|  *
 | ||
|  * Class OrderRepository
 | ||
|  * @package app\repository
 | ||
|  * @method self getInstance(Model $model = null) static
 | ||
|  */
 | ||
| class OrderRepository extends Repository
 | ||
| {
 | ||
|     use ShoppingCartTrait;
 | ||
|     use AfterSaleTrait;
 | ||
|     use ExpressTrait;
 | ||
|     use ExpressLogTrait;
 | ||
|     use ActivityTrait;
 | ||
| 
 | ||
|     /** 订单状态 **/
 | ||
|     public const STATUS_ORDER_PLACED        = Order::STATUS_ORDER_PLACED;//已下单  (已付款待发货)
 | ||
|     public const STATUS_MAKEING             = Order::STATUS_MAKEING;//制作中
 | ||
|     public const STATUS_SHIPPED             = Order::STATUS_SHIPPED;//已发货
 | ||
|     public const STATUS_ARRIVED             = Order::STATUS_ARRIVED;//已送达
 | ||
|     public const STATUS_COMPLETED           = Order::STATUS_COMPLETED;//已完成 确认收获 或者 到期自动确认
 | ||
|     public const STATUS_CANCEL              = Order::STATUS_CANCEL;//已取消
 | ||
| 
 | ||
|     public const IS_EVALUATE_YES = 1; // is_evaluate 已评价(订单中的已评价指全部评价完)
 | ||
|     public const IS_EVALUATE_NO  = 0; // is_evaluate 未评价(订单中的已评价指全部评价完)
 | ||
| 
 | ||
|     /** 订单状态文本描述 **/
 | ||
|     public static function orderStatusTextList(): array
 | ||
|     {
 | ||
|         return [
 | ||
|             self::STATUS_ORDER_PLACED   => '已下单',
 | ||
|             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;
 | ||
|                 $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 ($order['status'] != self::STATUS_SHIPPED) {
 | ||
|                 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");
 | ||
|     }
 | ||
| } |