* @copyright assimon * @link http://utf8.hk/ */ namespace App\Service; use App\Exceptions\RuleValidationException; use App\Jobs\ApiHook; use App\Jobs\MailSend; use App\Jobs\OrderExpired; use App\Jobs\ServerJiang; use App\Jobs\TelegramPush; use App\Jobs\BarkPush; use App\Jobs\WorkWeiXinPush; use App\Models\BaseModel; use App\Models\Coupon; use App\Models\Goods; use App\Models\Order; use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; /** * 订单处理层 * * Class OrderProcessService * @package App\Service * @author: Assimon * @email: Ashang@utf8.hk * @blog: https://utf8.hk * Date: 2021/5/30 */ class OrderProcessService { const PENDING_CACHE_KEY = 'PENDING_ORDERS_LIST'; /** * 优惠码服务层 * @var \App\Service\CouponService */ private $couponService; /** * 订单服务层 * @var \App\Service\OrderService */ private $orderService; /** * 卡密服务层 * @var \App\Service\CarmisService */ private $carmisService; /** * 邮件服务层 * @var \App\Service\EmailtplService */ private $emailtplService; /** * 商品服务层. * @var \App\Service\GoodsService */ private $goodsService; /** * 商品 * @var Goods */ private $goods; /** * 优惠码 * @var Coupon; */ private $coupon; /** * 其他输入框 * @var string */ private $otherIpt; /** * 购买数量 * @var int */ private $buyAmount; /** * 购买邮箱 * @var string */ private $email; /** * 查询密码 * @var string */ private $searchPwd; /** * 下单id * @var string */ private $buyIP; /** * 支付方式 * @var int */ private $payID; public function __construct() { $this->couponService = app('Service\CouponService'); $this->orderService = app('Service\OrderService'); $this->carmisService = app('Service\CarmisService'); $this->emailtplService = app('Service\EmailtplService'); $this->goodsService = app('Service\GoodsService'); } /** * 设置支付方式 * @param int $payID */ public function setPayID(int $payID): void { $this->payID = $payID; } /** * 下单ip * @param mixed $buyIP */ public function setBuyIP($buyIP): void { $this->buyIP = $buyIP; } /** * 设置查询密码 * @param mixed $searchPwd */ public function setSearchPwd($searchPwd): void { $this->searchPwd = $searchPwd; } /** * 设置购买数量 * @param mixed $buyAmount */ public function setBuyAmount($buyAmount): void { $this->buyAmount = $buyAmount; } /** * 设置下单邮箱 * @param mixed $email */ public function setEmail($email): void { $this->email = $email; } /** * 设置商品 * * @param Goods $goods * * @author assimon * @copyright assimon * @link http://utf8.hk/ */ public function setGoods(Goods $goods) { $this->goods = $goods; } /** * 设置优惠码. * * @param ?Coupon $coupon * * @author assimon * @copyright assimon * @link http://utf8.hk/ */ public function setCoupon(?Coupon $coupon) { $this->coupon = $coupon; } /** * 其他输入框设置. * * @param ?string $otherIpt * * @author assimon * @copyright assimon * @link http://utf8.hk/ */ public function setOtherIpt(?string $otherIpt) { $this->otherIpt = $otherIpt; } /** * 计算优惠码价格 * * @return float * * @author assimon * @copyright assimon * @link http://utf8.hk/ */ private function calculateTheCouponPrice(): float { $couponPrice = 0; // 优惠码优惠价格 if ($this->coupon) { $couponPrice = $this->coupon->discount; } return $couponPrice; } /** * 计算批发优惠 * @return float * * @author assimon * @copyright assimon * @link http://utf8.hk/ */ private function calculateTheWholesalePrice(): float { $wholesalePrice = 0; // 优惠单价 $wholesaleTotalPrice = 0; // 优惠总价 if ($this->goods->wholesale_price_cnf) { $formatWholesalePrice = format_wholesale_price($this->goods->wholesale_price_cnf); foreach ($formatWholesalePrice as $item) { if ($this->buyAmount >= $item['number']) { $wholesalePrice = $item['price']; } } } if ($wholesalePrice > 0 ) { $totalPrice = $this->calculateTheTotalPrice(); // 实际原总价 $newTotalPrice = bcmul($wholesalePrice, $this->buyAmount, 2); // 批发价优惠后的总价 $wholesaleTotalPrice = bcsub($totalPrice, $newTotalPrice, 2); // 批发总优惠 } return $wholesaleTotalPrice; } /** * 订单总价 * @return float * * @author assimon * @copyright assimon * @link http://utf8.hk/ */ private function calculateTheTotalPrice(): float { $price = $this->goods->actual_price; return bcmul($price, $this->buyAmount, 2); } /** * 计算实际需要支付的价格 * * @param float $totalPrice 总价 * @param float $couponPrice 优惠码优惠价 * @param float $wholesalePrice 批发优惠 * @return float * * @author assimon * @copyright assimon * @link http://utf8.hk/ */ private function calculateTheActualPrice(float $totalPrice, float $couponPrice, float $wholesalePrice): float { $actualPrice = bcsub($totalPrice, $couponPrice, 2); $actualPrice = bcsub($actualPrice, $wholesalePrice, 2); if ($actualPrice <= 0) { $actualPrice = 0; } return $actualPrice; } /** * 创建订单. * @return Order * @throws RuleValidationException * * @author assimon * @copyright assimon * @link http://utf8.hk/ */ public function createOrder(): Order { try { $order = new Order(); // 生成订单号 $order->order_sn = strtoupper(Str::random(16)); // 设置商品 $order->goods_id = $this->goods->id; // 标题 $order->title = $this->goods->gd_name . ' x ' . $this->buyAmount; // 订单类型 $order->type = $this->goods->type; // 查询密码 $order->search_pwd = $this->searchPwd; // 邮箱 $order->email = $this->email; // 支付方式. $order->pay_id = $this->payID; // 商品单价 $order->goods_price = $this->goods->actual_price; // 购买数量 $order->buy_amount = $this->buyAmount; // 订单详情 $order->info = $this->otherIpt; // ip地址 $order->buy_ip = $this->buyIP; // 优惠码优惠价格 $order->coupon_discount_price = $this->calculateTheCouponPrice(); if ($this->coupon) { $order->coupon_id = $this->coupon->id; } // 批发价 $order->wholesale_discount_price = $this->calculateTheWholesalePrice(); // 订单总价 $order->total_price = $this->calculateTheTotalPrice(); // 订单实际需要支付价格 $order->actual_price = $this->calculateTheActualPrice( $this->calculateTheTotalPrice(), $this->calculateTheCouponPrice(), $this->calculateTheWholesalePrice() ); // 保存订单 $order->save(); // 如果有用到优惠券 if ($this->coupon) { // 设置优惠码已经使用 $this->couponService->used($this->coupon->coupon); // 使用次数-1 $this->couponService->retDecr($this->coupon->coupon); } // 将订单加入队列 x分钟后过期 $expiredOrderDate = dujiaoka_config_get('order_expire_time', 5); OrderExpired::dispatch($order->order_sn)->delay(Carbon::now()->addMinutes($expiredOrderDate)); return $order; } catch (\Exception $exception) { throw new RuleValidationException($exception->getMessage()); } } /** * 订单成功方法 * * @param string $orderSN 订单号 * @param float $actualPrice 实际支付金额 * @param string $tradeNo 第三方订单号 * @return Order * * @author assimon * @copyright assimon * @link http://utf8.hk/ */ public function completedOrder(string $orderSN, float $actualPrice, string $tradeNo = '') { DB::beginTransaction(); try { // 得到订单详情 $order = $this->orderService->detailOrderSN($orderSN); if (!$order) { throw new \Exception(__('dujiaoka.prompt.order_does_not_exist')); } // 订单已经处理 if ($order->status == Order::STATUS_COMPLETED) { throw new \Exception(__('dujiaoka.prompt.order_status_completed')); } $bccomp = bccomp($order->actual_price, $actualPrice, 2); // 金额不一致 if ($bccomp != 0) { throw new \Exception(__('dujiaoka.prompt.order_inconsistent_amounts')); } $order->actual_price = $actualPrice; $order->trade_no = $tradeNo; // 区分订单类型 // 自动发货 if ($order->type == Order::AUTOMATIC_DELIVERY) { $completedOrder = $this->processAuto($order); } else { $completedOrder = $this->processManual($order); } // 销量加上 $this->goodsService->salesVolumeIncr($order->goods_id, $order->buy_amount); DB::commit(); // 如果开启了server酱 if (dujiaoka_config_get('is_open_server_jiang', 0) == BaseModel::STATUS_OPEN) { ServerJiang::dispatch($order); } // 如果开启了TG推送 if (dujiaoka_config_get('is_open_telegram_push', 0) == BaseModel::STATUS_OPEN) { TelegramPush::dispatch($order); } // 如果开启了Bark推送 if (dujiaoka_config_get('is_open_bark_push', 0) == BaseModel::STATUS_OPEN) { BarkPush::dispatch($order); } // 如果开启了企业微信Bot推送 if (dujiaoka_config_get('is_open_qywxbot_push', 0) == BaseModel::STATUS_OPEN) { WorkWeiXinPush::dispatch($order); } // 回调事件 ApiHook::dispatch($order); return $completedOrder; } catch (\Exception $exception) { DB::rollBack(); throw new RuleValidationException($exception->getMessage()); } } /** * 手动处理的订单. * * @param Order $order 订单 * @return Order 订单 * * @author assimon * @copyright assimon * @link http://utf8.hk/ */ public function processManual(Order $order) { // 设置订单为待处理 $order->status = Order::STATUS_PENDING; // 保存订单 $order->save(); // 商品库存减去 $this->goodsService->inStockDecr($order->goods_id, $order->buy_amount); // 邮件数据 $mailData = [ 'created_at' => $order->create_at, 'product_name' => $order->goods->gd_name, 'webname' => dujiaoka_config_get('text_logo', '云泽数卡'), 'weburl' => config('app.url') ?? 'http://dujiaoka.com', 'ord_info' => str_replace(PHP_EOL, '
', $order->info), 'ord_title' => $order->title, 'order_id' => $order->order_sn, 'buy_amount' => $order->buy_amount, 'ord_price' => $order->actual_price, 'created_at' => $order->created_at, ]; $tpl = $this->emailtplService->detailByToken('manual_send_manage_mail'); $mailBody = replace_mail_tpl($tpl, $mailData); $manageMail = dujiaoka_config_get('manage_email', ''); // 邮件发送 MailSend::dispatch($manageMail, $mailBody['tpl_name'], $mailBody['tpl_content']); return $order; } /** * 处理自动发货. * * @param Order $order 订单 * @return Order 订单 * * @author assimon * @copyright assimon * @link http://utf8.hk/ */ public function processAuto(Order $order): Order { // 获得卡密 $carmis = $this->carmisService->withGoodsByAmountAndStatusUnsold($order->goods_id, $order->buy_amount); // 实际可使用的库存已经少于购买数量了 if (count($carmis) != $order->buy_amount) { $order->info = __('dujiaoka.prompt.order_carmis_insufficient_quantity_available'); $order->status = Order::STATUS_ABNORMAL; $order->save(); return $order; } $carmisInfo = array_column($carmis, 'carmi'); $ids = array_column($carmis, 'id'); $order->info = implode(PHP_EOL, $carmisInfo); $order->status = Order::STATUS_COMPLETED; $order->save(); // 将卡密设置为已售出 $this->carmisService->soldByIDS($ids); // 邮件数据 $mailData = [ 'created_at' => $order->create_at, 'product_name' => $order->goods->gd_name, 'webname' => dujiaoka_config_get('text_logo', '云泽数卡'), 'weburl' => config('app.url') ?? 'http://dujiaoka.com', 'ord_info' => implode('
', $carmisInfo), 'ord_title' => $order->title, 'order_id' => $order->order_sn, 'buy_amount' => $order->buy_amount, 'ord_price' => $order->actual_price, ]; $tpl = $this->emailtplService->detailByToken('card_send_user_email'); $mailBody = replace_mail_tpl($tpl, $mailData); // 邮件发送 MailSend::dispatch($order->email, $mailBody['tpl_name'], $mailBody['tpl_content']); return $order; } }