50], [['remark', 'goods_name', 'goods_attr_name','pic_url'], 'string', 'max' => 255], [['mobile'], 'string', 'max' => 11], [['receiver_name'], 'string', 'max' => 50], [['zip'], 'string', 'max' => 20], [['address', 'region_name'], 'string', 'max' => 200], [['marketing'], 'safe'], ]; } /** * {@inheritdoc} */ public function attributeLabels() { return [ 'id' => 'id', 'mall_id' => '商城ID', 'mch_id' => '商户id', 'user_id' => '用户ID', 'order_id' => '主订单表id', 'order_no' => '订单编号', 'out_trade_no' => '外部交易号', 'group_buy_active_id' => '拼团活动id', 'open_group_id' => '开团id', 'commander_id' => '拼团长id', 'is_commander' => '是否团长 0:否:1:是', 'goods_id' => '商品id', 'goods_attr_id' => '商品规格id', 'pic_url' => '商品图片', 'order_status' => '拼团状态 0待支付 1参团中 2拼团成功 3拼团失败', 'price' => '商品原销售价格', 'group_buy_price' => '拼团商品价格', 'num' => '购买数量', 'win_time' => '拼中时间', 'payment_type' => '支付方式:1.在线支付 2.货到付款 3.余额支付', 'pay_time' => '支付时间', 'total_pay_price' => '实际支付总费用(含运费)', 'remark' => '用户订单备注', 'is_pay' => '是否支付:0.未支付|1.已支付', 'status' => '状态[-1:删除;0:禁用;1启用]', 'created_at' => '创建时间', 'updated_at' => '更新时间', 'mobile' => '收货人的手机号码', 'receiver_name' => '收货人姓名', 'province' => '收货人所在省', 'city' => '收货人所在城市', 'area' => '收货人所在街道', 'zip' => '收货人邮编', 'address' => '详细地址', 'region_name' => '地区名称(省市区[乡/镇])', 'marketing' => '拼团营销优惠', 'is_free' => '是否免费开团', ]; } // 关联模型 public function getUser() { return $this->hasOne(User::class, ['id' => 'user_id']); } public function getGroupBuyActive() { return $this->hasOne(GroupBuyActive::class, ['id' => 'group_buy_active_id']); } // 获取当团下的所有订单 public function getCurrentGroup() { return $this->hasMany(GroupBuyOrder::class, ['open_group_id' => 'open_group_id']); } /** * @desc:生成订单 * @Author: hua * @Date: 2021/12/15 * @Time: 15:14 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $form * @return array|false */ public static function createOrder($form) { $stock_locks = []; try { $group_order_goods = $form['group_order_goods']; $data = is_array($form->data) ? $form->data : Json::decode($form->data, true); $trans = Yii::$app->db->beginTransaction(); $order_trade_info = []; $goods_stock_arr = []; $open_group_id = 0; //只有一个订单 $order_id_arr = []; foreach ($group_order_goods as $mch_order) { $mch_order_info = $mch_order['mch_order_info']; $order = new GroupBuyOrder(); $order->loadDefaultValues(); $order->load($mch_order_info, ''); $order->remark = $data['remark']; $order->order_status = OrderStatusEnum::NOT_PAY; $order->total_pay_price = $mch_order_info['pay_money']; $order->order_no = StringHelper::generateOrderNo('gy'); $marketing = []; if (isset($form->marketing['deduct']['shipping_fee'])) { $marketing['shipping_fee'] = $form->marketing['deduct']['shipping_fee']; } if (isset($form->marketing['deduct']['is_free_open_group'])) { $marketing['is_free_open_group'] = $form->marketing['deduct']['is_free_open_group']; } $order->marketing = $marketing; $res = $order->save(); if ($res === false) throw new \Exception($order->getErrorMessage()); $order_id_arr[]=$order->id; if (empty($mch_order_info['open_group_id'])) { $order->open_group_id = $order->id; $order->save(); } $open_group_id = $order->open_group_id; // 总商品库存维护 $lock_key = RedisKeyEnum::suffix(RedisKeyEnum::LOCK_GROUP_BUY_GOODS_ATTR , $mch_order_info['goods_attr_id']); $identification = uniqid(); if (!LockHelper::lock($lock_key, $identification, 30, 60)) throw new \Exception('当前访问人数过多'); $stock_locks[$lock_key] = $identification; //操作商品库存-减 GroupBuyActiveGoodsAttr::handleStock($form['mall_id'], $mch_order_info['group_buy_active_id'], $mch_order_info['goods_attr_id'], $mch_order_info['num'] * -1); if (!isset($goods_stock_arr[$mch_order_info['goods_id']])) { $goods_stock_arr[$mch_order_info['goods_id']] = $mch_order_info['num']; } else { $goods_stock_arr[$mch_order_info['goods_id']] += $mch_order_info['num']; } $order_trade_info[] = [ 'order_id' => $order->id, 'order_no' => $order->order_no, 'amount' => $order->total_pay_price, 'title' => mb_strlen($mch_order_info['goods_name']) > 32 ? mb_substr($mch_order_info['goods_name'], 0, 32) : $mch_order_info['goods_name'], 'notify_class' => GroupBuyEnum::NOTIFY_CLASS, 'order_type' => PaymentTradeOrderEnum::GROUP_BUY_ORDER, ]; foreach ($goods_stock_arr as $goods_id => $num) { $active_model = GroupBuyActive::findOne(['mall_id' => $form['mall_id'], 'id' => $data['active_id'], 'goods_id' => $goods_id]); $active_model->goods_stock -= $num; $active_model->save(); } } $trans->commit(); LockHelper::batchUlock($stock_locks); //生成支付流水 $res = \Yii::$app->services->pay->createTrade($form['mall_id'], $form['user_id'], $order_trade_info); if ($res == false) throw new \Exception('生成支付流水失败'); // 更新订单金额 $payment = PaymentTrade::findOne(['id' => $res['trade_id']]); GroupBuyOrder::updateAll(['out_trade_no'=>$res['trade_no'], 'total_pay_price' => $payment->pay_fee],['id'=>$order_id_arr]); $arr['transaction'] = $res; $arr['open_group_id'] = $open_group_id; $arr['pay_success_page'] = Yii::$app->services->setting->mall->get($form['mall_id'], 'order.pay_after_link');// 支付成功后跳转的地址 return $arr; } catch (\Exception $e) { LockHelper::batchUlock($stock_locks); if (isset($trans)) $trans->rollback(); self::$static_error = $e->getMessage(); return false; } } /** * @desc:取消拼团订单 * @Author: hua * @Date: 2021/12/15 * @Time: 17:19 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $params * @param false $force * @return GroupBuyOrder|false * @throws \yii\db\Exception */ public static function close($params, $force = false) { $trans = Yii::$app->db->beginTransaction(); $stock_locks = []; try { $cond = ['mall_id' => $params['mall_id'], 'id' => $params['id'], 'status' => [StatusEnum::ENABLED]]; isset($params['user_id']) && $cond['user_id'] = $params['user_id']; $model = self::findOne($cond); if (empty($model)) throw new \Exception('订单不存在或已删除'); // 判断订单是否已经被关闭 if ($model->order_status == GroupBuyEnum::GROUP_CLOSE) throw new Exception('订单已经关闭'); // 判断是否是未支付的订单 if ($force == false && $model->order_status != OrderStatusEnum::NOT_PAY) throw new Exception('订单已经被处理'); $model->order_status = GroupBuyEnum::GROUP_CLOSE; if (!$model->save()) throw new Exception($model->getErrorMessage()); self::rollBackStock($model, $stock_locks);//回退库存 $trans->commit(); LockHelper::batchUlock($stock_locks); return $model; } catch (Exception $e) { $trans->rollback(); LockHelper::batchUlock($stock_locks); self::$static_error = $e->getMessage(); return false; } } /** * @desc:回退库存 * @Author: hua * @Date: 2021/12/15 * @Time: 17:34 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $model * @param $stock_locks * @throws Exception */ public static function rollBackStock($model, &$stock_locks) { $goods_attr_arr[$model->goods_attr_id] = $model->num; //操作规格库存-回退 $lock_key = RedisKeyEnum::suffix(RedisKeyEnum::LOCK_GROUP_BUY_GOODS_ATTR , $model->goods_attr_id); $identification = uniqid(); if (!LockHelper::lock($lock_key, $identification, 30, 60)) throw new Exception('当前访问人数过多'); $stock_locks[$lock_key] = $identification; //操作商品库存-回退 GroupBuyActiveGoodsAttr::handleStock($model->mall_id, $model->group_buy_active_id, $model->goods_attr_id, $model->num); $stock = GroupBuyActiveGoodsAttr::find()->where(['mall_id' => $model->mall_id, 'group_buy_active_id' => $model->group_buy_active_id, 'goods_id' => $model->goods_id])->sum('stock'); $stock = $stock ?? 0; $res = GroupBuyActive::updateAll(['goods_stock' => $stock], ['mall_id' => $model->mall_id, 'id' => $model->group_buy_active_id, 'goods_id' => $model->goods_id]); if (!$res) throw new \Exception(Goods::getStaticError()); } /** * @desc:结束拼团 * @Author: hua * @Date: 2021/12/15 * @Time: 11:43 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $mall_id * @param $open_group_id * @return bool */ public static function finishGroupBuyOrder($mall_id, $open_group_id) { // 避免并发 $finishGroupBuyOrderLockKey = RedisKeyEnum::suffix('GroupBuy:finishGroupBuyOrder:', $open_group_id); $identification = uniqid('', true); if (!LockHelper::lock($finishGroupBuyOrderLockKey, $identification, 60, 60)) throw new Exception('当前访问太频繁'); Yii::$app->db->close(); $t = Yii::$app->db->beginTransaction(); $stock_locks = []; try { $list = self::findAll(['mall_id' => $mall_id, 'open_group_id' => $open_group_id, 'order_status' => GroupBuyEnum::JOINING]); foreach ($list as $group_buy_order) { //退款逻辑 $desc = '用户(' . $group_buy_order->user_id . '),拼团失败,订单(' . $group_buy_order->id . ')退款:,金额:' . $group_buy_order->total_pay_price; $payment_order = PaymentTradeOrder::findOne(['order_no'=>$group_buy_order->order_no]); $payment_trade = PaymentTrade::findOne(['id'=>$payment_order->trade_id]); try { $res = Yii::$app->services->pay->refund($payment_trade->pay_fee,$payment_trade->pay_fee, $payment_trade->out_trade_no, $group_buy_order,$desc, UserWalletEnum::FROM_GROUP_BUY, UserWalletEnum::REFUND_ORDER); if ($res == false) throw new \Exception('退款失败'); }catch (Exception $e){ var_dump('finishGroupBuyOrder:::'.$e->getMessage()); continue; } //归还库存 self::rollBackStock($group_buy_order, $stock_locks); } //拼团失败 $res = self::updateAll(['order_status' => GroupBuyEnum::GROUP_FAILURE], ['mall_id' => $mall_id, 'open_group_id' => $open_group_id, 'order_status' => GroupBuyEnum::JOINING]); if ($res == false) throw new \Exception(self::getStaticError()); LockHelper::uLock($finishGroupBuyOrderLockKey, $identification); LockHelper::batchUlock($stock_locks); $t->commit(); return true; } catch (Exception $e) { $t->rollback(); LockHelper::uLock($finishGroupBuyOrderLockKey, $identification); LockHelper::batchUlock($stock_locks); self::$static_error = $e->getMessage(); return false; } } public static function finishGroupBuyOrderSync($data){ self::finishGroupBuyOrder($data['mall_id'],$data['open_group_id']); } /** * @desc:支付 * @Author: hua * @Date: 2021/12/15 * @Time: 15:15 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $params * @param $peyment_type * @return GroupBuyOrder|false */ public static function pay($params, $peyment_type) { $trans = Yii::$app->db->beginTransaction(); try { $model = self::findOne(['id' => $params['id'], 'status' => [StatusEnum::ENABLED]]); if (empty($model)) throw new \Exception('订单不存在或已删除'); if ($model->is_pay != OrderStatusEnum::NOT_PAY) throw new \Exception('订单已经被处理,无法进行支付'); if ($model->is_pay == StatusEnum::ENABLED) throw new \Exception('订单已支付,请不要重复支付'); $model->is_pay = OrderStatusEnum::PAY; $model->order_status = GroupBuyEnum::JOINING; $model->payment_type = $peyment_type; $model->pay_time = time(); $res = $model->save(); if ($res === false) throw new \Exception('修改订单失败:' . $model->getErrorMessage()); $trans->commit(); // 触发订单支付事件,事务提交完成后触发 $event = new OrderPaidEvent($model->mall_id); $order['id'] = $model['id']; $order['operator_id'] = $params['operator_id'] ?? ''; $order['operator_name'] = $params['operator_name'] ?? ''; Yii::$app->services->events->dispatch($event, $order, $event->listeners); return $model; } catch (\Exception $e) { if (isset($trans)) $trans->rollback(); self::$static_error = $e->getMessage(); return false; } } /** * @desc:延时队列,执行自动关闭拼团订单 * @Author: hua * @Date: 2022/1/4 * @Time: 10:38 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $data */ public static function autoFinish($data){ $lock_key = RedisKeyEnum::suffix(RedisKeyEnum::LOCK_GROUP_BUY_OPEN_GROUP_ID , $data['open_group_id']); $identification = uniqid(); try { if (!LockHelper::lock($lock_key, $identification, 30, 60)) throw new Exception('当前访问人数过多:order_id:' . $data['order_id']); $group_buy_order = self::findOne( ['id' => $data['order_id'],'open_group_id'=>$data['open_group_id'],'is_pay'=>1,'order_status' => GroupBuyEnum::JOINING]); if(empty($group_buy_order)) throw new Exception('该订单不存在'); $active = GroupBuyActive::findOne(['mall_id' => $group_buy_order->mall_id, 'id' => $group_buy_order->group_buy_active_id]); $where = [ ['mall_id' => $group_buy_order->mall_id], ['group_buy_active_id' => $group_buy_order->group_buy_active_id], ['open_group_id' => $group_buy_order->open_group_id], ['is_pay' => 1], ['order_status' => GroupBuyEnum::JOINING], ]; $limit = $active->gt_group_size?$active->gt_group_size:$active->group_size; $list = GroupBuyOrder::lists(['where' => $where, 'order' => 'pay_time asc,id asc', 'limit' => $limit], false); $counts = count($list); if ($counts>=$active->group_size){ //拼团成功(虚拟和非虚拟) $res = GroupBuyOrder::addMainOrder($group_buy_order,$list,$active,$counts); if($res==false) throw new \Exception('生成主订单错误,触发:order_id:' . $data['order_id']); }else{ if ($active->is_virtual && $active->virtual_group_type == 2 && $counts >= $active->gt_group_size) { //虚拟拼团成功 $res = GroupBuyOrder::addMainOrder($group_buy_order,$list,$active,$counts); if($res==false) throw new \Exception('生成主订单错误,触发:order_id:' . $data['order_id']); }else{ $res = self::finishGroupBuyOrder($group_buy_order->mall_id,$group_buy_order->open_group_id); if ($res == false) { throw new Exception(' 开团id[ID:' . $group_buy_order->open_group_id . ']过期,执行拼团结束失败, errmsg: ' . GroupBuyOrder::getStaticError() . PHP_EOL); } } } LockHelper::uLock($lock_key, $identification); }catch (Exception $e){ $e->getMessage(); LockHelper::uLock($lock_key, $identification); } } /** * @desc:生成主订单 * @Author: hua * @Date: 2022/1/4 * @Time: 10:33 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $last_order * @param $list * @param $active * @param $counts */ public static function addMainOrder($last_order,$list,$active,$counts){ $t = Yii::$app->db->beginTransaction(); try { $goods = Goods::findOne(['id' => $last_order->goods_id]); $goods_attr = GoodsAttr::findOne(['id' => $last_order->goods_attr_id]); $marketing_type = GroupBuyEnum::ADDONS_NAME; $total_num = $total_orders_price = 0; //成功开团,往主订单写入数据 $main_order_id_arr = []; foreach ($list as $group_buy_order) { $order_id = self::createMallOrder($group_buy_order, $marketing_type, $goods, $goods_attr); $group_buy_order->order_id = $order_id; $main_order_id_arr[$group_buy_order->user_id] = $order_id; $group_buy_order->order_status = GroupBuyEnum::GROUP_SUCCESS; $group_buy_order->win_time = $last_order->created_at; $res = $group_buy_order->save(); if ($res == false) throw new \Exception('开团成功回写主订id失败'); $total_num += $group_buy_order['num']; $total_orders_price += $group_buy_order['group_buy_price'] * $group_buy_order['num']; } $active->total_num += $total_num +($active->group_size-$counts); $active->total_orders += $counts; $active->total_orders_price += $total_orders_price; $res = $active->save(); if ($res == false) throw new \Exception('更新拼团活动失败'); $t->commit(); self::giveReward($last_order->commander_id, GroupBuyOrder::tableName(), $last_order->open_group_id, $active); // 触发订单支付事件,事务提交完成后触发 $user_id_arr = array_keys($main_order_id_arr); $user_list = User::lists(['where' => [['id' => $user_id_arr]], 'index' => 'id']); foreach ($main_order_id_arr as $user_id => $order_id) { $event = new \common\events\order\OrderPaidEvent($last_order->mall_id); $order['id'] = $order_id; $order['operator_id'] = $user_id; $order['operator_name'] = $user_list[$user_id]['nickname'] ?? ''; Yii::$app->services->events->dispatch($event, $order, $event->listeners); } return true; }catch (Exception $e){ $t->rollBack(); $exception = FormatHelper::exception($e, "订单支付触发拼团事件"); echo $exception; Yii::error($exception); return false; } } /** * @desc:生成主订单 * @Author: hua * @Date: 2021/12/16 * @Time: 14:32 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $group_buy_order * @param $marketing_type * @param $goods * @param $goods_attr * @return int * @throws Exception */ public static function createMallOrder($group_buy_order, $marketing_type, $goods, $goods_attr) { $order = new Order(); $order->loadDefaultValues(); $order->load($group_buy_order->toArray(), ''); $order->order_status = OrderStatusEnum::PAY; $order->marketing_id = $group_buy_order->group_buy_active_id; $order->marketing_type = $marketing_type; $order->goods_price = $group_buy_order->group_buy_price; $order->pay_money = $group_buy_order->total_pay_price; $order->shipping_money = $group_buy_order->shipping_money; $order->shipping_type = $group_buy_order->shipping_type; $order->original_goods_price = $goods_attr->original_price; $order->pay_status = 1; $res = $order->save(); if ($res == false) throw new \Exception('开团成功生成主订单失败'); self::createMallOrderDetail($group_buy_order, $order->id, $goods, $goods_attr, $marketing_type); return $order->id; } /** * @desc:生成主订单详情 * @Author: hua * @Date: 2021/12/16 * @Time: 14:32 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $group_buy_order * @param $order_id * @param $goods * @param $goods_attr * @param $marketing_type * @throws Exception */ public static function createMallOrderDetail($group_buy_order, $order_id, $goods, $goods_attr, $marketing_type) { // 订单详情 $order_detail = new OrderDetail(); $order_detail->loadDefaultValues(); $order_detail->user_id = $group_buy_order->user_id; $order_detail->mall_id = $group_buy_order->mall_id; $order_detail->mch_id = $group_buy_order->mch_id; $order_detail->order_id = $order_id; $order_detail->goods_id = $group_buy_order->goods_id; $order_detail->goods_attr_id = $group_buy_order->goods_attr_id; $order_detail->goods_name = $group_buy_order->goods_name; $order_detail->goods_attr_name = $group_buy_order->goods_attr_name; $order_detail->price = $group_buy_order->price; $order_detail->cost_price = $goods_attr->cost_price; $order_detail->original_goods_price = bcmul($goods_attr->price, $group_buy_order->num, 2); $order_detail->goods_price = $group_buy_order->group_buy_price; $order_detail->num = $group_buy_order->num; $order_detail->pic_url = $group_buy_order->pic_url; $order_detail->marketing_id = $group_buy_order->group_buy_active_id; $order_detail->marketing_type = $marketing_type; $order_detail->order_status = OrderStatusEnum::PAY; $order_detail->remark = $group_buy_order->remark; //$order_detail->is_virtual = $goods->goods_type; $order_detail->is_virtual = $goods->goods_type == GoodsTypeEnum::GoodsTypeVirtual ? 1 : 0; $res = $order_detail->save(); if ($res == false) throw new \Exception('开团成功生成订单详情失败'); } /** * @desc:拼团奖励 * @Author: hua * @Date: 2022/1/4 * @Time: 10:35 * @Copyright: copyright (c) 2021 广东七件事集团 * @param $user_id * @param $source_table * @param $open_group_id * @param $active * @throws Exception */ public static function giveReward($user_id, $source_table, $open_group_id, $active) { $insert_wallet = []; $give_arr = ['balance' => ['numbers' => $active->give_balance, 'name' => '余额'], 'score' => ['numbers' => $active->give_score, 'name' => '积分']]; foreach ($give_arr as $code => $item) { if ($item['numbers']) { $desc = '用户(' . $user_id . '),获得开团(' . $open_group_id . ')奖励,奖励类型:' . $item['name'] . ',数量:' . $item['numbers']; $insert_wallet [] = ['mall_id' => $active->mall_id, 'user_id' => $user_id, 'is_frozen' => false, 'wallet_type' => $code, 'money' => $item['numbers'], 'desc' => $desc, 'source_table' => $source_table, 'source_table_id' => $open_group_id, 'from_type' => UserWalletEnum::FROM_GROUP_BUY, 'capital_type' => UserWalletEnum::GIVE, ]; } } if (!empty($insert_wallet)) { UserWalletService::batchHandleByQueue($insert_wallet); } } public static function check($params): bool { $order_no_arr = $params['order_no_arr']; $payment_order_list = $params['payment_order_list']; $counts = GroupBuyOrder::find()->where(['order_no'=>$order_no_arr,'is_pay'=>0])->count(); if ($counts != count($payment_order_list)) { self::$static_error = '拼团订单不一致'; return false; } return true; } }