[已解决] 优惠卷的规则验证以及多产品支持的实现方式寻求更好的思路]

下面的逻辑是我暂时想到的实现方式, 但是不能够优雅的实现需要的业务
而且在后续增加产品, 增加更多优惠, 一些列的验证会很复杂...
故此需求更好的思路

// 订单ID
$orderId = 1;

// 优惠劵ID
$couponId = 1;

// 最初的需要支付价格
$payPrice = 100;

// 已经优惠的索引, 存入表,用于后续查看订单都使用了哪些优惠
$disList = [];

// 优惠劵服务类
$coupon = Coupon:make($couponId);

// 获取年卡会员优惠信息
if($user['is_vip']) {
    $vipDis = UserVipSrv::dis($payPrice);
    // 向优惠索引插入年卡的折扣信息
    $disList['vip'] = $vipDis;
    // 年卡折扣后的价格
    $payPrice = $vipDis['price'];
    // 这里传入已使用年卡折扣后的价格, 用于优惠券计算最终支付价格  && 优惠劵与年互斥
    $coupon->vip($vipDis['price']);
}

// 更多优惠减价规则操作
// ...

// 这里向优惠劵类, 传入是否支持拼团的套餐, 用于优惠劵类验证: 是否与拼团互斥
if(!empty($isPink)) {
    $coupon->isPink();
}

if($coupon->isDis()) {
    $couponDis = $coupon->getDis();
    // 向优惠索引插入优惠劵的折扣信息
    $disList['coupon'] = $couponDis;
    // 优惠劵折扣后的价格, 这里是最终支付价格
    $payPrice = $couponDis['price'];
}

// 更新订单的折扣信息索引
if(!empty($disList) && Helper::isArray($disList)) {
    OrderSrv::update($orderId, ['pay_price' => $payPrice, 'dis_list' => json_encode($dis_list)]);
}

// 支付流程
pay($orderId);
本帖已被设为精华帖!
本帖由系统于 1年前 自动加精
最佳答案

我在贴最后一次。。。

优惠券,满减,打折,vip,都是优惠,只是类型不一样。

你说的管道,更好的使用场景是针对某个对象进行流式处理,比如创建订单,处理优惠,切换订单状态等。

这里就能看到区别,处理优惠是其中的某一步,你也可以说我这个管道就是处理优惠的!也可以,但是没有用!!

比如:我现在需要知道,到底哪个优惠活动力度最大,你通过管道是体现不出来的!这样讲能明白吗?

那下面的$maps变量里面存储的就是个个优惠的优惠价格,我直接拿出来就可以比较了!我觉得这样更灵活一些,不要过度设计!!!!!!!!!

<?php

// 优惠处理对象。
class PromotionManger
{
    protected $promotions = [];

    public function add($promotion)
    {
        $this->promotions[] = $promotion;
    }

    // 针对order进行处理。
    public function dicount(OrderInterface $order, $payload=[])
    {
        //返回所有折扣
        $maps = [];
        foreach($promotions as $promotion)
        {
            if ($dicount = $promotion->dicount($order, $payload))
            {
                $mpas[get_class($promotion)] = $discount;
            }
        }
    }
}

class OrderProcessor
{
    protected $promotionMgr;

    public function __construct()
    {
        $this->promotionMgr = new PromotionManger();
    }

    public function process(OrderInterface $order, $payload=[])
    {
        //total 是订单原价,不需要在这里处理什么东西。
        //dicount 是订单优惠后的价格。
        $this->promotionMgr->add(new VipPromion());
        $this->promotionMgr->add(new SomePromion());
        $dicounts = $this->promotionMgr->discount($order, $payload);

        // 这里就是可以处理,无非就是把折扣加起来 然后使用 order->total() - 折扣价格 = 实际支付价格!
    }
}

//打个比方
class VipPromion extends AbcPromion
{
    public function dicount($order, $paylaod)
    {
        // user 应该从订单获取. 传进来也可以。
        $user = $oder->getUser();
        //or
        $user = request()->user();
        //or
        $user = $payload['user'];

        if ( isvip($user) )
        {
            //这里是你处理vip价格的地方.
            //比如vip用户打8折
            return $order->total() * .08; //只返回折扣后的价格!!!
        }
    }
}

//所以在service中的业务代码应该是这样

public function handleOrder()
{
    $order = Order::find(1);

    $orderProcessor = new OrderProcessor();

    $ret = $orderProcessor->process($order);

    return $ret;
}
4年前 评论
isszz (楼主) 4年前
讨论数量: 8
<?php

//ps, 我是在群里看到你发的,所以我过来帮你解答一下!
//我只写大概方法,优惠券最简单的方式就是使用策略模式。

class Order
{
    protected $user;
    protected $car;
    protected $promotion;
    protected $_total;

    public function __construct($user, $car, $promotion)
    {
        $this->user = $user;
        $this->car = $car;
        $this->promotion = $promotion;
    }

    public function total()
    {
        if ( !$this->total)
        {
            $this->_total = array_reduce($this->car->items(), function($first, $second)
            {
                return $first->price() + $second->price();
            });
        }

        return $this->_total;
    }

    public function discount()
    {
        return $this->total() - $this->promotion->discount($this);
    }
}

interface Promotion
{
    public function discount(Order $order);
}

//会员vip折扣
class VipPromotion implements Promotion
{

    public function discount(Order $order)
    {
        if ( $order->user->isVip() )
        {
            //
        }
    }
}

//优惠券折扣
class CodePromotion implements Promotion
{
    public function discount(Order $order)
    {
        $coupon = Coupon:make($order->coupon_id);

        //balabala!!!
    }
}
4年前 评论
isszz (楼主) 4年前
isszz (楼主) 4年前
tangq 4年前

感谢果大王的思路
file

4年前 评论

动态添加折扣的场景

class Order
{
    protected $user;
    protected $car;
    protected $promotion = []; //最简单用数组,也可以抽象一个manger对象。
    protected $_total;

    public function __construct($user, $car, $promotion=[])
    {
        $this->user = $user;
        $this->car = $car;
        $this->promotion = $promotion;
    }

    public function total()
    {
        if ( !$this->total)
        {
            $this->_total = array_reduce($this->car->items(), function($first, $second)
            {
                return $first->price() + $second->price();
            });
        }

        return $this->_total;
    }

    public function discount()
    {
        $maps = [];

        foreach( $this->promotion as $promotion)
        {
            $maps[$promotion->ident] = $promotion->discount($this);
        }

        // maps 就是所有折扣
    }

    public function addPromotion(Promotion $promotion)
    {
        $this->promotion[] = $promotion;
    }
}
4年前 评论

@果实王 老哥直接贴代码厉害了。
我记得我之前是写了一个抽象工厂类。有优惠券就去处理优惠券,有打折去处理打折,有满减就去处理满减,有返现就去处理返现。当时就这样子实现了。

4年前 评论
果实王 4年前

@果实王 这样写不对吧- -...感觉有点饶...是不是我理解错了... :sweat_smile:

class PromotionDiscountSrv
{
    public $order;
    public $user;
    // protected $promotion;
    protected $promotions = [];
    protected $_total;

    public function __construct($order = [], $uid/*, $promotion*/)
    {
        $this->order = $order ?: [];
        $this->user = $this->user ?: UserSrv::get($uid, ['uid', 'username', 'is_vip']);
        // $this->promotion = $promotion;
    }

    public function total()
    {
        if ( !$this->_total)
        {
            $this->_total = array_reduce($this->discount(), function($first, $second)
            {

                if (!empty($first))
                {

                    if (!empty($second['is_dis']))
                    {
                        return bcadd($first, $second['price'], 2);
                    }
                    return $first;
                }

                if ($second['is_dis'] === true)
                {
                    return $second['price'];
                }
            });
        }

        return $this->_total;
    }

    public function discount()
    {
        $maps = [];

        foreach( $this->promotions as $promotion)
        {
            $maps[$promotion->ident] = $promotion->discount($this);
        }

        return $maps;
    }

    public function addPromotion(Promotion $promotion)
    {
        $this->promotions[] = $promotion;
    }
}

abstract class PromotionManger
{
    protected $promotions = [];
}

interface Promotion
{
    public function discount(PromotionDiscountSrv $promotion);
    public function getDiscount();
}

//会员vip折扣
class VipPromotion implements Promotion
{

    public $ident = 'vip';
    public $disPrice = 0;

    public function __construct($id)
    {
        $this->id = $id;
    }

    public function discount(PromotionDiscountSrv $promotion)
    {

        if (!empty($promotion->user['is_vip']))
        {

            $rule = [
                'type' => 1,
                'cost' => 0,
                'price' => 9.8,
            ];

            $rule['price'] = (float)$rule['price'];
            $rule['cost'] = (float)$rule['cost'];

            if (empty($rule))
            {
                return [];
            }

            if (!empty($rule['cost']))
            {
                if ($rule['type'])
                {
                    // 折扣错误
                    if ($rule['cost'] > 9.9 || $rule['cost'] < 0.1)
                    {
                        return [];
                    }
                    // 折扣时的满减要求
                    if ($promotion->order['price'] < $rule['cost'])
                    {
                        return [];
                    }
                }
                else
                {
                    // 立减时的价格要求
                    if ($promotion->order['price'] > $rule['cost'])
                    {
                        return [];
                    }
                }
            }

            // 立减
            $discount = $rule['price'];

            // 计算价格
            if ($rule['type'])
            {
                $price = bcmul($promotion->order['price'], $rule['price'] / 10, 2);
                $discount = bcsub($promotion->order['price'], $price, 2); ;
            } else
            {
                $price = bcsub($promotion->order['price'], $discount, 2);
            }

            $this->disPrice = $discount;

            return ['is_dis' => true, 'price' => $price,  'discount' => $discount];
        }
    }
    public function getDiscount()
    {
        return $this->disPrice;
    }
}

//优惠券折扣
class CouponPromotion implements Promotion
{

    public $ident = 'coupon';

    public $disPrice = 0;

    public function __construct($id)
    {
        $this->id = $id;
    }

    public function discount(PromotionDiscountSrv $promotion)
    {
        // $coupon = Coupon::make($promotion->data['coupon_id']);

        //balabala!!!
    }
    public function getDiscount()
    {
        return $this->disPrice;
    }
}

$PromotionDiscountSrv = new PromotionDiscountSrv([
    'oid' => 1,
    'price' => 99,
], 2);

$PromotionDiscountSrv->addPromotion(new VipPromotion(1));
$PromotionDiscountSrv->addPromotion(new CouponPromotion(2));

$discountList = $PromotionDiscountSrv->total();
4年前 评论

我在贴最后一次。。。

优惠券,满减,打折,vip,都是优惠,只是类型不一样。

你说的管道,更好的使用场景是针对某个对象进行流式处理,比如创建订单,处理优惠,切换订单状态等。

这里就能看到区别,处理优惠是其中的某一步,你也可以说我这个管道就是处理优惠的!也可以,但是没有用!!

比如:我现在需要知道,到底哪个优惠活动力度最大,你通过管道是体现不出来的!这样讲能明白吗?

那下面的$maps变量里面存储的就是个个优惠的优惠价格,我直接拿出来就可以比较了!我觉得这样更灵活一些,不要过度设计!!!!!!!!!

<?php

// 优惠处理对象。
class PromotionManger
{
    protected $promotions = [];

    public function add($promotion)
    {
        $this->promotions[] = $promotion;
    }

    // 针对order进行处理。
    public function dicount(OrderInterface $order, $payload=[])
    {
        //返回所有折扣
        $maps = [];
        foreach($promotions as $promotion)
        {
            if ($dicount = $promotion->dicount($order, $payload))
            {
                $mpas[get_class($promotion)] = $discount;
            }
        }
    }
}

class OrderProcessor
{
    protected $promotionMgr;

    public function __construct()
    {
        $this->promotionMgr = new PromotionManger();
    }

    public function process(OrderInterface $order, $payload=[])
    {
        //total 是订单原价,不需要在这里处理什么东西。
        //dicount 是订单优惠后的价格。
        $this->promotionMgr->add(new VipPromion());
        $this->promotionMgr->add(new SomePromion());
        $dicounts = $this->promotionMgr->discount($order, $payload);

        // 这里就是可以处理,无非就是把折扣加起来 然后使用 order->total() - 折扣价格 = 实际支付价格!
    }
}

//打个比方
class VipPromion extends AbcPromion
{
    public function dicount($order, $paylaod)
    {
        // user 应该从订单获取. 传进来也可以。
        $user = $oder->getUser();
        //or
        $user = request()->user();
        //or
        $user = $payload['user'];

        if ( isvip($user) )
        {
            //这里是你处理vip价格的地方.
            //比如vip用户打8折
            return $order->total() * .08; //只返回折扣后的价格!!!
        }
    }
}

//所以在service中的业务代码应该是这样

public function handleOrder()
{
    $order = Order::find(1);

    $orderProcessor = new OrderProcessor();

    $ret = $orderProcessor->process($order);

    return $ret;
}
4年前 评论
isszz (楼主) 4年前

暂时根据 @果大王的代码写了一下自己实现, 效果基本达到需求, 这里再次感谢@果大王

下面放出我这边根据自己的业务逻辑的代码, 有需要的可以参考下

promotion\Order.php

<?php

namespace App\Libs\Promotion;

class Order {

    public $user;
    public $order;

    public function __construct(array $order = [], $id = null)
    {

        if (empty($order) && !empty($id)) {
            $this->order = $id ? \ActivityOrderSrv::get($id) : [];
        }

        $this->order = $order ?? $this->order;

        $this->user = $this->user ?: \UserSrv::get($this->order['uid']);

    }

    public static function get($id = null, array $order = []) {
        return new static($id, $order);
    }

    public function getUser() {
        return $this->user;

    }

    public function total() {
        return $this->order['price'];

    }
}

promotion\OrderProcessor.php

<?php

namespace App\Libs\Promotion;

class OrderProcessor
{
    protected $promotionMgr;
    protected $discount;

    public function __construct()
    {
        $this->promotionMgr = new PromotionManger();
    }

    public function process(Order $order, $payload = [])
    {
        //total 是订单原价,不需要在这里处理什么东西。
        //dicount 是订单优惠后的价格。

        foreach ($this->discount as $discount) {
            $this->promotionMgr->add($discount);
        }

        // $this->promotionMgr->add(new vip());
        // $this->promotionMgr->add(new coupon());

        $dicounts = $this->promotionMgr->discount($order, $payload);

        $totalDiscount = array_reduce($dicounts, function($first, $second) {
            return bcadd($first, $second, 2);
        });

        // 这里就是可以处理,无非就是把折扣加起来 然后使用 order->total() - 折扣价格 = 实际支付价格!
        $total = bcsub($order->total(), $totalDiscount, 2);

        pr($total);
    }

    public function addDiscount($discount) {
        $this->discount[] = $discount;
    }
}

promotion\PromotionManger.php

<?php

namespace App\Libs\Promotion;

class PromotionManger
{
    protected $promotions = [];

    public function add($promotion)
    {
        $this->promotions[] = $promotion;
    }

    // 针对order进行处理。
    public function discount(Order $order, $payload = [])
    {
        // 返回所有折扣
        $maps = [];
        foreach($this->promotions as $promotion)
        {
            if ($discount = $promotion->discount($order, $payload))
            {
                $mpas[get_class($promotion)] = $discount;
            }
        }

        return $mpas;
    }
}

promotion\Discount.php

<?php

namespace App\Libs\Promotion;

interface Discount {
    public function discount(Order $order, $paylaod);
}

promotion\Discount\Coupon.php

<?php

namespace App\Libs\Promotion\Discount;

use App\Libs\Promotion\Discount;

class Coupon implements Discount
{
    public function discount($order, $paylaod)
    {
        // user 应该从订单获取. 传进来也可以。
        $user = $order->getUser();

        //这里是你处理vip价格的地方.
        //比如vip用户打8折

        $price = bcmul($order->total(), 9.8 / 10, 2);
        $discount = bcsub($order->total(), $price);

        return $discount;
    }
}

promotion\Discount\Vip.php

<?php

namespace App\Libs\Promotion\Discount;

use App\Libs\Promotion\Discount;

class Vip implements Discount
{
    public function discount($order, $paylaod)
    {
        // user 应该从订单获取. 传进来也可以。
        $user = $order->getUser();

        if ($user['is_vip'])
        {
            //这里是你处理vip价格的地方.
            //比如vip用户打8折

            $price = bcmul($order->total(), 9.8 / 10, 2);

            $discount = bcsub($order->total(), $price);

            return $discount; //只返回折扣后的价格!!!
        }
    }
}

获取优惠

use App\Libs\Promotion\Order;
use App\Libs\Promotion\OrderProcessor;

use App\Libs\Promotion\Discount\Vip;
use App\Libs\Promotion\Discount\Coupon;

$order = Order::get(3186);

$orderProcessor = new OrderProcessor();

// 这里根据业务逻辑动态增加相应的优惠
$orderProcessor->addDiscount(new Vip());
$orderProcessor->addDiscount(new Coupon());

$ret = $orderProcessor->process($order);
pr($ret);

暂时这样, 还没有把自己的业务逻辑加进来, 后面实现功能应该差不多也在这个基础上改动了

根据@果大王的代码, 实现的效果, 美观, 通用,完美达到预期的功能效果

4年前 评论
Aliliin 4年前

@Ali ,是的优惠规则在promotion\Discount下面的类处理,只是这里没有加入业务逻辑
如果传递$paylaod['rule']也是后端取的比如套餐内的年卡优惠, 后端前置获取, 或者传递一个优惠规则的数据id在这里取rule

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!