feat: 添加购物车功能,包括添加、修改、删除购物车商品,以及购物车列表展示

feat: 购物车商品数量限制为100个,超出时无法添加更多商品
fix: 修复订单创建时支付方式验证错误
refactor: 重构订单创建逻辑,优化代码结构
style: 修改金额计算方式,保留两位小数
test: 增加购物车列表接口测试用例
docs: 更新购物车相关接口文档
build: 更新依赖包版本
This commit is contained in:
mkm 2024-06-08 13:33:17 +08:00
parent 2c93ecba28
commit 20640a2b84
8 changed files with 349 additions and 76 deletions

View File

@ -29,7 +29,7 @@ class StoreBranchProductLists extends BaseAdminDataLists implements ListsSearchI
{
return [
'=' => ['product_id', 'cate_id','store_id'],
'%like%' => ['store_name'],
'%like%' => ['store_name|bar_code'],
];
}

View File

@ -50,7 +50,10 @@ class CartValidate extends BaseValidate
{
return $this->only(['cart_num','store_id','product_id']);
}
public function sceneStoreAdd()
{
return $this->only(['cart_num','product_id']);
}
/**
* @notes 编辑场景
@ -62,7 +65,16 @@ class CartValidate extends BaseValidate
{
return $this->only(['cart_num','store_id','product_id']);
}
/**
* @notes 编辑场景
* @return CartValidate
* @author likeadmin
* @date 2024/04/24 10:37
*/
public function sceneStoreChange()
{
return $this->only(['cart_num','product_id']);
}
/**
* @notes 删除场景

View File

@ -5,12 +5,16 @@ namespace app\common\logic\store_order;
use app\common\enum\OrderEnum;
use app\common\logic\BaseLogic;
use app\common\enum\PayEnum;
use app\common\enum\YesNoEnum;
use app\common\model\dict\DictType;
use app\common\model\order\Cart;
use app\common\model\store_branch_product\StoreBranchProduct;
use app\common\model\store_order\StoreOrder;
use app\common\model\store_order_cart_info\StoreOrderCartInfo;
use app\common\model\store_product\StoreProduct;
use app\common\model\store_product_unit\StoreProductUnit;
use app\common\model\user\UserAddress;
use app\Request;
use think\facade\Db;
class StoreOrderLogic extends BaseLogic
@ -25,7 +29,7 @@ class StoreOrderLogic extends BaseLogic
static public function cartIdByOrderInfo($cartId, $addressId, $user = null, $params = [])
{
$where = ['is_pay' => 0, 'is_fail' => 0];
$where = ['is_pay' => 0];
$cart_select = Cart::whereIn('id', $cartId)->where($where)->field('id,product_id,cart_num')->select()->toArray();
if (empty($cart_select)) {
self::setError('购物车为空');
@ -35,27 +39,62 @@ class StoreOrderLogic extends BaseLogic
self::$total = 0;
/** 计算价格 */
foreach ($cart_select as $k => $v) {
$find = StoreProduct::where(['id' => $v['product_id']])->field('store_name,image,unit,price')->find();
$find = StoreBranchProduct::where(['product_id' => $v['product_id'],'store_id'=>Request()->adminInfo['store_id']])->field('store_name,image,unit,price')->find();
if (!$find) {
continue;
}
$cart_select[$k]['total'] = bcmul($v['cart_num'], $find['price'], 2);
$cart_select[$k]['total'] = bcmul($v['cart_num'], $find['price'], 2); //钱
$cart_select[$k]['price'] = $find['price'];
$cart_select[$k]['name'] = $find['store_name'];
$cart_select[$k]['image'] = $find['image'];
$cart_select[$k]['cart_id'] = $v['id'];
$cart_select[$k]['product_id'] = $v['product_id'];
$cart_select[$k]['old_cart_id'] = $v['id'];
$cart_select[$k]['cart_num'] = $v['cart_num'];
$cart_select[$k]['verify_code'] = $params['verify_code'] ?? '';
$cartInfo = $cart_select[$k];
$cartInfo['name'] = $find['store_name'];
$cartInfo['image'] = $find['image'];
//计算好vip价格
$vipPrice = false;
if ($vipPrice) {
$cartInfo['price'] = $vipPrice;
}
$cartInfo['vip_price'] = $cart_select[$k]['total'] - $vipPrice ?? 0;
$cart_select[$k]['cart_info'] = json_encode($cartInfo);
//理论上每笔都是拆分了
$cart_select[$k]['name'] = $find['store_name'];
$cart_select[$k]['imgs'] = $find['image'];
$cart_select[$k]['unit_name'] = StoreProductUnit::where(['id' => $find['unit']])->value('name');
self::$total = bcadd(self::$total, $cart_select[$k]['total'], 2);
}
//TODO 收单打9.9折 会员按照比例打折 等级按照充值去升级
$pay_price = self::$total;
// $check = StoreOrder::where('uid',\request()->userId)->count();//首单逻辑
$check = DictType::where('type', 'activities')->find();
$vipPrice = 0;
if (isset($check) && $check['status'] == 1) {
// $discountRate = '0.99';//首单逻辑
$discountRate = $check['remark'];
$discountRate = bcdiv($discountRate, '100', 2);
$pay_price = bcdiv(bcmul($pay_price, $discountRate, 4), '1', 2);
} else {
$discountRate = bcdiv(1, '100', 2);
$pay_price = bcdiv(bcmul($pay_price, $discountRate, 4), '1', 2);
}
if (!empty(self::$total) && !empty($pay_price)) {
bcscale(2);
$vipPrice = bcsub(self::$total, $pay_price, 2);
}
$order = [
'time' => time(),
'number' => getNewOrderId('PF'),
'total' => self::$total,
'create_time' => time(),
'order_id' => getNewOrderId('PF'),
'total_price' => self::$total, //总价
'pay_price' => $pay_price, //后期可能有降价抵扣
'vip_price' => $vipPrice,
'total_num' => count($cart_select), //总数
'pay_type' => $params['pay_type'] ?? 0,
'cart_id' => implode(',', $cartId),
'delivery_msg' => ' 预计48小时发货 '
'store_id' => $params['store_id'] ?? 0,
'shipping_type' => 2 //配送方式 1=快递 2=门店自提
];
} catch (\Exception $e) {
self::setError($e->getMessage());
@ -63,33 +102,25 @@ class StoreOrderLogic extends BaseLogic
}
return ['order' => $order, 'cart_list' => $cart_select];
}
/**
* 创建新订单
* @return Object|bool
* @return Object|bool|array
*/
static public function createOrder($cartId, $addressId, $user = null, $params = [])
{
$verify_code = verificationCode();
$params['verify_code'] = $verify_code;
$orderInfo = self::cartIdByOrderInfo($cartId, $addressId, $user, $params);
if (!$orderInfo) {
return false;
}
$_order = $orderInfo['order'];
$_order['deduction_price'] = 0;
$_order['store_id'] = $params['store_id'];
$_order['uid'] = request()->userId;
if ($addressId > 0) {
$address = UserAddress::where(['id' => $addressId, 'uid' => Request()->userId])->find();
if ($address) {
$_order['real_name'] = $address['real_name'];
$_order['user_phone'] = $address['phone'];
$_order['user_address'] = $address['detail'];
// $_order['address_id'] = $addressId;
}
}
if ($params['pay_type'] == PayEnum::CASH_PAY) {
$_order['money'] = $_order['total'];
$_order['pay_type'] = $orderInfo['order']['pay_type'];
$_order['verify_code'] = $verify_code;
if (isset($params['reservation_time'])) {
$_order['reservation_time'] = $params['reservation_time'];
$_order['reservation'] = YesNoEnum::YES;
}
Db::startTrans();
try {
@ -97,10 +128,11 @@ class StoreOrderLogic extends BaseLogic
$goods_list = $orderInfo['cart_list'];
foreach ($goods_list as $k => $v) {
$goods_list[$k]['oid'] = $order->id;
$goods_list[$k]['uid'] = request()->userId;
$goods_list[$k]['cart_id'] = implode(',', $cartId);
$goods_list[$k]['delivery_id'] = $params['store_id']; //商家id
}
(new StoreOrderCartInfo())->saveAll($goods_list);
$where = ['is_pay' => 0, 'is_fail' => 0];
$where = ['is_pay' => 0];
Cart::whereIn('id', $cartId)->where($where)->update(['is_pay' => 1]);
Db::commit();
return $order;

View File

@ -0,0 +1,84 @@
<?php
namespace app\store\controller\cart;
use app\api\logic\order\CartLogic;
use app\api\validate\CartValidate;
use app\store\lists\cart\CartList;
use app\common\model\order\Cart;
use app\store\controller\BaseAdminController;
use hg\apidoc\annotation as ApiDoc;
#[ApiDoc\NotParse()]
class CartController extends BaseAdminController
{
public function list()
{
return $this->dataLists(new CartList());
}
/**
* @notes 添加购物车
*/
public function create()
{
$params = (new CartValidate())->post()->goCheck('StoreAdd');
$adminInfo = $this->adminInfo;
$params['uid'] = 0;
$params['staff_id'] = $adminInfo['admin_id'];
$params['store_id'] = $adminInfo['store_id'];
$result = Cart::where(['uid' => 0,'staff_id'=>$adminInfo['admin_id'], 'store_id' => $adminInfo['store_id'], 'product_id' => $params['product_id'], 'is_fail' => 0, 'is_pay' => 0])->find();
$count = Cart::where(['uid' => $params['uid'], 'is_pay' => 0])->count();
if ($count > 100) {
return $this->fail('购物车商品不能大于100个请先结算');
}
if ($result) {
$res = CartLogic::edit($params);
} else {
$res = CartLogic::add($params);
}
if ($res) {
return $this->success('添加成功');
} else {
return $this->fail(CartLogic::getError());
}
}
/**
* @notes 修改购物车
*/
public function change()
{
$params = (new CartValidate())->post()->goCheck('StoreChange');
$adminInfo = $this->adminInfo;
$params['uid'] = 0;
$params['staff_id'] = $adminInfo['admin_id'];
$params['store_id'] = $adminInfo['store_id'];
$res = CartLogic::edit($params, 'dec');
if ($res) {
return $this->success('修改成功');
} else {
return $this->fail(CartLogic::getError());
}
}
/**
* @notes 删除购物车
*/
public function delete()
{
$params = (new CartValidate())->post()->goCheck('delete');
$adminInfo = $this->adminInfo;
$params['uid'] = 0;
$params['staff_id'] = $adminInfo['admin_id'];
$params['store_id'] = $adminInfo['store_id'];
$res = CartLogic::delete($params);
if ($res) {
return $this->success('删除成功');
} else {
return $this->fail(CartLogic::getError());
}
}
}

View File

@ -3,15 +3,17 @@
namespace app\store\controller\store_order;
use app\admin\lists\store_order\StoreOrderLists;
use app\store\lists\store_order\StoreOrderLists;
use app\common\controller\Definitions;
use app\common\enum\PayEnum;
use app\common\logic\PaymentLogic;
use app\common\logic\PayNotifyLogic;
use app\common\logic\SystemStoreStaffLogic;
use app\store\controller\BaseAdminController;
use app\common\logic\store_order\StoreOrderLogic;
use app\store\validate\store_order\StoreOrderValidate;
use hg\apidoc\annotation as ApiDoc;
use Webman\RedisQueue\Redis;
/**
* 订单列表控制器
@ -85,44 +87,6 @@ class StoreOrderController extends BaseAdminController
]);
}
#[
ApiDoc\Title('添加订单'),
ApiDoc\url('/store/store_order/storeOrder/add'),
ApiDoc\Method('POST'),
ApiDoc\NotHeaders(),
ApiDoc\Author('中国队长'),
ApiDoc\Header(ref: [Definitions::class, "token"]),
ApiDoc\ResponseSuccess("data", type: "array"),
]
public function add()
{
$params = (new StoreOrderValidate())->post()->goCheck('add');
$result = StoreOrderLogic::add($params);
if (true === $result) {
return $this->success('添加成功', [], 1, 1);
}
return $this->fail(StoreOrderLogic::getError());
}
#[
ApiDoc\Title('编辑订单'),
ApiDoc\url('/store/store_order/storeOrder/edit'),
ApiDoc\Method('POST'),
ApiDoc\NotHeaders(),
ApiDoc\Author('中国队长'),
ApiDoc\Header(ref: [Definitions::class, "token"]),
ApiDoc\ResponseSuccess("data", type: "array"),
]
public function edit()
{
$params = (new StoreOrderValidate())->post()->goCheck('edit');
$result = StoreOrderLogic::edit($params);
if (true === $result) {
return $this->success('编辑成功', [], 1, 1);
}
return $this->fail(StoreOrderLogic::getError());
}
#[
ApiDoc\Title('订单详情'),
ApiDoc\url('/store/store_order/storeOrder/detail'),
@ -140,5 +104,73 @@ class StoreOrderController extends BaseAdminController
return $this->data($result);
}
public function checkOrder()
{
$cartId = (array)$this->request->post('cart_id', []);
$params = $this->request->post();
$res = StoreOrderLogic::cartIdByOrderInfo($cartId, null, null, $params);
if ($res == false) {
$msg = StoreOrderLogic::getError();
if ($msg == '购物车为空') {
return $this->data([]);
}
return $this->fail(StoreOrderLogic::getError());
}
return $this->data($res);
}
public function createOrder()
{
$cartId = (array)$this->request->post('cart_id', []);
$pay_type = (int)$this->request->post('pay_type');
$addressId = (int)$this->request->post('address_id');
$auth_code = $this->request->post('auth_code'); //微信支付条码
$params = $this->request->post();
if ($auth_code==''&&$pay_type!=PayEnum::CASH_PAY) {
return $this->fail('支付条码不能为空');
}
if (count($cartId) > 100) {
return $this->fail('购物车商品不能超过100个');
}
$params['store_id'] = $this->request->adminInfo['store_id']; //当前登录的店铺id用于判断是否是当前店铺的订单
$order = StoreOrderLogic::createOrder($cartId, $addressId, null, $params);
if ($order != false) {
switch ($pay_type) {
case PayEnum::CASH_PAY:
//现金支付
PayNotifyLogic::handle('cash_pay', $order['order_id']);
return $this->success('现金支付成功');
case PayEnum::WECHAT_PAY_BARCODE:
//微信条码支付
$result = PaymentLogic::codepay($auth_code, $order);
if (PaymentLogic::hasError()) {
return $this->fail(PaymentLogic::getError(), $params);
}
if (isset($result['trade_state_desc']) && $result['trade_state_desc'] == '支付成功') {
PayNotifyLogic::handle('StoreOrder', $result['out_trade_no'], $result);
} else {
Redis::send('send-code-pay', ['number' => $order['number']]);
return $this->success('用户支付中');
}
return $this->success('支付成功', ['out_trade_no'=>$result['out_trade_no'],'pay_type'=>PayEnum::WECHAT_PAY_BARCODE,'transaction_id'=>$result['transaction_id']]);
case PayEnum::ALIPAY_BARCODE:
//支付宝条码支付
$result = PaymentLogic::ali_auth_code($auth_code, $order);
if (PaymentLogic::hasError()) {
return $this->fail(PaymentLogic::getError(), $params);
}
if ($result['msg'] !== 'Success') {
return $this->success('用户支付中');
}
$result['create_time'] = $order['create_time'];
return $this->success('支付成功', ['out_trade_no'=>$result['out_trade_no'],'pay_type'=>PayEnum::ALIPAY_BARCODE,'transaction_id'=>$result['trade_no']]);
default:
return $this->fail('支付方式错误');
}
} else {
return $this->fail(StoreOrderLogic::getError());
}
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace app\store\lists\cart;
use app\admin\lists\BaseAdminDataLists;
use app\common\lists\ListsSearchInterface;
use app\common\model\order\Cart;
use app\common\lists\ListsExtendInterface;
use app\common\model\store_branch_product\StoreBranchProduct;
use app\common\model\store_product_attr_value\StoreProductAttrValue;
use app\common\model\store_product_unit\StoreProductUnit;
/**
* 购物车列表
* Class RetailOrderList
* @package app\store\cart
*/
class CartList extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
{
protected $total_price = 0;
/**
* @notes 设置搜索条件
* @return \string[][]
* @author likeadmin
*/
public function setSearch(): array
{
return [];
}
/**
* @notes 购物车列表
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @date 2024/04/27 11:26
*/
public function lists($where = []): array
{
$this->searchWhere[]=['staff_id'=>$this->adminId];
$this->searchWhere[]=['is_pay'=>0];
$list = Cart::where($this->searchWhere)
->field('id,product_id,cart_num,store_id')
->limit($this->limitOffset, $this->limitLength)
->order(['id' => 'desc'])
->select()->each(function ($item) {
$find = StoreBranchProduct::where(['product_id' => $item['product_id'],'store_id'=>$item['store_id']])
->field('product_id,image,price,store_name,unit')
->find();
if ($find) {
$item['total_price'] = bcmul($item['cart_num'], $find['price'], 2);
$item['image'] = $find['image'];
$item['price'] = $find['price'];
$item['store_name'] = $find['store_name'];
$item['unit_name'] = StoreProductUnit::where('id', $find['unit'])->value('name');
}
return $item;
})
->toArray();
return $list;
}
/**
* @notes 购物车数量
* @return int
* @date 2024/04/27 11:26
*/
public function count(): int
{
return Cart::where($this->searchWhere)->count();
}
public function extend()
{
// return ['total_price' => $this->total_price];
}
}

View File

@ -2,11 +2,12 @@
namespace app\store\lists\store_order;
use app\common\enum\OrderEnum;
use app\store\lists\BaseAdminDataLists;
use app\common\model\store_order\StoreOrder;
use app\common\lists\ListsSearchInterface;
use app\common\model\store_branch_product\StoreBranchProduct;
use app\common\model\store_order_cart_info\StoreOrderCartInfo;
/**
* 订单列表列表
@ -26,7 +27,9 @@ class StoreOrderLists extends BaseAdminDataLists implements ListsSearchInterface
public function setSearch(): array
{
return [
'=' => ['order_id', 'pay_type'],
'=' => ['pay_type'],
'%like%' => ['order_id'],
'between_time' => 'create_time',
];
}
@ -42,11 +45,36 @@ class StoreOrderLists extends BaseAdminDataLists implements ListsSearchInterface
*/
public function lists(): array
{
$store_id = $this->adminInfo['store_id'];
$this->searchWhere[] = ['store_id' ,'=',$store_id];
$is_sashier=$this->request->get('is_sashier');
if($is_sashier==1){//收银台订单
$this->searchWhere[] = ['pay_type','in',[17,9,13]];
}elseif($is_sashier==2){//小程序订单
$this->searchWhere[] = ['pay_type','=',7];
}
return StoreOrder::where($this->searchWhere)
->field(['id', 'order_id', 'pay_price', 'pay_time', 'pay_type', 'status'])
->limit($this->limitOffset, $this->limitLength)
->order(['id' => 'desc'])
->select()
->select()->each(function ($item) use ($store_id) {
$item['pay_time'] = $item['pay_time'] > 0 ? date('Y-m-d H:i:s', $item['pay_time']) : '';
$item['status_name'] = OrderEnum::getOrderType($item['status']) ?? '';
if ($item['paid'] == 0) {
$item['status_name'] = '待支付';
}
$product_id = StoreOrderCartInfo::where('oid', $item['id'])->limit(3)->column('product_id');
if ($product_id) {
$item['product_info'] = StoreBranchProduct::whereIn('product_id' ,$product_id)->where('store_id', $store_id)->field(['product_id', 'store_name', 'image', 'price'])
->select();
} else {
$item['product_info'] = [];
}
return $item;
})
->toArray();
}
@ -61,5 +89,4 @@ class StoreOrderLists extends BaseAdminDataLists implements ListsSearchInterface
{
return StoreOrder::where($this->searchWhere)->count();
}
}

View File

@ -60,6 +60,7 @@ class LoginLogic extends BaseLogic
'avatar' => $avatar,
'role_name' => $adminInfo['role_name'],
'token' => $adminInfo['token'],
'store_id' => $admin['store_id'],
];
}