<?php
namespace app\api\service\sharing\order;
use app\api\model\sharing\Order as OrderModel;
use app\api\model\User as UserModel;
use app\api\model\Setting as SettingModel;
use app\api\model\sharing\Goods as GoodsModel;
use app\api\model\sharing\GoodsSku as GoodsSkuModel;
use app\api\model\sharing\Active as ActiveModel;
use app\api\model\sharing\Setting as SharingSettingModel;
use app\api\model\store\Shop as ShopModel;
use app\api\model\UserCoupon as UserCouponModel;
use app\api\model\dealer\Order as DealerOrderModel;
use app\api\service\User as UserService;
use app\api\service\Payment as PaymentService;
use app\api\service\coupon\GoodsDeduct as GoodsDeductService;
use app\api\service\points\GoodsDeduct as PointsDeductService;
use app\common\library\helper;
use app\common\enum\OrderType as OrderTypeEnum;
use app\common\enum\order\PayType as PayTypeEnum;
use app\common\enum\DeliveryType as DeliveryTypeEnum;
use app\common\enum\order\OrderSource as OrderSourceEnum;
use app\common\service\delivery\Express as ExpressService;
use app\common\exception\BaseException;
class Checkout
{
public $model;
private $wxapp_id;
private $user;
private $goodsList = [];
protected $error;
private $param = [
'active_id' => 0, 'delivery' => null, 'shop_id' => 0, 'linkman' => '', 'phone' => '', 'coupon_id' => 0, 'is_use_points' => 0, 'remark' => '', 'pay_type' => PayTypeEnum::WECHAT, 'order_type' => 20, ];
private $checkoutRule = [
'is_user_grade' => true, 'is_coupon' => true, 'is_use_points' => true, 'is_dealer' => true, ];
private $orderSource = [
'source' => OrderSourceEnum::MASTER,
'source_id' => 0,
];
private $orderData = [];
public function __construct()
{
$this->model = new OrderModel;
$this->wxapp_id = OrderModel::$wxapp_id;
}
public function setParam($param)
{
$this->param = array_merge($this->param, $param);
return $this->getParam();
}
public function getParam()
{
return $this->param;
}
public function setCheckoutRule($data)
{
$this->checkoutRule = array_merge($this->checkoutRule, $data);
}
public function setOrderSource($data)
{
$this->orderSource = array_merge($this->orderSource, $data);
}
public function onCheckout($user, $goodsList)
{
$this->user = $user;
$this->goodsList = $goodsList;
return $this->checkout();
}
private function checkout()
{
$this->orderData = $this->getOrderData();
$this->validateGoodsList();
$orderTotalNum = helper::getArrayColumnSum($this->goodsList, 'total_num');
$this->setOrderGoodsGradeMoney();
$this->setOrderTotalPrice();
$this->setOrderPoints();
$couponList = $this->getUserCouponList($this->orderData['order_total_price']);
$this->setOrderCouponMoney($couponList, $this->param['coupon_id']);
$this->setOrderGoodsPayPrice();
!$this->param['delivery'] && $this->param['delivery'] = current(SettingModel::getItem('store')['delivery_type']);
if ($this->param['delivery'] == DeliveryTypeEnum::EXPRESS) {
$this->setOrderExpress();
} elseif ($this->param['delivery'] == DeliveryTypeEnum::EXTRACT) {
$this->param['shop_id'] > 0 && $this->orderData['extract_shop'] = ShopModel::detail($this->param['shop_id']);
}
$this->setOrderPayPrice();
$this->setOrderPointsBonus();
return array_merge([
'goods_list' => array_values($this->goodsList), 'order_total_num' => $orderTotalNum, 'coupon_list' => array_values($couponList), 'has_error' => $this->hasError(),
'error_msg' => $this->getError(),
], $this->orderData);
}
private function setOrderPoints()
{
$this->setDefaultGoodsPoints();
$setting = SettingModel::getItem('points');
if (!$setting['is_shopping_discount'] || !$this->checkoutRule['is_use_points']) {
return false;
}
if (helper::bccomp($setting['discount']['full_order_price'], $this->orderData['order_total_price']) === 1) {
return false;
}
$this->setOrderGoodsMaxPointsNum();
$maxPointsNumCount = helper::getArrayColumnSum($this->goodsList, 'max_points_num');
$actualPointsNum = min($maxPointsNumCount, $this->user['points']);
if ($actualPointsNum < 1) {
return false;
}
$GoodsDeduct = new PointsDeductService($this->goodsList);
$GoodsDeduct->setGoodsPoints($maxPointsNumCount, $actualPointsNum);
$orderPointsMoney = helper::getArrayColumnSum($this->goodsList, 'points_money');
$this->orderData['points_money'] = helper::number2($orderPointsMoney);
$this->orderData['points_num'] = $actualPointsNum;
$this->orderData['is_allow_points'] = true;
return true;
}
private function setOrderGoodsMaxPointsNum()
{
$setting = SettingModel::getItem('points');
foreach ($this->goodsList as &$goods) {
if (!$goods['is_points_discount']) continue;
$deductionRatio = helper::bcdiv($setting['discount']['max_money_ratio'], 100);
$maxPointsMoney = helper::bcmul($goods['total_price'], $deductionRatio);
$goods['max_points_num'] = helper::bcdiv($maxPointsMoney, $setting['discount']['discount_ratio'], 0);
}
return true;
}
private function setDefaultGoodsPoints()
{
foreach ($this->goodsList as &$goods) {
$goods['max_points_num'] = 0;
$goods['points_num'] = 0;
$goods['points_money'] = 0.00;
}
return true;
}
private function getOrderData()
{
$deliveryType = SettingModel::getItem('store')['delivery_type'];
$pointsSetting = SettingModel::getItem('points');
return [
'order_type' => $this->param['order_type'],
'delivery' => $this->param['delivery'] > 0 ? $this->param['delivery'] : $deliveryType[0],
'address' => $this->user['address_default'],
'exist_address' => $this->user['address_id'] > 0,
'express_price' => 0.00,
'intra_region' => true,
'extract_shop' => [],
'is_allow_points' => false,
'is_use_points' => $this->param['is_use_points'],
'points_money' => 0.00,
'points_bonus' => 0,
'pay_type' => $this->param['pay_type'],
'setting' => [
'delivery' => $deliveryType, 'points_name' => $pointsSetting['points_name'], 'points_describe' => $pointsSetting['describe'], ],
'last_extract' => UserService::getLastExtract($this->user['user_id']),
'deliverySetting' => $deliveryType,
];
}
private function getUserCouponList($orderTotalPrice)
{
if (!$this->checkoutRule['is_coupon'] && SharingSettingModel::getItem('basic')['is_coupon']) {
return [];
}
return UserCouponModel::getUserCouponList($this->user['user_id'], $orderTotalPrice);
}
private function validateGoodsList()
{
foreach ($this->goodsList as $goods) {
if ($goods['goods_status']['value'] != 10) {
$this->setError("很抱歉,商品 [{$goods['goods_name']}] 已下架");
}
if ($goods['total_num'] > $goods['goods_sku']['stock_num']) {
$this->setError("很抱歉,商品 [{$goods['goods_name']}] 库存不足");
}
}
}
private function setOrderTotalPrice()
{
$this->orderData['order_total_price'] = helper::number2(helper::getArrayColumnSum($this->goodsList, 'total_price'));
}
private function setOrderPayPrice()
{
$this->orderData['order_price'] = helper::number2(helper::getArrayColumnSum($this->goodsList, 'total_pay_price'));
$this->orderData['order_pay_price'] = helper::number2(helper::bcadd($this->orderData['order_price'], $this->orderData['express_price']));
}
private function setOrderPointsBonus()
{
foreach ($this->goodsList as &$goods) {
$goods['points_bonus'] = 0;
}
$setting = SettingModel::getItem('points');
if (!$setting['is_shopping_gift']) {
return false;
}
foreach ($this->goodsList as &$goods) {
$ratio = $setting['gift_ratio'] / 100;
$goods['points_bonus'] = $goods['is_points_gift'] ? helper::bcmul($goods['total_pay_price'], $ratio, 0) : 0;
}
$this->orderData['points_bonus'] = helper::getArrayColumnSum($this->goodsList, 'points_bonus');
return true;
}
private function setOrderGoodsPayPrice()
{
foreach ($this->goodsList as &$goods) {
$value = helper::bcsub($goods['total_price'], $goods['coupon_money']);
if ($this->orderData['is_allow_points'] && $this->orderData['is_use_points']) {
$value = helper::bcsub($value, $goods['points_money']);
}
$goods['total_pay_price'] = helper::number2($value);
}
return true;
}
private function setOrderGoodsGradeMoney()
{
helper::setDataAttribute($this->goodsList, [
'is_user_grade' => false,
'grade_ratio' => 0,
'grade_goods_price' => 0.00,
'grade_total_money' => 0.00,
], true);
if (!$this->checkoutRule['is_user_grade']) {
return false;
}
if (!(
$this->user['grade_id'] > 0 && !empty($this->user['grade'])
&& !$this->user['grade']['is_delete'] && $this->user['grade']['status']
)) {
return false;
}
foreach ($this->goodsList as &$goods) {
if (!$goods['is_enable_grade']) {
continue;
}
if ($goods['is_alone_grade'] && isset($goods['alone_grade_equity'][$this->user['grade_id']])) {
$discountRatio = helper::bcdiv($goods['alone_grade_equity'][$this->user['grade_id']], 10);
} else {
$discountRatio = helper::bcdiv($this->user['grade']['equity']['discount'], 10);
}
if ($discountRatio > 0) {
$gradeTotalPrice = max(0.01, helper::bcmul($goods['total_price'], $discountRatio));
helper::setDataAttribute($goods, [
'is_user_grade' => true,
'grade_ratio' => $discountRatio,
'grade_goods_price' => helper::number2(helper::bcmul($goods['goods_price'], $discountRatio), true),
'grade_total_money' => helper::number2(helper::bcsub($goods['total_price'], $gradeTotalPrice)),
'total_price' => $gradeTotalPrice,
], false);
}
}
return true;
}
private function setOrderCouponMoney($couponList, $couponId)
{
helper::setDataAttribute($this->orderData, [
'coupon_id' => 0, 'coupon_money' => 0, ], false);
helper::setDataAttribute($this->goodsList, [
'coupon_money' => 0, ], true);
if (!$this->checkoutRule['is_coupon']) {
return false;
}
if ($couponId <= 0 || empty($couponList)) {
return true;
}
$couponInfo = helper::getArrayItemByColumn($couponList, 'user_coupon_id', $couponId);
if ($couponInfo == false) {
throw new BaseException(['msg' => '未找到优惠券信息']);
}
$goodsListTemp = helper::getArrayColumns($this->goodsList, ['total_price']);
$CouponMoney = new GoodsDeductService;
$completed = $CouponMoney->setGoodsCouponMoney($goodsListTemp, $couponInfo['reduced_price']);
foreach ($this->goodsList as $key => &$goods) {
$goods['coupon_money'] = $completed[$key]['coupon_money'] / 100;
}
$this->orderData['coupon_id'] = $couponId;
$this->orderData['coupon_money'] = helper::number2($CouponMoney->getActualReducedMoney() / 100);
return true;
}
private function setOrderExpress()
{
helper::setDataAttribute($this->goodsList, [
'express_price' => 0,
], true);
$cityId = $this->user['address_default'] ? $this->user['address_default']['city_id'] : null;
$ExpressService = new ExpressService(
$this->wxapp_id,
$cityId,
$this->goodsList,
OrderTypeEnum::SHARING
);
$notInRuleGoods = $ExpressService->getNotInRuleGoods();
$intraRegion = $this->orderData['intra_region'] = $notInRuleGoods === false;
if ($intraRegion == false) {
$notInRuleGoodsName = $notInRuleGoods['goods_name'];
$this->setError("很抱歉,您的收货地址不在商品 [{$notInRuleGoodsName}] 的配送范围内");
} else {
$ExpressService->setExpressPrice();
}
$this->orderData['express_price'] = helper::number2($ExpressService->getTotalFreight());
return true;
}
public function createOrder($order)
{
$order['active_id'] = $this->param['active_id'];
if (!$this->validateOrderForm($order, $this->param['linkman'], $this->param['phone'])) {
return false;
}
$status = $this->model->transaction(function () use ($order) {
return $this->createOrderEvent($order);
});
if ($status && $order['pay_type'] == PayTypeEnum::BALANCE) {
return $this->model->onPaymentByBalance($this->model['order_no']);
}
return $status;
}
private function createOrderEvent($order)
{
$status = $this->add($order, $this->param['remark']);
if ($order['delivery'] == DeliveryTypeEnum::EXPRESS) {
$this->saveOrderAddress($order['address']);
} elseif ($order['delivery'] == DeliveryTypeEnum::EXTRACT) {
$this->saveOrderExtract($this->param['linkman'], $this->param['phone']);
}
$this->saveOrderGoods($order);
$this->updateGoodsStockNum($order['goods_list']);
UserCouponModel::setIsUse($this->param['coupon_id']);
if ($order['is_allow_points'] && $order['is_use_points'] && $order['points_num'] > 0) {
$describe = "用户消费:{$this->model['order_no']}";
$this->user->setIncPoints(-$order['points_num'], $describe);
}
$detail = OrderModel::getUserOrderDetail($this->model['order_id'], $this->user['user_id']);
if ($this->checkoutRule['is_dealer'] && SharingSettingModel::getItem('basic')['is_dealer']) {
DealerOrderModel::createOrder($detail, OrderTypeEnum::SHARING);
}
return $status;
}
public function onOrderPayment()
{
return PaymentService::orderPayment($this->user, $this->model, $this->param['pay_type']);
}
private function validateOrderForm(&$order, $linkman, $phone)
{
if ($order['delivery'] == DeliveryTypeEnum::EXPRESS) {
if (empty($order['address'])) {
$this->error = '请先选择收货地址';
return false;
}
}
if ($order['delivery'] == DeliveryTypeEnum::EXTRACT) {
if (empty($order['extract_shop'])) {
$this->error = '请先选择自提门店';
return false;
}
if (empty($linkman) || empty($phone)) {
$this->error = '请填写联系人和电话';
return false;
}
}
if ($order['pay_type'] == PayTypeEnum::BALANCE) {
if ($this->user['balance'] < $order['order_pay_price']) {
$this->error = '用户余额不足,无法使用余额支付';
return false;
}
}
if ($order['active_id'] > 0) {
$detail = ActiveModel::detail($order['active_id']);
if (empty($detail)) {
$this->error = '很抱歉,拼单不存在';
return false;
}
if (!$detail->checkAllowJoin()) {
$this->error = $detail->getError();
return false;
}
}
return true;
}
private function isExistPointsDeduction($order)
{
return $order['is_allow_points'] && $order['is_use_points'];
}
private function add(&$order, $remark = '')
{
$isExistPointsDeduction = $this->isExistPointsDeduction($order);
$data = [
'user_id' => $this->user['user_id'],
'order_type' => $order['order_type'],
'active_id' => $order['active_id'],
'order_no' => $this->model->orderNo(),
'total_price' => $order['order_total_price'],
'order_price' => $order['order_price'],
'coupon_id' => $order['coupon_id'],
'coupon_money' => $order['coupon_money'],
'points_money' => $isExistPointsDeduction ? $order['points_money'] : 0.00,
'points_num' => $isExistPointsDeduction ? $order['points_num'] : 0,
'pay_price' => $order['order_pay_price'],
'delivery_type' => $order['delivery'],
'pay_type' => $order['pay_type'],
'buyer_remark' => trim($remark),
'points_bonus' => $order['points_bonus'],
'wxapp_id' => $this->wxapp_id,
];
if ($order['delivery'] == DeliveryTypeEnum::EXPRESS) {
$data['express_price'] = $order['express_price'];
} elseif ($order['delivery'] == DeliveryTypeEnum::EXTRACT) {
$data['extract_shop_id'] = $order['extract_shop']['shop_id'];
}
return $this->model->save($data);
}
private function saveOrderGoods(&$order)
{
$isExistPointsDeduction = $this->isExistPointsDeduction($order);
$goodsList = [];
foreach ($order['goods_list'] as $goods) {
$goodsList[] = [
'user_id' => $this->user['user_id'],
'wxapp_id' => $this->wxapp_id,
'goods_id' => $goods['goods_id'],
'goods_name' => $goods['goods_name'],
'image_id' => $goods['image'][0]['image_id'],
'people' => $goods['people'],
'group_time' => $goods['group_time'],
'is_alone' => $goods['is_alone'],
'deduct_stock_type' => $goods['deduct_stock_type'],
'spec_type' => $goods['spec_type'],
'spec_sku_id' => $goods['goods_sku']['spec_sku_id'],
'goods_sku_id' => $goods['goods_sku']['goods_sku_id'],
'goods_attr' => $goods['goods_sku']['goods_attr'],
'content' => $goods['content'],
'goods_no' => $goods['goods_sku']['goods_no'],
'goods_price' => $goods['goods_sku']['goods_price'],
'line_price' => $goods['goods_sku']['line_price'],
'goods_weight' => $goods['goods_sku']['goods_weight'],
'is_user_grade' => (int)$goods['is_user_grade'],
'grade_ratio' => $goods['grade_ratio'],
'grade_goods_price' => $goods['grade_goods_price'],
'grade_total_money' => $goods['grade_total_money'],
'coupon_money' => $goods['coupon_money'],
'points_money' => $isExistPointsDeduction ? $goods['points_money'] : 0.00,
'points_num' => $isExistPointsDeduction ? $goods['points_num'] : 0,
'points_bonus' => $goods['points_bonus'],
'total_num' => $goods['total_num'],
'total_price' => $goods['total_price'],
'total_pay_price' => $goods['total_pay_price'],
'is_ind_dealer' => $goods['is_ind_dealer'],
'dealer_money_type' => $goods['dealer_money_type'],
'first_money' => $goods['first_money'],
'second_money' => $goods['second_money'],
'third_money' => $goods['third_money'],
];
}
return $this->model->goods()->saveAll($goodsList);
}
private function updateGoodsStockNum($goods_list)
{
$deductStockData = [];
foreach ($goods_list as $goods) {
$goods['deduct_stock_type'] == 10 && $deductStockData[] = [
'goods_sku_id' => $goods['goods_sku']['goods_sku_id'],
'stock_num' => ['dec', $goods['total_num']]
];
}
!empty($deductStockData) && (new GoodsSkuModel)->isUpdate()->saveAll($deductStockData);
}
private function saveOrderAddress($address)
{
if ($address['region_id'] == 0 && !empty($address['district'])) {
$address['detail'] = $address['district'] . ' ' . $address['detail'];
}
return $this->model->address()->save([
'user_id' => $this->user['user_id'],
'wxapp_id' => $this->wxapp_id,
'name' => $address['name'],
'phone' => $address['phone'],
'province_id' => $address['province_id'],
'city_id' => $address['city_id'],
'region_id' => $address['region_id'],
'detail' => $address['detail'],
]);
}
private function saveOrderExtract($linkman, $phone)
{
UserService::setLastExtract($this->model['user_id'], trim($linkman), trim($phone));
return $this->model->extract()->save([
'linkman' => trim($linkman),
'phone' => trim($phone),
'user_id' => $this->model['user_id'],
'wxapp_id' => $this->wxapp_id,
]);
}
protected function setError($error)
{
empty($this->error) && $this->error = $error;
}
public function getError()
{
return $this->error;
}
public function hasError()
{
return !empty($this->error);
}
}