* @day: 2017/12/20 */ namespace app\models\store; use crmeb\basic\BaseModel; use think\facade\Cache; use crmeb\traits\ModelTrait; use app\models\system\SystemStore; use app\models\user\{ User, UserAddress, UserBill, WechatUser }; use crmeb\repositories\{ GoodsRepository, PaymentRepositories, OrderRepository, ShortLetterRepositories, UserRepository }; use EasyWeChat\Store\Store; /** * 正常情況, 一次性下單購物車中多個商品,只產生一個訂單。 支付,積分,發貨,退貨,等等都是基於這一個訂單操作。 * 一個明顯的問題是:當此訂單中多個商品不能在一個快遞中發貨時,訂單就變得不易管理 * * 這個類的目的就是。已最小的侵入性來實現:一個訂單中包含多個商品時,自動按照一定的邏輯把這個訂單拆分爲若幹個訂單 * 每個訂單中的商品可在同一個快遞中發貨。 * * 有點類似一個電商平臺多個商家的情況,肯定是各個商家派發購物車中屬於自己家的商品。 * * 實現: * * 單獨建一個表,此表只記錄一個大訂單號(用於支付接口傳遞訂單參數)和關聯的所有子訂單 * 支付接口按大訂單號走,發貨等訂單管理按子訂單走。 * 這樣有一個問題:子訂單退貨沒法執行三方支付退款(暫定) */ class StoreOrderBatch extends BaseModel { /** * 数据表主键 * @var string */ protected $pk = 'id'; /** * 模型名称 * @var string */ protected $name = 'store_order_batch'; use ModelTrait; /** * 获取订单各项金额 * */ public static function getOrderPriceGroup($cartInfo, $addr) { return StoreOrder::getOrderPriceGroup($cartInfo, $addr); } /** * 缓存订单信息 * @param $uid * @param $cartInfo * @param $priceGroup * @param array $other * @param int $cacheTime * @return string 总订单ID * @throws \Psr\SimpleCache\InvalidArgumentException */ public static function cacheOrderInfo($uid, $cartInfo, $priceGroup, $other = [], $cacheTime = 600) { $key = md5(time() . $uid); Cache::set('user_order_' . $uid . $key, compact('cartInfo', 'priceGroup', 'other'), $cacheTime); return $key; } /** * 获取订单缓存信息 * @param $uid * @param $key * @return mixed|null * @throws \Psr\SimpleCache\InvalidArgumentException */ public static function getCacheOrderInfo($uid, $key) { $cacheName = 'user_order_' . $uid . $key; if (!Cache::has($cacheName)) return null; return Cache::get($cacheName); } /** * 生成总订单唯一id * @param $uid 用户uid * @return string */ public static function getNewPOrderId() { $prefix = 'wxcn'; list($msec, $sec) = explode(' ', microtime()); $msectime = number_format((floatval($msec) + floatval($sec)) * 1000, 0, '', ''); $porderId = $prefix . $msectime . mt_rand(10000, 99999); if (self::be(['porder_id' => $porderId])) { $porderId = $prefix . $msectime . mt_rand(10000, 99999); } return $porderId; } /** * 获取所有子订单 * @param string $porderId: porder_id */ public static function getAllSubOrders(string $porderId): Array { $subOrders = self::alias('b')->join('StoreOrder o', 'o.id=b.oid') ->where('b.porder_id', $porderId)->select()->toArray(); foreach($subOrders as &$subOrder) { $subOrder['cart_id'] = json_decode($subOrder['cart_id'], true); } return $subOrders; } /** * 字段累加 * @param Array $subOrders * @param Array $field_name_list * @return Array */ public static function sumFields(Array $marr, Array $field_name_list): Array { $sums = []; foreach($marr as $row) { foreach($field_name_list as $fname) { $fv = $row[$fname] ?? 0; // field value $sums[$fname] = (float)bcadd($sums[$fname] ?? 0, $fv, 2); } } return $sums; } /** * 微信支付 为 0元时 * @param $order_id * @param $uid * @param string $formId * @return bool * @throws \think\Exception * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public static function jsPayPriceBatch($porder_id, $uid, $formId = '') { $subOrders = self::getAllSubOrders($porder_id); if (!$subOrders) return self::setErrorInfo('订单不存在!'); $fieldValues = self::sumFields($subOrders, ['paid']); if ($fieldValues['paid']) return self::setErrorInfo('该订单已支付!'); $userInfo = User::getUserInfo($uid); self::beginTrans(); $res1 = $res2 = true; foreach($subOrders as $orderInfo) { $res1 = $res1 && UserBill::expend('购买商品', $uid, 'now_money', 'pay_product', $orderInfo['pay_price'], $orderInfo['id'], $userInfo['now_money'], '微信支付' . floatval($orderInfo['pay_price']) . '元购买商品'); $res2 = $res2 && StoreOrder::paySuccess($orderInfo['order_id'], 'weixin', $formId);//微信支付为0时 } $res = $res1 && $res2; self::checkTrans($res); return $res; } /** * 查找购物车里的所有产品标题 * @param $cartId 购物车id * @return bool|string */ public static function getProductTitleBatch($subOrders) { $title = ''; $cartId = []; foreach($subOrders as $order) { $cartId = array_merge($cartId, $order['cart_id']); } try { $orderCart = StoreOrderCartInfo::where('cart_id', 'in', $cartId)->field('cart_info')->select(); foreach ($orderCart as $item) { if (isset($item['cart_info']['productInfo']['store_name'])) { $title .= $item['cart_info']['productInfo']['store_name'] . '|'; } } unset($item); if (!$title) { $productIds = StoreCart::where('id', 'in', $cartId)->column('product_id'); $productlist = ($productlist = StoreProduct::getProductField($productIds, 'store_name')) ? $productlist->toArray() : []; foreach ($productlist as $item) { if (isset($item['store_name'])) $title .= $item['store_name'] . '|'; } } if ($title) $title = substr($title, 0, strlen($title) - 1); unset($item); } catch (\Exception $e) { } return $title; } /** * 余额支付 * @param $order_id * @param $uid * @param string $formId * @return bool * @throws \think\Exception * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public static function yuePayBatch($porder_id, $uid, $formId = '') { $porderInfo = self::where('uid', $uid)->where('porder_id', $porder_id)->find(); if (!$porderInfo) { return self::setErrorInfo('订单不存在!'); } $subOrders = self::getAllSubOrders($porder_id); if (!$subOrders) { return self::setErrorInfo('订单不存在!'); } $fieldValues = self::sumFields($subOrders, ['paid']); if ($fieldValues['paid']) { return self::setErrorInfo('该订单已支付!'); } $userInfo = User::getUserInfo($uid); if ($userInfo['now_money'] < $porderInfo['total_price']) return self::setErrorInfo(['status' => 'pay_deficiency', 'msg' => '余额不足' . floatval($porderInfo['total_price'])]); self::beginTrans(); $res1 = $res2 = $res3 = true; foreach($subOrders as $orderInfo) { $res1 = $res1 && (false !== User::bcDec($uid, 'now_money', $orderInfo['pay_price'], 'uid')); $res2 = $res2 && UserBill::expend('购买商品', $uid, 'now_money', 'pay_product', $orderInfo['pay_price'], $orderInfo['id'], $userInfo['now_money'], '余额支付' . floatval($orderInfo['pay_price']) . '元购买商品'); $res3 = $res3 && StoreOrder::paySuccess($orderInfo['order_id'], 'yue', $formId);//余额支付成功 try { PaymentRepositories::yuePayProduct($userInfo, $orderInfo); } catch (\Exception $e) { self::rollbackTrans(); return self::setErrorInfo($e->getMessage()); } } $res = $res1 && $res2 && $res3; self::checkTrans($res); return $res; } public static function paySuccessBatch($porderId, $paytype = 'weixin', $formId = '') { $subOrders = self::getAllSubOrders($porderId); $res = true; foreach($subOrders as $order) { $res = $res && StoreOrder::paySuccess($order['order_id'], $paytype, $formId); } return $res; } /** * 生成订单 * @param $uid * @param $key orderKey 由客户端传入,总订单ID * @param $addressId * @param $payType weixin / yue * @param bool $useIntegral: 是否使用积分 * @param int $couponId * @param string $mark: 订单留言 * @param int $combinationId * @param int $pinkId * @param int $seckill_id * @param int $bargain_id * @param bool $test * @param int $isChannel 0, 1, 2 : routine, weixin, weixinh5 * @param int $shipping_type * @param string $real_name * @param string $phone * @param int $storeId: 门店ID * @return StoreOrder|bool|\think\Model * @throws \think\Exception * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public static function cacheKeyCreateOrderBatch($uid, $key, $addressId, $payType, $useIntegral = false, $couponId = 0, $mark = '', $combinationId = 0, $pinkId = 0, $seckill_id = 0, $bargain_id = 0, $test = false, $isChannel = 0, $shipping_type = 1, $real_name = '', $phone = '', $storeId = 0) { StoreOrder::beginTrans(); try { $shipping_type = (int)$shipping_type; // 支付方式判断 $offlinePayStatus = (int)sys_config('offline_pay_status') ?? (int)2; //线下支付状态 2 关闭 if ($offlinePayStatus == 2) { unset(StoreOrder::$payType['offline']); } if (!array_key_exists($payType, StoreOrder::$payType)) { return self::setErrorInfo('选择支付方式有误!', true); } // 查总订单 sql-1 if (self::be(['unique' => $key, 'uid' => $uid])) { return self::setErrorInfo('请勿重复提交订单', true); } // 用户信息 sql-2 $userInfo = User::getUserInfo($uid); if (!$userInfo) return self::setErrorInfo('用户不存在!', true); // 取订单缓存 $cartGroup = self::getCacheOrderInfo($uid, $key); if (!$cartGroup) return self::setErrorInfo('订单已过期,请刷新当前页面!', true); $cartInfo = $cartGroup['cartInfo']; $priceGroup = $cartGroup['priceGroup']; $other = $cartGroup['other']; // $payPrice = (float)$priceGroup['totalPrice']; // 收货地址 sql-3 $addr = UserAddress::where('uid', $uid)->where('id', $addressId)->find(); // if ($payType == 'offline' && sys_config('offline_postage') == 1) { // $payPostage = 0; // } else { // $payPostage = $priceGroup['storePostage']; // } if ($shipping_type === 1) { if (!$test && !$addressId) { return self::setErrorInfo('请选择收货地址!', true); } // sql-4,5 if (!$test && (!UserAddress::be(['uid' => $uid, 'id' => $addressId, 'is_del' => 0]) || !($addressInfo = UserAddress::find($addressId)))) return self::setErrorInfo('地址选择有误!', true); } else { if ((!$real_name || !$phone) && !$test) { return self::setErrorInfo('请填写姓名和电话', true); } $addressInfo['real_name'] = $real_name; $addressInfo['phone'] = $phone; $addressInfo['province'] = ''; $addressInfo['city'] = ''; $addressInfo['district'] = ''; $addressInfo['detail'] = ''; } $totalPrice = 0.0; $totalPayPrice = 0.0; $SurplusIntegral = 0; $totalPostage = 0.0; $totalDeductionPrice = 0.0; $totalCouponPrice = 0.0; $counter = 0; $subOrders = []; // 子订单逐个处理 foreach ($cartInfo as $ci) { $counter += 1; // 一个订单的购物车 $subOrderCart = [$ci]; $subOrderId = StoreOrder::getNewOrderId(); $subPriceGroup = StoreOrder::getOrderPriceGroup($subOrderCart, $addr); if (!$subPriceGroup) { return false; } $payPrice = (float)$subPriceGroup['totalPrice']; $totalPrice = (float)bcadd($totalPrice, $payPrice, 2); $cartIds = []; // 子订单购物车ID $totalNum = 0; // 总数量 $gainIntegral = 0; // 总可得积分 foreach ($subOrderCart as $cart){ // sql-6 x n if (!$test && !StoreOrder::checkProductStock($uid, $cart['product_id'], $cart['cart_num'], $cart['product_attr_unique'], $cart['combination_id'], $cart['seckill_id'], $cart['bargain_id'])) { return false; } $cartIds[] = $cart['id']; $totalNum += $cart['cart_num']; if (!$seckill_id) $seckill_id = $cart['seckill_id']; if (!$bargain_id) $bargain_id = $cart['bargain_id']; if (!$combinationId) $combinationId = $cart['combination_id']; // 可赚积分 $cartInfoGainIntegral = isset($cart['productInfo']['give_integral']) ? bcmul($cart['cart_num'], $cart['productInfo']['give_integral'], 2) : 0; // 汇总 $gainIntegral = bcadd($gainIntegral, $cartInfoGainIntegral, 2); } // foreach // 活动 $deduction = $seckill_id || $bargain_id || $combinationId; if ($deduction) { $couponId = 0; $useIntegral = false; if (!$test) { unset(StoreOrder::$payType['offline']); if (!array_key_exists($payType, StoreOrder::$payType)) { return self::setErrorInfo('营销产品不能使用线下支付!', true); } } }// //使用优惠劵 $res1 = true; if ($couponId) { $couponInfo = StoreCouponUser::validAddressWhere()->where('id', $couponId)->where('uid', $uid)->find(); if (!$couponInfo) return self::setErrorInfo('选择的优惠劵无效!', true); $coupons = StoreCouponUser::getUsableCouponList($uid, ['valid' => $subOrderCart], $payPrice); $flag = false; foreach ($coupons as $coupon) { if ($coupon['id'] == $couponId) { $flag = true; continue; } } if (!$flag) return self::setErrorInfo('不满足优惠劵的使用条件!', true); $payPrice = (float)bcsub($payPrice, $couponInfo['coupon_price'], 2); $res1 = StoreCouponUser::useCoupon($couponId); $couponPrice = $couponInfo['coupon_price']; } else { $couponId = 0; $couponPrice = 0; } if (!$res1) { return self::setErrorInfo('使用优惠劵失败!', true); } $totalCouponPrice = (float)bcadd($totalCouponPrice, $couponPrice, 2); //$shipping_type = 1 快递发货 $shipping_type = 2 门店自提 $store_self_mention = sys_config('store_self_mention') ?? 0; if (!$store_self_mention) $shipping_type = 1; $payPostage = 0; if ($shipping_type === 1) { //是否包邮 if ((isset($other['offlinePostage']) && $other['offlinePostage'] && $payType == 'offline')) { $payPostage = 0; } $payPrice = (float)bcadd($payPrice, $payPostage, 2); } else if ($shipping_type === 2) { //门店自提没有邮费支付 $subPriceGroup['storePostage'] = 0; $payPostage = 0; if (!$storeId && !$test) { return self::setErrorInfo('请选择门店', true); } } $totalPostage = (float)bcadd($totalPostage, $payPostage, 2); //积分抵扣 $res2 = true; $usedIntegral = 0.0; if ($useIntegral && $userInfo['integral'] > 0) { $deductionPrice = (float)bcmul($userInfo['integral'], $other['integralRatio'], 2); if ($deductionPrice < $payPrice) { $payPrice = bcsub($payPrice, $deductionPrice, 2); $usedIntegral = $userInfo['integral']; $SurplusIntegral = 0; // sql $res2 = false !== User::edit(['integral' => 0], $userInfo['uid'], 'uid'); } else { $deductionPrice = $payPrice; $usedIntegral = (float)bcdiv($payPrice, $other['integralRatio'], 2); $SurplusIntegral = bcsub($userInfo['integral'], $usedIntegral, 2); $res2 = false !== User::bcDec($userInfo['uid'], 'integral', $usedIntegral, 'uid'); $payPrice = 0; } $res2 = $res2 && false != UserBill::expend('积分抵扣', $uid, 'integral', 'deduction', $usedIntegral, $subOrderId, $userInfo['integral'], '购买商品使用' . floatval($usedIntegral) . '积分抵扣' . floatval($deductionPrice) . '元'); } else { $deductionPrice = 0; $usedIntegral = 0; } if (!$res2) { return self::setErrorInfo('使用积分抵扣失败!', true); } // fix at runtime $userInfo['integral'] = (float)bcsub($userInfo['integral'], $usedIntegral, 2); if ($userInfo['integral'] < 0) { $userInfo['integral'] = 0; } $totalDeductionPrice = (float)bcadd($totalDeductionPrice, $deductionPrice, 2); $totalPayPrice = (float)bcadd($totalPayPrice, $payPrice, 2); if ($test) { continue; } $unique = mb_substr($key, 0, mb_strlen($key)-3) . $counter; // 增加子订单 $orderInfo = [ 'uid' => $uid, 'order_id' => $test ? 0 : $subOrderId, 'real_name' => $addressInfo['real_name'], 'user_phone' => $addressInfo['phone'], 'user_address' => $addressInfo['province'] . ' ' . $addressInfo['city'] . ' ' . $addressInfo['district'] . ' ' . $addressInfo['detail'], 'cart_id' => $cartIds, 'total_num' => $totalNum, 'total_price' => $subPriceGroup['totalPrice'], 'total_postage' => $subPriceGroup['storePostage'], 'coupon_id' => $couponId, 'coupon_price' => $couponPrice, 'pay_price' => $payPrice, 'pay_postage' => $payPostage, 'deduction_price' => $deductionPrice, 'paid' => 0, 'pay_type' => $payType, 'use_integral' => $usedIntegral, 'gain_integral' => $gainIntegral, 'mark' => htmlspecialchars($mark), 'combination_id' => $combinationId, 'pink_id' => $pinkId, 'seckill_id' => $seckill_id, 'bargain_id' => $bargain_id, 'cost' => $subPriceGroup['costPrice'], 'is_channel' => $isChannel, 'add_time' => time(), 'unique' => $unique, 'shipping_type' => $shipping_type, ]; if ($shipping_type === 2) { $orderInfo['verify_code'] = StoreOrder::getStoreCode(); $orderInfo['store_id'] = SystemStore::getStoreDispose($storeId, 'id'); if (!$orderInfo['store_id']) return self::setErrorInfo('暂无门店无法选择门店自提!', true); } $order = StoreOrder::create($orderInfo); if (!$order) { return self::setErrorInfo('订单生成失败!', true); } $res5 = true; foreach ($subOrderCart as $cart) { //减库存加销量 if ($combinationId) { $res5 = $res5 && StoreCombination::decCombinationStock($cart['cart_num'], $combinationId, isset($cart['productInfo']['attrInfo']) ? $cart['productInfo']['attrInfo']['unique'] : ''); } else if ($seckill_id) { $res5 = $res5 && StoreSeckill::decSeckillStock($cart['cart_num'], $seckill_id, isset($cart['productInfo']['attrInfo']) ? $cart['productInfo']['attrInfo']['unique'] : ''); } else if ($bargain_id) { $res5 = $res5 && StoreBargain::decBargainStock($cart['cart_num'], $bargain_id, isset($cart['productInfo']['attrInfo']) ? $cart['productInfo']['attrInfo']['unique'] : ''); } else { $res5 = $res5 && StoreProduct::decProductStock($cart['cart_num'], $cart['productInfo']['id'], isset($cart['productInfo']['attrInfo']) ? $cart['productInfo']['attrInfo']['unique'] : ''); } } //保存购物车商品信息 $res4 = false !== StoreOrderCartInfo::setCartInfo($order['id'], $subOrderCart); //购物车状态修改 $res6 = false !== StoreCart::where('id', 'IN', $cartIds)->update(['is_pay' => 1]); if (!$res4 || !$res5 || !$res6) { return self::setErrorInfo('订单生成失败!', true); } StoreOrderStatus::status($order['id'], 'cache_key_create_order', '订单生成'); $subOrders[] = $order; } // foreach if ($totalPayPrice <= 0) $totalPayPrice = 0; if ($test) { StoreOrder::rollbackTrans(); return [ 'total_price' => $totalPrice, 'pay_price' => $totalPayPrice, 'pay_postage' => $totalPostage, 'coupon_price' => $totalCouponPrice, 'deduction_price' => $totalDeductionPrice, 'SurplusIntegral' => $SurplusIntegral, ]; } $pOrderId = self::getNewPOrderId(); foreach($subOrders as $subOrder) { $porderInfo = [ 'porder_id' => $pOrderId, 'oid' => $subOrder['id'], 'total_price' => $totalPayPrice, 'unique' => $key, 'uid' => $uid, ]; $porder = self::create($porderInfo); if(!$porder) { return self::setErrorInfo('订单生成失败!', true); } } //自动设置默认地址 if (count($subOrders)) { UserRepository::storeProductOrderCreateEbApi($subOrders[0], compact('cartInfo', 'addressId')); } StoreOrder::clearCacheOrderInfo($uid, $key); StoreOrder::commitTrans(); return $pOrderId; } catch (\PDOException $e) { StoreOrder::rollbackTrans(); return self::setErrorInfo('生成订单时SQL执行错误错误原因:' . $e->getMessage()); } catch (\Exception $e) { StoreOrder::rollbackTrans(); return self::setErrorInfo('生成订单时系统错误错误原因:' . $e->getMessage()); } } }