first commit
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* The file was created by Assimon.
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
|
||||
use App\Models\Carmis;
|
||||
|
||||
class CarmisService
|
||||
{
|
||||
|
||||
/**
|
||||
* 通过商品查询一些数量未使用的卡密
|
||||
*
|
||||
* @param int $goodsID 商品id
|
||||
* @param int $byAmount 数量
|
||||
* @return array|null
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function withGoodsByAmountAndStatusUnsold(int $goodsID, int $byAmount)
|
||||
{
|
||||
$carmis = Carmis::query()
|
||||
->where('goods_id', $goodsID)
|
||||
->where('status', Carmis::STATUS_UNSOLD)
|
||||
->take($byAmount)
|
||||
->get();
|
||||
return $carmis ? $carmis->toArray() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id集合设置卡密已售出
|
||||
*
|
||||
* @param array $ids 卡密id集合
|
||||
* @return bool
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function soldByIDS(array $ids): bool
|
||||
{
|
||||
return Carmis::query()->whereIn('id', $ids)->where('is_loop', 0)->update(['status' => Carmis::STATUS_SOLD]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* The file was created by Assimon.
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
|
||||
use App\Models\Coupon;
|
||||
|
||||
class CouponService
|
||||
{
|
||||
|
||||
/**
|
||||
* 获得优惠码,通过商品关联
|
||||
*
|
||||
* @param string $coupon 优惠码
|
||||
* @param int $goodsID 商品id
|
||||
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function withHasGoods(string $coupon, int $goodsID)
|
||||
{
|
||||
$coupon = Coupon::query()->whereHas('goods', function ($query) use ($goodsID) {
|
||||
$query->where('goods_id', $goodsID);
|
||||
})->where('is_open', Coupon::STATUS_OPEN)->where('coupon', $coupon)->first();
|
||||
return $coupon;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置优惠券已使用
|
||||
* @param string $coupon
|
||||
* @return bool
|
||||
*/
|
||||
public function used(string $coupon): bool
|
||||
{
|
||||
return Coupon::query()
|
||||
->where('coupon', $coupon)
|
||||
->update(['is_use' => Coupon::STATUS_USE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置优惠券使用次数 -1
|
||||
* @param string $coupon
|
||||
* @return int
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function retDecr(string $coupon)
|
||||
{
|
||||
return Coupon::query()
|
||||
->where('coupon', $coupon)
|
||||
->decrement('ret', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置优惠券次数+1
|
||||
*
|
||||
* @param int $id
|
||||
* @return int
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function retIncrByID(int $id)
|
||||
{
|
||||
return Coupon::query()->where('id', $id)->increment('ret', 1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* The file was created by Assimon.
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
|
||||
use App\Models\Emailtpl;
|
||||
|
||||
class EmailtplService
|
||||
{
|
||||
|
||||
/**
|
||||
* 通过邮件标识获得邮件模板
|
||||
*
|
||||
* @param string $token 邮件标识
|
||||
* @return Emailtpl
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function detailByToken(string $token): Emailtpl
|
||||
{
|
||||
$tpl = Emailtpl::query()->where('tpl_token', $token)->first();
|
||||
return $tpl;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* The file was created by Assimon.
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
|
||||
use App\Exceptions\RuleValidationException;
|
||||
use App\Models\Carmis;
|
||||
use App\Models\Goods;
|
||||
use App\Models\GoodsGroup;
|
||||
|
||||
/**
|
||||
* 商品服务层
|
||||
*
|
||||
* Class GoodsService
|
||||
* @package App\Service
|
||||
* @author: Assimon
|
||||
* @email: Ashang@utf8.hk
|
||||
* @blog: https://utf8.hk
|
||||
* Date: 2021/5/30
|
||||
*/
|
||||
class GoodsService
|
||||
{
|
||||
|
||||
/**
|
||||
* 获取所有分类并加载该分类下的商品
|
||||
*
|
||||
* @return array|null
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function withGroup(): ?array
|
||||
{
|
||||
$goods = GoodsGroup::query()
|
||||
->with(['goods' => function($query) {
|
||||
$query->withCount(['carmis' => function($query) {
|
||||
$query->where('status', Carmis::STATUS_UNSOLD);
|
||||
}])->where('is_open', Goods::STATUS_OPEN)->orderBy('ord', 'DESC');
|
||||
}])
|
||||
->where('is_open', GoodsGroup::STATUS_OPEN)
|
||||
->orderBy('ord', 'DESC')
|
||||
->get();
|
||||
// 将自动
|
||||
return $goods ? $goods->toArray() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品详情
|
||||
*
|
||||
* @param int $id 商品id
|
||||
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function detail(int $id)
|
||||
{
|
||||
$goods = Goods::query()
|
||||
->with(['coupon'])
|
||||
->withCount(['carmis' => function($query) {
|
||||
$query->where('status', Carmis::STATUS_UNSOLD);
|
||||
}])->where('id', $id)->first();
|
||||
return $goods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化商品信息
|
||||
*
|
||||
* @param Goods $goods 商品模型
|
||||
* @return Goods
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function format(Goods $goods)
|
||||
{
|
||||
// 格式化批发配置以及输入框配置
|
||||
$goods->wholesale_price_cnf = $goods->wholesale_price_cnf ?
|
||||
format_wholesale_price($goods->wholesale_price_cnf) :
|
||||
null;
|
||||
// 如果存在其他配置输入框且为代充
|
||||
$goods->other_ipu = $goods->other_ipu_cnf ?
|
||||
format_charge_input($goods->other_ipu_cnf) :
|
||||
null;
|
||||
return $goods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证商品状态
|
||||
*
|
||||
* @param Goods $goods
|
||||
* @return Goods
|
||||
* @throws RuleValidationException
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function validatorGoodsStatus(Goods $goods): Goods
|
||||
{
|
||||
if (empty($goods)) {
|
||||
throw new RuleValidationException(__('dujiaoka.prompt.goods_does_not_exist'));
|
||||
}
|
||||
// 上架判断.
|
||||
if ($goods->is_open != Goods::STATUS_OPEN) {
|
||||
throw new RuleValidationException(__('dujiaoka.prompt.the_goods_is_not_on_the_shelves'));
|
||||
}
|
||||
return $goods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 库存减去
|
||||
*
|
||||
* @param int $id 商品id
|
||||
* @param int $number 出库数量
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function inStockDecr(int $id, int $number = 1): bool
|
||||
{
|
||||
return Goods::query()->where('id', $id)->decrement('in_stock', $number);
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品销量加
|
||||
*
|
||||
* @param int $id 商品id
|
||||
* @param int $number 数量
|
||||
* @return bool
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function salesVolumeIncr(int $id, int $number = 1): bool
|
||||
{
|
||||
return Goods::query()->where('id', $id)->increment('sales_volume', $number);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
<?php
|
||||
/**
|
||||
* The file was created by Assimon.
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @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<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function setGoods(Goods $goods)
|
||||
{
|
||||
$this->goods = $goods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置优惠码.
|
||||
*
|
||||
* @param ?Coupon $coupon
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function setCoupon(?Coupon $coupon)
|
||||
{
|
||||
$this->coupon = $coupon;
|
||||
}
|
||||
|
||||
/**
|
||||
* 其他输入框设置.
|
||||
*
|
||||
* @param ?string $otherIpt
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function setOtherIpt(?string $otherIpt)
|
||||
{
|
||||
$this->otherIpt = $otherIpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算优惠码价格
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
private function calculateTheCouponPrice(): float
|
||||
{
|
||||
$couponPrice = 0;
|
||||
// 优惠码优惠价格
|
||||
if ($this->coupon) {
|
||||
$couponPrice = $this->coupon->discount;
|
||||
}
|
||||
return $couponPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算批发优惠
|
||||
* @return float
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @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<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @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<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @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<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @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<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @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<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @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, '<br/>', $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<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @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('<br/>', $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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
/**
|
||||
* The file was created by Assimon.
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
|
||||
use App\Exceptions\RuleValidationException;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\Coupon;
|
||||
use App\Models\Goods;
|
||||
use App\Models\Carmis;
|
||||
use App\Models\Order;
|
||||
use App\Rules\SearchPwd;
|
||||
use App\Rules\VerifyImg;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class OrderService
|
||||
{
|
||||
|
||||
/**
|
||||
* 商品服务层.
|
||||
* @var \App\Service\PayService
|
||||
*/
|
||||
private $goodsService;
|
||||
|
||||
|
||||
/**
|
||||
* 优惠码服务层
|
||||
* @var \App\Service\CouponService
|
||||
*/
|
||||
private $couponService;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->goodsService = app('Service\GoodsService');
|
||||
$this->couponService = app('Service\CouponService');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证集合
|
||||
*
|
||||
* @param Request $request
|
||||
* @throws RuleValidationException
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function validatorCreateOrder(Request $request): void
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'gid' => 'required' ,
|
||||
'email' => ['required', 'email'],
|
||||
'payway' => ['required', 'integer'],
|
||||
'search_pwd' => [new SearchPwd()],
|
||||
'by_amount' => ['required', 'integer', 'min:1'],
|
||||
'img_verify_code' => [new VerifyImg()],
|
||||
], [
|
||||
'by_amount.required' => __('dujiaoka.prompt.buy_amount_format_error'),
|
||||
'by_amount.integer' => __('dujiaoka.prompt.buy_amount_format_error'),
|
||||
'by_amount.min' => __('dujiaoka.prompt.buy_amount_format_error'),
|
||||
'payway.required' => __('dujiaoka.prompt.please_select_mode_of_payment'),
|
||||
'payway.integer' => __('dujiaoka.prompt.please_select_mode_of_payment'),
|
||||
'email.required' => __('dujiaoka.prompt.email_format_error'),
|
||||
'email.email' => __('dujiaoka.prompt.email_format_error'),
|
||||
'gid.required' => __('dujiaoka.prompt.goods_does_not_exist'),
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
throw new RuleValidationException($validator->errors()->first());
|
||||
}
|
||||
// 极验验证
|
||||
if (
|
||||
dujiaoka_config_get('is_open_geetest') == BaseModel::STATUS_OPEN
|
||||
&&
|
||||
!Validator::make($request->all(),
|
||||
['geetest_challenge' => 'geetest',],
|
||||
[ 'geetest' => __('dujiaoka.prompt.geetest_validate_fail')])
|
||||
|
||||
) {
|
||||
throw new RuleValidationException(__('dujiaoka.prompt.geetest_validate_fail'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到商品详情并验证
|
||||
*
|
||||
* @param Request $request 请求
|
||||
* @throws RuleValidationException
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function validatorGoods(Request $request): Goods
|
||||
{
|
||||
// 获得商品详情
|
||||
$goods = $this->goodsService->detail($request->input('gid'));
|
||||
// 商品状态验证
|
||||
$this->goodsService->validatorGoodsStatus($goods);
|
||||
// 如果有限购
|
||||
if ($goods->buy_limit_num > 0 && $request->input('by_amount') > $goods->buy_limit_num) {
|
||||
throw new RuleValidationException(__('dujiaoka.prompt.purchase_limit_exceeded'));
|
||||
}
|
||||
// 库存不足
|
||||
if ($request->input('by_amount') > $goods->in_stock) {
|
||||
throw new RuleValidationException(__('dujiaoka.prompt.inventory_shortage'));
|
||||
}
|
||||
return $goods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有循环卡密
|
||||
*
|
||||
* @param int $goodsID 商品id
|
||||
* @return array|null
|
||||
*
|
||||
* @author ZhangYiQiu<me@zhangyiqiu.net>
|
||||
* @copyright ZhangYiQiu<me@zhangyiqiu.net>
|
||||
* @link http://zhangyiqiu.net/
|
||||
*/
|
||||
public function validatorLoopCarmis(Request $request)
|
||||
{
|
||||
$carmis = Carmis::query()
|
||||
->where('goods_id', $request->input('gid'))
|
||||
->where('status', Carmis::STATUS_UNSOLD)
|
||||
->where('is_loop', true)
|
||||
->count();
|
||||
if($carmis > 0 && $request->input('by_amount') > 1){
|
||||
throw new RuleValidationException(__('dujiaoka.prompt.loop_carmis_limit'));
|
||||
}
|
||||
return $carmis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优惠码验证
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Coupon|null
|
||||
* @throws RuleValidationException
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function validatorCoupon(Request $request):? Coupon
|
||||
{
|
||||
// 如果提交了优惠码
|
||||
if ($request->filled('coupon_code')) {
|
||||
// 查询优惠码是否存在
|
||||
$coupon = $this->couponService->withHasGoods($request->input('coupon_code'), $request->input('gid'));
|
||||
// 此商品没有这个优惠码
|
||||
if (empty($coupon)) {
|
||||
throw new RuleValidationException(__('dujiaoka.prompt.coupon_does_not_exist'));
|
||||
}
|
||||
// 剩余次数不足
|
||||
if ($coupon->ret <= 0) {
|
||||
throw new RuleValidationException(__('dujiaoka.prompt.coupon_lack_of_available_opportunities'));
|
||||
}
|
||||
return $coupon;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 代充框验证.
|
||||
*
|
||||
* @param Goods $goods
|
||||
* @param Request $request
|
||||
* @return string
|
||||
* @throws RuleValidationException
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function validatorChargeInput(Goods $goods, Request $request): string
|
||||
{
|
||||
$otherIpt = '';
|
||||
// 代充框验证
|
||||
if ($goods->type == Goods::MANUAL_PROCESSING && !empty($goods->other_ipu_cnf)) {
|
||||
// 如果有其他输入框 判断其他输入框内容 然后载入信息
|
||||
$formatIpt = format_charge_input($goods->other_ipu_cnf);
|
||||
foreach ($formatIpt as $item) {
|
||||
if ($item['rule'] && !$request->filled($item['field'])) {
|
||||
$errMessage = $item['desc'] . __('dujiaoka.prompt.can_not_be_empty');
|
||||
throw new RuleValidationException($errMessage);
|
||||
}
|
||||
$otherIpt .= $item['desc'].':'.$request->input($item['field']) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
return $otherIpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过订单号查询订单
|
||||
* @param string $orderSN
|
||||
* @return Order
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function detailOrderSN(string $orderSN):? Order
|
||||
{
|
||||
$order = Order::query()->with(['coupon', 'pay', 'goods'])->where('order_sn', $orderSN)->first();
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据订单号过期订单.
|
||||
*
|
||||
* @param string $orderSN
|
||||
* @return bool
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function expiredOrderSN(string $orderSN): bool
|
||||
{
|
||||
return Order::query()->where('order_sn', $orderSN)->update(['status' => Order::STATUS_EXPIRED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置订单优惠码已回退
|
||||
*
|
||||
* @param string $orderSN
|
||||
* @return bool
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function couponIsBack(string $orderSN): bool
|
||||
{
|
||||
return Order::query()->where('order_sn', $orderSN)->update(['coupon_ret_back' => Order::COUPON_BACK_OK]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过邮箱和查询密码查询
|
||||
*
|
||||
* @param string $email 邮箱
|
||||
* @param string $searchPwd 查询面面
|
||||
* @return array|\Illuminate\Database\Concerns\BuildsQueries[]|\Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Query\Builder[]|\Illuminate\Support\Collection
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function withEmailAndPassword(string $email, string $searchPwd = '')
|
||||
{
|
||||
return Order::query()
|
||||
->where('email', $email)
|
||||
->when(!empty($searchPwd), function ($query) use ($searchPwd) {
|
||||
$query->where('search_pwd', $searchPwd);
|
||||
})
|
||||
->orderBy('created_at', 'DESC')
|
||||
->take(5)
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过订单号集合查询
|
||||
*
|
||||
* @param array $orderSNS 订单号集合
|
||||
* @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Query\Builder[]|\Illuminate\Support\Collection
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function byOrderSNS(array $orderSNS)
|
||||
{
|
||||
return Order::query()
|
||||
->whereIn('order_sn', $orderSNS)
|
||||
->orderBy('created_at', 'DESC')
|
||||
->take(5)
|
||||
->get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* The file was created by Assimon.
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
|
||||
use App\Models\Pay;
|
||||
|
||||
class PayService
|
||||
{
|
||||
|
||||
/**
|
||||
* 加载支付网关
|
||||
*
|
||||
* @param string|int $payClient 支付场景客户端
|
||||
* @return array|null
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function pays(string $payClient = Pay::PAY_CLIENT_PC): ?array
|
||||
{
|
||||
$payGateway = Pay::query()
|
||||
->whereIn('pay_client', [$payClient, Pay::PAY_CLIENT_ALL])
|
||||
->where('is_open', Pay::STATUS_OPEN)
|
||||
->get();
|
||||
return $payGateway ? $payGateway->toArray() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过支付标识获得支付配置
|
||||
*
|
||||
* @param string $check 支付标识
|
||||
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function detailByCheck(string $check)
|
||||
{
|
||||
$gateway = Pay::query()
|
||||
->where('pay_check', $check)
|
||||
->where('is_open', Pay::STATUS_OPEN)
|
||||
->first();
|
||||
return $gateway;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询支付网关
|
||||
*
|
||||
* @param int $id 支付网关id
|
||||
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
|
||||
*
|
||||
* @author assimon<ashang@utf8.hk>
|
||||
* @copyright assimon<ashang@utf8.hk>
|
||||
* @link http://utf8.hk/
|
||||
*/
|
||||
public function detail(int $id)
|
||||
{
|
||||
$gateway = Pay::query()
|
||||
->where('id', $id)
|
||||
->where('is_open', Pay::STATUS_OPEN)
|
||||
->first();
|
||||
return $gateway;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user