1793 lines
65 KiB
PHP
Executable File
1793 lines
65 KiB
PHP
Executable File
<?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;
|
||
$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");
|
||
}
|
||
} |