50], [['remark', 'refund_desc'], 'string', 'max' => 255], ]; } /** * {@inheritdoc} */ public function attributeLabels() { return [ 'id' => 'ID', 'mall_id' => '商城id', 'user_id' => 'User ID', 'course_id' => '课程id', 'order_no' => '订单编号', 'price' => '价格', 'pay_money' => '实际付款金额', 'pay_type' => 'Pay Type', 'use_discount' => '是否使用折扣,1:是,0:否', 'discount_type' => '使用的折扣类型:0:未使用,1:积分,2:红包,3:活动打折,4:会员等级优惠,5:优惠劵,跟 use_discount 字段相关联,只有当 use_discount = 1 时,此字段的值有意义', 'discount_quota' => '折扣金额,跟 use_discount 字段相关联,只有当 use_discount = 1 时,此字段的值有意义', 'order_status' => '订单状态,1:未支付,2:已取消,3:已支付,4:待退款(会员已发起退款申请但平台未处理),5:退款完成,6:订单完成(从订单支付完成的时间点起,已过15天)', 'pay_time' => '支付时间', 'cancel_time' => '订单取消的时间,下单后未支付,经过一定的时间系统自动取消', 'remark' => '备注', 'status' => '状态 0禁用,1启用,-1是删除', 'created_at' => 'Created At', 'updated_at' => 'Updated At', ]; } public function getUser(){ return $this->hasOne(User::class, ['id' => 'user_id']); } public function getCourse(){ return $this->hasOne(AddonsCourse::class, ['id' => 'course_id']); } public static function setData(array $data) { $model = new self(); $model->load($data, ''); $model->status = StatusEnum::ENABLED; if (!$model->save()) { self::$static_error = '保存数据失败:' . $model->getErrorMessage(); return false; } return $model; } /** * 新增用户购买的课程 * * @return boolean * @throws \Exception */ public function addBought($id, $user_id) { $bought = AddonsCourseBought::findOne(["order_id" => $id, "buy_type" => 1, "user_id" => $user_id]); if (empty($bought)) { $bought = new AddonsCourseBought(); $bought->mall_id = $this->mall_id; $bought->user_id = $this->user_id; $bought->course_id = $this->course_id; $bought->amount = $this->real_pay; $bought->buy_type = 1; $bought->order_id = $this->id; $bought->status = 2; if (!$bought->save()) throw new \Exception("保存课程失败"); } return true; } public static function generateOrderNo($prefix = '') { $randLen = 6; $id = base_convert(substr(uniqid(), 0 - $randLen), 16, 10); if (strlen($id) > 10) { $id = substr($id, -10); } elseif (strlen($id) < 10) { $rLen = 10 - strlen($id); $id = $id . rand(pow(10, $rLen - 1), pow(10, $rLen) - 1); } $dateTimeStr = date('YmdHis'); return $prefix . $dateTimeStr . $id; } /** * @throws \Exception */ public static function preview($user, $mall_id, $post) { return self::handleData($user, $mall_id, $post, 1); } public static function createOrder($user, $mall_id, $params) { $t = \Yii::$app->db->beginTransaction(); try { if (Order::isStopOrder($mall_id)) throw new Exception('站点维护中,暂时无法下单'); $data = self::handleData($user, $mall_id, $params, 2); //生成订单 $orderno = AddonsCourseOrders::generateOrderNo('C'); $oldOrder = AddonsCourseOrders::getOne([['order_no' => $orderno]]); if ($oldOrder) { self::$static_error = '重复下单'; return false; } $use_discount = 0; $discount_type = 0; foreach ($data['list'] as $orderItem) { if (!empty($orderItem['use_discount'])) { $use_discount = 1; $discount_type = 4; } } $params = [ 'mall_id' => $mall_id, 'user_id' => $user['id'], 'course_id' => $data['list'][0]['id'], 'order_no' => $orderno, 'price' => $data['total_price'], 'pay_money' => $data['total_price'], 'remark' => isset($post["remark"]) ? $post["remark"] : "", 'pay_type' => CourseEnum::PAY_TYPE_NO_PAY, 'order_status' => CourseEnum::STATUS_WAIT_PAY, 'use_discount' => $use_discount, 'discount_type' => $discount_type, 'discount_quota' => !empty($data['discount_quota']) ? $data['discount_quota'] : 0.00, ]; $state = AddonsCourseOrders::setData($params); if (!$state) { throw new \Exception("订单保存失败"); } AddonsCourseBought::addBought($params, $state->id); $order_trade_info[] = [ 'order_no' => $orderno, 'amount' => $data['total_price'], 'title' => '购买课程' . mb_strlen($data['list'][0]['name']) > 32 ? mb_substr($data['list'][0]['name'], 0, 32) : $data['list'][0]['name'], 'notify_class' => CourseEnum::NOTIFY_CLASS, 'order_type' => PaymentTradeOrderEnum::COURSE_ORDER, ]; $t->commit(); //生成支付流水 $res = \Yii::$app->services->pay->createTrade($params['mall_id'], $params['user_id'], $order_trade_info); if ($res == false) throw new \Exception('生成支付流水失败'); $arr['transaction'] = $res; $arr['orderno'] = $orderno; $arr['pay_success_page'] = Yii::$app->services->setting->mall->get($params['mall_id'], 'order.pay_after_link');// 支付成功后跳转的地址 return $arr; } catch (\Exception $exception) { $t->rollBack(); self::$static_error = $exception->getMessage(); return false; } } /** * 支付 * User: wniu * Date: 2022/3/18 * Time: 3:49 下午 * * * @param $params * @param $pay_type * @return AddonsCourseOrders|false */ public static function pay($params, $pay_type) { $trans = Yii::$app->db->beginTransaction(); try { $model = self::findOne(['id' => $params['id'], 'status' => [StatusEnum::ENABLED]]); if (empty($model)) throw new \Exception('订单不存在或已删除'); if ($model->order_status != CourseEnum::STATUS_WAIT_PAY) throw new \Exception('订单已经被处理,无法进行支付'); $model->order_status = CourseEnum::IS_PAY_YES; $model->pay_type = $pay_type; $model->pay_time = time(); $res = $model->save(); $course = AddonsCourse::getOne([['id' => $params['course_id']]]); if ($course) { $course->sales = $course->sales + 1; $course->save(); } $bought = AddonsCourseBought::getOne([['order_id' => $params['id']]]); if ($bought) { $bought->is_pay = CourseEnum::IS_PAY_YES; $bought->save(); } if ($res === false) throw new \Exception('修改订单失败:' . $model->getErrorMessage()); $trans->commit(); // 触发订单支付事件,事务提交完成后触发 $event = new CourseOrderPaidEvent($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; } } /** * @param int $type 1 预览订单 2:提交订单 * * @param $form_data * @param int $type * @throws \Exception */ protected static function handleData($user, $mall_id, $form_data, $type = 1) { $listData = self::getCourseListData($form_data["list"], $user); $total_price = 0.00; $total_original_price = 0.00; foreach ($listData as &$priceItem) { $total_price += $priceItem['price']; $total_original_price += $priceItem['original_price']; } $discount_quota = 0.00; if ($total_original_price > $total_price) { $discount_quota = bcsub($total_original_price, $total_price, 2); } return [ 'list' => $listData, 'total_price' => self::price_format($total_price), 'total_original_price' => self::price_format($total_original_price), 'discount_quota' => self::price_format($discount_quota), ]; } /** * 课程列表数据 * @param $courseList * @return array * @throws \Exception */ protected static function getCourseListData($courseList, $user) { $list = []; if (!empty($courseList)) { foreach ($courseList as $i => $item) { $list[] = self::getOneCourseItemData($item, $user); } } return $list; } /** * 单个课程数据 * @return array * @throws \Exception */ protected static function getOneCourseItemData($item, $user) { $course = AddonsCourse::getOne([['id' => $item['id'], 'status' => StatusEnum::ENABLED]]); if (!$course) { throw new \Exception('课程不存在或已下架。'); } //是否使用了折扣 $use_discount = false; //会员价 $price = $course->getMemberPrice($user, $use_discount); $itemData = [ 'id' => $course->id, 'name' => $course->name, 'num' => $item['num'], 'price' => self::price_format($price * $item['num']), 'logo' => $course->logo, 'original_price' => self::price_format($course->price * $item['num']), 'use_discount' => $use_discount, 'line_off_price' => $course->line_off_price, ]; return $itemData; } static function price_format($val, $decimals = 2) { $val = floatval($val); $result = number_format($val, $decimals, '.', ''); return $result; } /** * 上传物流信息(微信小程序要求) * @param $order * @return true|void * */ public static function uploadShippingInfo($order) { $order_no = $order['order_no']; $mall_id = $order['mall_id']; $course = $order['course']; try { $payment_trade_order_model = PaymentTradeOrder::findOne(['order_no'=>$order_no,'is_pay'=>1]); if(empty($payment_trade_order_model)) throw new \Exception("order_no:".$order_no.",payment_trade_order_model不存在"); $pay_type = [PayTypeEnum::WECHAT, PayTypeEnum::JOINPAY, PayTypeEnum::HUIFUPAY, PayTypeEnum::UNIONPAY]; $trade_type = ['JSAPI', 'WEIXIN_XCX', 'T_MINIAPP', 'W06-CASHIER', 'W06']; $payment_trade_model = PaymentTrade::findOne(['id'=>$payment_trade_order_model['trade_id'],'pay_type'=>$pay_type,'trade_type'=>$trade_type]); if(empty($payment_trade_model)) throw new \Exception("trade_id:".$payment_trade_order_model['trade_id'].",payment_trade_model不存在"); if($payment_trade_model['is_pay_group']){ //组合支付,查询对应的子流水 $payment_trade_model = PaymentTrade::findOne(['parent_trade_id'=>$payment_trade_order_model['trade_id'],'pay_type'=>$pay_type,'trade_type'=>$trade_type]); }else{ $payment_trade_model = PaymentTrade::findOne(['id'=>$payment_trade_order_model['trade_id'],'pay_type'=>$pay_type,'trade_type'=>$trade_type]); } $third_trade_no = ''; switch ($payment_trade_model['pay_type']){ case PayTypeEnum::WECHAT: //微信支付 $third_trade_no = $payment_trade_model['third_trade_no']; break; case PayTypeEnum::JOINPAY: if(!MallAddons::isInstall($mall_id,'JoinPay')){ return true; } $third_trade_no = $payment_trade_model['second_trade_no']; break; case PayTypeEnum::HUIFUPAY: if(!MallAddons::isInstall($mall_id,'HuifuPay')){ return true; } $third_trade_no = $payment_trade_model['second_trade_no']; break; } if(empty($third_trade_no)){ return true; } $params['mall_id'] = $mall_id; $params['item_desc'] = $course['name']??''; $params['user_id'] = $payment_trade_model['user_id']; $params['third_trade_no'] = $third_trade_no; $params['delivery_mode'] = 1; $params['is_all_delivered'] = false; WechatMiniProgramLogic::uploadShippingInfoWechatByVirtually($params); }catch (\Exception $e){ Yii::error('uploadShippingInfo:' . $e->getMessage()); } } }