Explorar o código

添加用户会员

llp hai 4 días
pai
achega
de41b42a0d
Modificáronse 27 ficheiros con 818 adicións e 15 borrados
  1. 1 1
      bootstrap/providers.php
  2. 1 1
      composer.json
  3. 8 8
      packages/Longyi/Gift/src/Listeners/InvoiceHandler.php
  4. 1 1
      packages/Longyi/Gift/src/Providers/EventServiceProvider.php
  5. 4 1
      packages/Longyi/Gift/src/Repositories/CustomInvoiceRepository.php
  6. 21 0
      packages/Longyi/Gift/src/Resources/views/sales/invoices/view.blade.php
  7. 4 1
      packages/Longyi/Gift/src/Resources/views/sales/orders/view.blade.php
  8. 7 0
      packages/Longyi/Member/src/Contracts/Member.php
  9. 99 0
      packages/Longyi/Member/src/Http/Controllers/Shop/MemberDiscountController.php
  10. 25 0
      packages/Longyi/Member/src/Listeners/InvoicesViewHandler.php
  11. 47 0
      packages/Longyi/Member/src/Listeners/MemberHandler.php
  12. 65 0
      packages/Longyi/Member/src/Listeners/OrderPlacedHandler.php
  13. 28 0
      packages/Longyi/Member/src/Listeners/OrderViewHandler.php
  14. 96 0
      packages/Longyi/Member/src/Listeners/VipDiscountHandler.php
  15. 30 0
      packages/Longyi/Member/src/Models/Member.php
  16. 9 0
      packages/Longyi/Member/src/Models/MemberProxy.php
  17. 34 0
      packages/Longyi/Member/src/Providers/EventServiceProvider.php
  18. 3 1
      packages/Longyi/Member/src/Providers/MemberServiceProvider.php
  19. 9 0
      packages/Longyi/Member/src/Resources/lang/en/app.php
  20. 9 0
      packages/Longyi/Member/src/Resources/lang/zh_CN/app.php
  21. 16 0
      packages/Longyi/Member/src/Resources/views/components/member-discount-cartsummary.blade.php
  22. 183 0
      packages/Longyi/Member/src/Resources/views/components/vip-member-discount-cartsummary.blade.php
  23. 22 0
      packages/Longyi/Member/src/Resources/views/customers/account/orders/member-info.blade.php
  24. 29 0
      packages/Longyi/Member/src/Resources/views/sales/orders/view.blade.php
  25. 7 1
      packages/Longyi/Member/src/Routes/shop-routes.php
  26. 9 0
      packages/Webkul/Sales/src/Transformers/OrderResource.php
  27. 51 0
      packages/Webkul/Shop/src/Http/Resources/CartResource.php

+ 1 - 1
bootstrap/providers.php

@@ -13,8 +13,8 @@ return [
     Longyi\Core\Providers\LongyiCoreServiceProvider::class,
     Longyi\DynamicMenu\Providers\DynamicMenuServiceProvider::class,
     Longyi\RewardPoints\Providers\RewardPointsServiceProvider::class,
-    Longyi\Gift\Providers\GiftServiceProvider::class,
     Longyi\Member\Providers\MemberServiceProvider::class,
+    Longyi\Gift\Providers\GiftServiceProvider::class,
     Webkul\Attribute\Providers\AttributeServiceProvider::class,
     Webkul\BookingProduct\Providers\BookingProductServiceProvider::class,
     Webkul\CMS\Providers\CMSServiceProvider::class,

+ 1 - 1
composer.json

@@ -69,8 +69,8 @@
             "Longyi\\Core\\": "packages/Longyi/Core/src/",
             "Longyi\\DynamicMenu\\": "packages/Longyi/DynamicMenu/src/",
             "Longyi\\RewardPoints\\": "packages/Longyi/RewardPoints/src/",
-            "Longyi\\Gift\\": "packages/Longyi/Gift/src/",
             "Longyi\\Member\\": "packages/Longyi/Member/src/",
+            "Longyi\\Gift\\": "packages/Longyi/Gift/src/",
             "Webkul\\Admin\\": "packages/Webkul/Admin/src",
             "Webkul\\Attribute\\": "packages/Webkul/Attribute/src",
             "Webkul\\BookingProduct\\": "packages/Webkul/BookingProduct/src",

+ 8 - 8
packages/Longyi/Gift/src/Listeners/InvoiceHandler.php

@@ -21,13 +21,13 @@ class InvoiceHandler
             return;
         }
 
-        // 更新发票的礼品卡信息
-        DB::table('invoices')
-            ->where('id', $invoice->id)
-            ->update([
-                'giftcard_number' => $order->giftcard_number,
-                'giftcard_amount' => $order->giftcard_amount,
-                'base_giftcard_amount' => $order->base_giftcard_amount,
-            ]);
+//        // 更新发票的礼品卡信息
+//        DB::table('invoices')
+//            ->where('id', $invoice->id)
+//            ->update([
+//                'giftcard_number' => $order->giftcard_number,
+//                'giftcard_amount' => $order->giftcard_amount,
+//                'base_giftcard_amount' => $order->base_giftcard_amount,
+//            ]);
     }
 }

+ 1 - 1
packages/Longyi/Gift/src/Providers/EventServiceProvider.php

@@ -16,7 +16,7 @@ class EventServiceProvider extends ServiceProvider
     {
         Event::listen('checkout.cart.collect.totals.after', 'Longyi\Gift\Listeners\GiftHandler@applyGiftCard');
         Event::listen('checkout.order.save.after', 'Longyi\Gift\Listeners\OrderPlacedHandler@afterPlaceOrder');
-        Event::listen('bagisto.shop.checkout.onepage.summary.coupon.after', function($viewRenderEventManager) {
+        Event::listen('bagisto.shop.checkout.cart.summary.member_discount.after', function($viewRenderEventManager) {
             $viewRenderEventManager->addTemplate('gift::components.giftcard-cartsummary');
         });
         Event::listen('bagisto.shop.customers.account.orders.view.information.discount.after', 'Longyi\Gift\Listeners\OrderViewHandler@renderGiftCardInfo');

+ 4 - 1
packages/Longyi/Gift/src/Repositories/CustomInvoiceRepository.php

@@ -2,6 +2,7 @@
 
 namespace Longyi\Gift\Repositories;
 
+use Illuminate\Support\Facades\Event;
 use Webkul\Sales\Repositories\InvoiceRepository as BaseInvoiceRepository;
 
 class CustomInvoiceRepository extends BaseInvoiceRepository
@@ -13,6 +14,8 @@ class CustomInvoiceRepository extends BaseInvoiceRepository
         $invoice->tax_amount = $invoice->base_tax_amount = 0;
         $invoice->shipping_tax_amount = $invoice->shipping_tax_amount = 0;
         $invoice->discount_amount = $invoice->base_discount_amount = 0;
+        $invoice->giftcard_amount = $invoice->giftcard_amount = 0;
+        $invoice->base_giftcard_amount = $invoice->base_giftcard_amount = 0;
 
         foreach ($invoice->items as $item) {
             $invoice->tax_amount += $item->tax_amount;
@@ -79,7 +82,7 @@ class CustomInvoiceRepository extends BaseInvoiceRepository
         $invoice->base_grand_total = $invoice->base_sub_total + $invoice->base_tax_amount + $invoice->base_shipping_amount - $invoice->base_discount_amount - $invoice->base_giftcard_amount;
 
         $invoice->save();
-
+//        Event::dispatch('order.invoice.collect.totals.after', $invoice);
         return $invoice;
     }
 }

+ 21 - 0
packages/Longyi/Gift/src/Resources/views/sales/invoices/view.blade.php

@@ -297,6 +297,7 @@
                                 @lang('admin::app.sales.invoices.view.summary-discount')
                             </p>
                         @endif
+
                         @if ($order->giftcard_amount > 0)
                             <p class="text-gray-600 dark:text-gray-300 !leading-5 text-sm">
                                 @lang('gift::app.giftcard.giftcard_amount')
@@ -308,6 +309,16 @@
                                 @lang('gift::app.giftcard.giftcard_number')
                             </p>
                         @endif
+                        @if ($order->base_vip_plus_amount > 0)
+                            <p class="text-gray-600 dark:text-gray-300 !leading-5 text-sm">
+                                @lang('member::app.member.discount')
+                            </p>
+                        @endif
+                        @if ($order->base_vip_discount_amount > 0)
+                            <p class="text-gray-600 dark:text-gray-300 !leading-5 text-sm">
+                                @lang('member::app.member.vip_discount')
+                            </p>
+                        @endif
                         <p class="text-base font-semibold !leading-5 text-gray-800 dark:text-white">
                             @lang('admin::app.sales.invoices.view.grand-total')
                         </p>
@@ -377,6 +388,16 @@
                                 {{ ($invoice->giftcard_number) }}
                             </p>
                         @endif
+                        @if ($order->base_vip_plus_amount > 0)
+                            <p class="text-gray-600 dark:text-gray-300 !leading-5">
+                                +  {{ core()->formatBasePrice($invoice->base_vip_plus_amount) }}
+                            </p>
+                        @endif
+                        @if ($order->base_vip_discount_amount > 0)
+                            <p class="text-gray-600 dark:text-gray-300 !leading-5">
+                                -  {{ core()->formatBasePrice($invoice->base_vip_discount_amount) }}
+                            </p>
+                        @endif
                         <!-- Grand Total -->
                         <p class="text-base font-semibold !leading-5 text-gray-800 dark:text-white">
                             {{ core()->formatBasePrice($invoice->base_grand_total) }}

+ 4 - 1
packages/Longyi/Gift/src/Resources/views/sales/orders/view.blade.php

@@ -390,6 +390,9 @@
 
                             {!! view_render_event('bagisto.admin.sales.order.view.discount.before') !!}
 
+                            {!! view_render_event('bagisto.admin.sales.order.view.discount.after') !!}
+
+                            {!! view_render_event('bagisto.admin.sales.order.view.giftcard.before', ['order' => $order]) !!}
                             <!-- Discount -->
                             <div class="flex justify-between w-full gap-x-5">
                                 <p class="!leading-5 text-gray-600 dark:text-gray-300">
@@ -416,7 +419,7 @@
                                 </div>
                             @endif
 
-                            {!! view_render_event('bagisto.admin.sales.order.view.discount.after') !!}
+                            {!! view_render_event('bagisto.admin.sales.order.view.giftcard.after') !!}
 
                             {!! view_render_event('bagisto.admin.sales.order.view.grand-total.before') !!}
 

+ 7 - 0
packages/Longyi/Member/src/Contracts/Member.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Longyi\Member\Contracts;
+
+interface Member
+{
+}

+ 99 - 0
packages/Longyi/Member/src/Http/Controllers/Shop/MemberDiscountController.php

@@ -0,0 +1,99 @@
+<?php
+
+namespace Longyi\Member\Http\Controllers\Shop;
+
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Response;
+use Webkul\Checkout\Facades\Cart;
+use Webkul\Shop\Http\Controllers\Controller;
+
+class MemberDiscountController extends Controller
+{
+    protected const MEMBER_DISCOUNT_AMOUNT = 19.9;
+
+    /**
+     * Apply member discount to cart.
+     */
+    public function apply(): JsonResponse
+    {
+        try {
+            if (!auth()->guard('customer')->check()) {
+                return response()->json([
+                    'success' => false,
+                    'message' => 'Please log in to your member account first',
+                ], Response::HTTP_UNAUTHORIZED);
+            }
+
+            $cart = Cart::getCart();
+
+            if (!$cart) {
+                return response()->json([
+                    'success' => false,
+                    'message' => 'Shopping cart is empty',
+                ], Response::HTTP_NOT_FOUND);
+            }
+
+            if ($cart->vip_plus_amount && $cart->vip_plus_amount > 0) {
+                return response()->json([
+                    'success' => false,
+                    'message' => 'Member discount has been applied',
+                ], Response::HTTP_OK);
+            }
+
+            $discountAmountInCurrentCurrency = core()->convertPrice(self::MEMBER_DISCOUNT_AMOUNT);
+
+            $cart->vip_plus_amount = $discountAmountInCurrentCurrency;
+            $cart->base_vip_plus_amount = core()->convertToBasePrice($discountAmountInCurrentCurrency);
+            $cart->save();
+            Cart::collectTotals();
+
+            return response()->json([
+                'success' => true,
+                'message' => 'Member discount has been applied',
+                'discount_amount' => $cart->vip_plus_amount,
+            ]);
+        } catch (\Exception $e) {
+            return response()->json([
+                'success' => false,
+                'message' => 'Failed to apply member discount',
+                'error' => $e->getMessage(),
+            ], Response::HTTP_INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    /**
+     * Remove member discount from cart.
+     */
+    public function remove(): JsonResponse
+    {
+        try {
+            $cart = Cart::getCart();
+
+            if (!$cart || !$cart->vip_plus_amount || $cart->vip_plus_amount <= 0) {
+                return response()->json([
+                    'success' => false,
+                    'message' => 'Member discount not applied',
+                ], Response::HTTP_OK);
+            }
+
+            $discountAmount = $cart->vip_plus_amount;
+            $baseDiscountAmount = $cart->base_vip_plus_amount;
+
+            $cart->vip_plus_amount = null;
+            $cart->base_vip_plus_amount = null;
+            $cart->save();
+            Cart::collectTotals();
+
+            return response()->json([
+                'success' => true,
+                'message' => 'Member discount has been removed',
+            ]);
+        } catch (\Exception $e) {
+            return response()->json([
+                'success' => false,
+                'message' => 'Failed to remove member discount',
+                'error' => $e->getMessage(),
+            ], Response::HTTP_INTERNAL_SERVER_ERROR);
+        }
+    }
+}

+ 25 - 0
packages/Longyi/Member/src/Listeners/InvoicesViewHandler.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Longyi\Member\Listeners;
+
+use Webkul\Sales\Models\Order;
+
+class InvoicesViewHandler
+{
+    /**
+     * Handle order view information discount before event.
+     *
+     * @param  array  $data
+     * @return void
+     */
+    public function applyMemberDiscount($invoice)
+    {
+        $invoice->vip_plus_amount += $invoice->order->vip_plus_amount;
+        $invoice->base_vip_plus_amount += $invoice->order->base_vip_plus_amount;
+        $invoice->vip_discount_amount += $invoice->order->vip_discount_amount;
+        $invoice->base_vip_discount_amount += $invoice->order->base_vip_discount_amount;
+        $invoice->grand_total = $invoice->grand_total + $invoice->vip_plus_amount - $invoice->vip_discount_amount;
+        $invoice->base_grand_total = $invoice->base_grand_total + $invoice->base_vip_plus_amount- $invoice->base_vip_discount_amount;
+        $invoice->save();
+    }
+}

+ 47 - 0
packages/Longyi/Member/src/Listeners/MemberHandler.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace Longyi\Member\Listeners;
+
+use Webkul\Paypal\Payment\SmartButton;
+use Webkul\Sales\Repositories\OrderTransactionRepository;
+
+class MemberHandler
+{
+    protected const MEMBER_DISCOUNT_AMOUNT = 19.9;
+    /**
+     * Create a new listener instance.
+     *
+     * @return void
+     */
+    public function __construct(
+        protected SmartButton $smartButton,
+        protected OrderTransactionRepository $orderTransactionRepository
+    ) {}
+
+    /**
+     * Apply member discount to cart.
+     *
+     * @param  \Webkul\Checkout\Models\Cart  $cart
+     * @return void
+     */
+    public function applyMemberDiscount($cart)
+    {
+
+        if (!$cart || !auth()->guard('customer')->check()) {
+            return;
+        }
+        if ($cart->vip_plus_amount <= 0) {
+            return;
+        }
+        $discountAmountInCurrentCurrency = core()->convertPrice(self::MEMBER_DISCOUNT_AMOUNT);
+
+        $cart->vip_plus_amount = $discountAmountInCurrentCurrency;
+        $cart->base_vip_plus_amount = core()->convertToBasePrice($discountAmountInCurrentCurrency);
+
+        $cart->grand_total = max(0, round($cart->grand_total + $discountAmountInCurrentCurrency, 2));
+        $cart->base_grand_total = max(0, round($cart->base_grand_total + $cart->base_vip_plus_amount, 2));
+
+        $cart->save();
+    }
+
+}

+ 65 - 0
packages/Longyi/Member/src/Listeners/OrderPlacedHandler.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace Longyi\Member\Listeners;
+
+use Webkul\Paypal\Payment\SmartButton;
+use Webkul\Sales\Repositories\OrderTransactionRepository;
+use Webkul\Customer\Models\Customer;
+use Carbon\Carbon;
+
+class OrderPlacedHandler
+{
+    protected const MEMBER_DISCOUNT_AMOUNT = 19.9;
+
+
+    protected const VIP_EXTENSION_DAYS = 180;
+    /**
+     * Create a new listener instance.
+     *
+     * @return void
+     */
+    public function __construct(
+        protected SmartButton $smartButton,
+        protected OrderTransactionRepository $orderTransactionRepository
+    ) {}
+
+    /**
+     * Apply member discount to cart.
+     *
+     * @param  \Webkul\Checkout\Models\Cart  $cart
+     * @return void
+     */
+    public function afterPlaceOrder($order)
+    {
+        if (!$order || !$order->customer_id) {
+            return;
+        }
+
+        // 检查订单是否应用了会员优惠
+        if (!$order->vip_plus_amount || $order->vip_plus_amount <= 0) {
+            return;
+        }
+
+        // 获取客户信息
+        $customer = Customer::find($order->customer_id);
+
+        if (!$customer) {
+            return;
+        }
+
+        // 计算新的过期时间
+        $currentExpireDate = $customer->vip_expire_date ? Carbon::parse($customer->vip_expire_date) : now();
+
+        // 如果已过期,从当前时间开始计算;如果未过期,从过期时间延续
+        if ($currentExpireDate->isPast()) {
+            $newExpireDate = now()->addDays(self::VIP_EXTENSION_DAYS);
+        } else {
+            $newExpireDate = $currentExpireDate->addDays(self::VIP_EXTENSION_DAYS);
+        }
+
+        // 更新客户的 VIP 过期时间
+        $customer->vip_expire_date = $newExpireDate;
+        $customer->save();
+    }
+
+}

+ 28 - 0
packages/Longyi/Member/src/Listeners/OrderViewHandler.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Longyi\Member\Listeners;
+
+use Webkul\Sales\Models\Order;
+
+class OrderViewHandler
+{
+    /**
+     * Handle order view information discount before event.
+     *
+     * @param  array  $data
+     * @return void
+     */
+    public function renderMemberInfo($viewRenderEventManager)
+    {
+        // 从当前路由参数中获取订单ID
+        $orderId = request()->route('id');
+
+        if (!$orderId) {
+            return;
+        }
+
+        // 查询订单
+        $order = Order::find($orderId);
+        echo view('member::customers.account.orders.member-info', compact('order'))->render();
+    }
+}

+ 96 - 0
packages/Longyi/Member/src/Listeners/VipDiscountHandler.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace Longyi\Member\Listeners;
+
+use Carbon\Carbon;
+
+class VipDiscountHandler
+{
+    protected const VIP_DISCOUNT_PERCENTAGE = 0.03; // 3%
+
+    /**
+     * Apply VIP discount to cart.
+     *
+     * @param  \Webkul\Checkout\Models\Cart  $cart
+     * @return void
+     */
+    public function applyVipDiscount($cart)
+    {
+        if (!$cart || !auth()->guard('customer')->check()) {
+            return;
+        }
+
+        $customer = auth()->guard('customer')->user();
+
+        // 检查是否是有效 VIP
+        if (!$this->isValidVip($customer)) {
+            // 如果不是有效 VIP,清除可能存在的 VIP 折扣
+            if ($cart->vip_discount_amount && $cart->vip_discount_amount > 0) {
+                $this->removeVipDiscount($cart);
+            }
+            return;
+        }
+
+        // 计算购物车小计
+        $subTotal = $cart->sub_total;
+
+        if ($subTotal <= 0) {
+            return;
+        }
+
+        // 计算 3% 折扣
+        $discountAmountInCurrentCurrency = round($subTotal * self::VIP_DISCOUNT_PERCENTAGE, 2);
+        $baseDiscountAmount = core()->convertToBasePrice($discountAmountInCurrentCurrency);
+        // 应用新的 VIP 折扣
+        $cart->vip_discount_amount = $discountAmountInCurrentCurrency;
+        $cart->base_vip_discount_amount = $baseDiscountAmount;
+
+        $cart->grand_total = max(0, round($cart->grand_total - $discountAmountInCurrentCurrency, 2));
+        $cart->base_grand_total = max(0, round($cart->base_grand_total - $baseDiscountAmount, 2));
+
+        $cart->save();
+    }
+
+    /**
+     * Remove VIP discount from cart.
+     *
+     * @param  \Webkul\Checkout\Models\Cart  $cart
+     * @return void
+     */
+    public function removeVipDiscount($cart)
+    {
+        if (!$cart || !$cart->vip_discount_amount || $cart->vip_discount_amount <= 0) {
+            return;
+        }
+
+        // 1. 先取出要恢复的金额(在清空之前!)
+        $discountAmount = $cart->vip_discount_amount;
+        $baseDiscountAmount = $cart->base_vip_discount_amount;
+
+        // 2. 再清空字段
+        $cart->vip_discount_amount = null;
+        $cart->base_vip_discount_amount = null;
+
+        // 3. 恢复购物车总额
+        $cart->grand_total = round($cart->grand_total + $discountAmount, 2);
+        $cart->base_grand_total = round($cart->base_grand_total + $baseDiscountAmount, 2);
+
+        $cart->save();
+    }
+
+    /**
+     * Check if customer is a valid VIP.
+     *
+     * @param  \Webkul\Customer\Models\Customer  $customer
+     * @return bool
+     */
+    protected function isValidVip($customer): bool
+    {
+        if (!$customer->vip_expire_date) {
+            return false;
+        }
+
+        $expireDate = Carbon::parse($customer->vip_expire_date);
+        return !$expireDate->isPast();
+    }
+}

+ 30 - 0
packages/Longyi/Member/src/Models/Member.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace Longyi\Member\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Longyi\Member\Contracts\Member as MemberContract;
+
+class Member extends Model implements MemberContract
+{
+    use HasFactory;
+
+    /**
+     * The attributes that are mass assignable.
+     *
+     * @var array
+     */
+    protected $fillable = [
+        // Add your fillable attributes here
+    ];
+
+    /**
+     * The attributes that should be cast.
+     *
+     * @var array
+     */
+    protected $casts = [
+        // Add your attribute casts here
+    ];
+}

+ 9 - 0
packages/Longyi/Member/src/Models/MemberProxy.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace Longyi\Member\Models;
+
+use Konekt\Concord\Proxies\ModelProxy;
+
+class MemberProxy extends ModelProxy
+{
+}

+ 34 - 0
packages/Longyi/Member/src/Providers/EventServiceProvider.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace Longyi\Member\Providers;
+
+use Illuminate\Support\Facades\Event;
+use Illuminate\Support\ServiceProvider;
+use Longyi\Member\Listeners\MemberHandler;
+use Longyi\Member\Listeners\VipDiscountHandler;
+
+class EventServiceProvider extends ServiceProvider
+{
+    /**
+     * Bootstrap services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        Event::listen('checkout.cart.collect.totals.after', 'Longyi\Member\Listeners\MemberHandler@applyMemberDiscount');
+        Event::listen('checkout.cart.collect.totals.after', 'Longyi\Member\Listeners\VipDiscountHandler@applyVipDiscount');
+        Event::listen('order.invoice.collect.totals.after', 'Longyi\Member\Listeners\InvoicesViewHandler@applyMemberDiscount');
+        Event::listen('bagisto.shop.checkout.onepage.summary.coupon.after', function($viewRenderEventManager) {
+            $viewRenderEventManager->addTemplate('member::components.vip-member-discount-cartsummary');
+        });
+        Event::listen('bagisto.shop.checkout.cart.summary.vip_member_discount.after', function($viewRenderEventManager) {
+            $viewRenderEventManager->addTemplate('member::components.member-discount-cartsummary');
+        });
+        Event::listen('bagisto.admin.sales.order.view.giftcard.before', function($viewRenderEventManager) {
+            $viewRenderEventManager->addTemplate('member::sales.orders.view');
+        });
+        Event::listen('checkout.order.save.after', 'Longyi\Member\Listeners\OrderPlacedHandler@afterPlaceOrder');
+        Event::listen('bagisto.shop.customers.account.orders.view.information.discount.after', 'Longyi\Member\Listeners\OrderViewHandler@renderMemberInfo');
+    }
+}

+ 3 - 1
packages/Longyi/Member/src/Providers/MemberServiceProvider.php

@@ -4,6 +4,7 @@ namespace Longyi\Member\Providers;
 
 use Illuminate\Support\ServiceProvider;
 use Illuminate\Support\Facades\Event;
+use Longyi\Member\Providers\EventServiceProvider;
 
 class MemberServiceProvider extends ServiceProvider
 {
@@ -33,6 +34,7 @@ class MemberServiceProvider extends ServiceProvider
         Event::listen('bagisto.admin.layout.head', function($viewRenderEventManager) {
             $viewRenderEventManager->addTemplate('member::admin.layouts.style');
         });
+        $this->app->register(EventServiceProvider::class);
     }
 
     /**
@@ -50,4 +52,4 @@ class MemberServiceProvider extends ServiceProvider
             dirname(__DIR__) . '/Config/acl.php', 'acl'
         );
     }
-}
+}

+ 9 - 0
packages/Longyi/Member/src/Resources/lang/en/app.php

@@ -0,0 +1,9 @@
+<?php
+
+return [
+    'member' => [
+        'discount' => '会员优惠',
+        'vip_active' => 'VIP 会员权益生效中',
+        'vip_discount' => 'VIP amount',
+    ],
+];

+ 9 - 0
packages/Longyi/Member/src/Resources/lang/zh_CN/app.php

@@ -0,0 +1,9 @@
+<?php
+
+return [
+    'member' => [
+        'discount' => '会员优惠',
+        'vip_active' => 'VIP 会员权益生效中',
+        'vip_discount' => 'VIP amount',
+    ],
+];

+ 16 - 0
packages/Longyi/Member/src/Resources/views/components/member-discount-cartsummary.blade.php

@@ -0,0 +1,16 @@
+{!! view_render_event('bagisto.shop.checkout.cart.summary.member_discount.before') !!}
+
+<div
+    class="flex text-right justify-between mt-2"
+    v-if="cart.vip_discount_amount && parseFloat(cart.vip_discount_amount) > 0"
+>
+    <p class="text-base">
+        @lang('member::app.member.vip_discount')
+    </p>
+
+    <p class="text-base font-medium">
+        - @{{ cart.formatted_vip_discount_amount }}
+    </p>
+</div>
+
+{!! view_render_event('bagisto.shop.checkout.cart.summary.member_discount.after') !!}

+ 183 - 0
packages/Longyi/Member/src/Resources/views/components/vip-member-discount-cartsummary.blade.php

@@ -0,0 +1,183 @@
+{!! view_render_event('bagisto.shop.checkout.cart.summary.vip_member_discount.before') !!}
+
+<v-member-discount
+    :cart="cart"
+    @member-discount-toggled="getCart"
+>
+</v-member-discount>
+
+@pushOnce('scripts')
+    <script type="text/x-template" id="v-member-discount-template">
+        <!-- VIP 用户显示提示信息 -->
+        <div v-if="isVipUser" class="flex justify-between items-center mt-2 p-3 bg-green-50 border border-green-200 rounded-lg">
+            <div class="flex items-center gap-2">
+                <span class="text-green-600 text-xl">✓</span>
+                <div>
+                    <p class="text-base font-medium text-green-800">
+                        @lang('member::app.member.vip_active')
+                    </p>
+                    <p class="text-xs text-green-600">
+                        @{{ vipExpireMessage }}
+                    </p>
+                </div>
+            </div>
+        </div>
+
+        <!-- 非 VIP 或 VIP 已过期用户显示优惠开关 -->
+        <div v-else class="flex justify-between items-center mt-2">
+            <div class="flex items-center gap-2">
+                <p class="text-base">
+                    @lang('member::app.member.discount')
+                </p>
+
+                <!-- Toggle Switch -->
+                <div
+                    class="relative inline-flex items-center cursor-pointer"
+                    @click="toggleSwitch"
+                    :class="{ 'opacity-50 cursor-not-allowed': !canApplyDiscount }"
+                >
+                    <!-- Background -->
+                    <div
+                        class="w-11 h-6 rounded-full transition-colors duration-200 ease-in-out"
+                        :class="isMemberDiscountApplied ? 'bg-blue-600' : 'bg-gray-300'"
+                    ></div>
+
+                    <!-- Knob -->
+                    <div
+                        class="absolute left-0.5 top-0.5 w-5 h-5 bg-white rounded-full shadow-md transition-transform duration-200 ease-in-out border border-gray-300"
+                        :class="isMemberDiscountApplied ? 'transform translate-x-5' : ''"
+                    ></div>
+
+                    <!-- Hidden checkbox for form submission -->
+                    <input
+                        type="checkbox"
+                        class="sr-only"
+                        :checked="isMemberDiscountApplied"
+                        :disabled="!canApplyDiscount"
+                    >
+                </div>
+            </div>
+
+            <p class="text-base font-medium" v-if="isMemberDiscountApplied && cart.formatted_vip_plus_amount">
+                + @{{ cart.formatted_vip_plus_amount }}
+            </p>
+        </div>
+    </script>
+
+    <script type="module">
+        app.component('v-member-discount', {
+            template: '#v-member-discount-template',
+
+            props: ['cart'],
+
+            data() {
+                return {
+                    isMemberDiscountApplied: false,
+                };
+            },
+
+            computed: {
+                isVipUser() {
+                    const isVip = this.cart && this.cart.vip_status && this.cart.vip_status.is_vip;
+                    return isVip;
+                },
+
+                vipExpireMessage() {
+                    if (!this.cart || !this.cart.vip_status) {
+                        return '';
+                    }
+                    const days = this.cart.vip_status.days_remaining;
+                    if (days > 0) {
+                        return `VIP 有效期还剩 ${days} 天`;
+                    }
+                    return 'VIP 已激活';
+                },
+
+                canApplyDiscount() {
+                    return this.cart && parseFloat(this.cart.grand_total) > 19.9;
+                }
+            },
+
+            watch: {
+                cart: {
+                    handler(newCart) {
+                        if (newCart) {
+                            // 只同步状态,不自动应用
+                            this.isMemberDiscountApplied = !!(newCart.vip_plus_amount && parseFloat(newCart.vip_plus_amount) > 0);
+                        }
+                    },
+                    immediate: true,
+                    deep: true
+                }
+            },
+
+            methods: {
+                toggleSwitch() {
+                    if (this.isMemberDiscountApplied) {
+                        this.removeMemberDiscount();
+                    } else {
+                        this.applyMemberDiscount();
+                    }
+                },
+
+                applyMemberDiscount() {
+                    this.$axios.post("{{ route('shop.api.checkout.cart.member.apply') }}", {
+                        _token: "{{ csrf_token() }}"
+                    })
+                        .then((response) => {
+                            if (response.data.success) {
+                                this.isMemberDiscountApplied = true;
+                                this.$emitter.emit('add-flash', {
+                                    type: 'success',
+                                    message: response.data.message || '会员优惠已应用'
+                                });
+                                this.$emit('member-discount-toggled');
+                            } else {
+                                this.$emitter.emit('add-flash', {
+                                    type: 'error',
+                                    message: response.data.message || '应用会员优惠失败'
+                                });
+                            }
+                        })
+                        .catch((error) => {
+                            console.error('Apply member discount error:', error);
+                            this.$emitter.emit('add-flash', {
+                                type: 'error',
+                                message: error.response?.data?.message || '应用会员优惠失败'
+                            });
+                        });
+                },
+
+                removeMemberDiscount() {
+                    this.$axios.post("{{ route('shop.api.checkout.cart.member.remove') }}", {
+                        _token: "{{ csrf_token() }}"
+                    })
+                        .then((response) => {
+                            if (response.data.success) {
+                                this.isMemberDiscountApplied = false;
+                                this.$emitter.emit('add-flash', {
+                                    type: 'success',
+                                    message: response.data.message || '会员优惠已移除'
+                                });
+                                this.$emit('member-discount-toggled');
+                            } else {
+                                this.$emitter.emit('add-flash', {
+                                    type: 'error',
+                                    message: response.data.message || '移除会员优惠失败'
+                                });
+                            }
+                        })
+                        .catch((error) => {
+                            console.error('Remove member discount error:', error);
+                            this.$emitter.emit('add-flash', {
+                                type: 'error',
+                                message: error.response?.data?.message || '移除会员优惠失败'
+                            });
+                        });
+                }
+            }
+        });
+    </script>
+@endPushOnce
+
+{!! view_render_event('bagisto.shop.checkout.cart.summary.vip_member_discount.after') !!}

+ 22 - 0
packages/Longyi/Member/src/Resources/views/customers/account/orders/member-info.blade.php

@@ -0,0 +1,22 @@
+@if (isset($order) && $order->vip_plus_amount && $order->vip_plus_amount > 0)
+    <div class="flex w-full justify-between gap-x-5">
+        <p>
+            @lang('member::app.member.discount')
+        </p>
+
+        <p>
+            + {{ core()->formatPrice($order->vip_plus_amount, $order->order_currency_code) }}
+        </p>
+    </div>
+@endif
+@if (isset($order) && $order->vip_discount_amount && $order->vip_discount_amount > 0)
+    <div class="flex w-full justify-between gap-x-5">
+        <p>
+            @lang('member::app.member.vip_discount')
+        </p>
+
+        <p>
+            - {{ core()->formatPrice($order->vip_discount_amount, $order->order_currency_code) }}
+        </p>
+    </div>
+@endif

+ 29 - 0
packages/Longyi/Member/src/Resources/views/sales/orders/view.blade.php

@@ -0,0 +1,29 @@
+{{-- Member Discount (19.9) --}}
+@if (isset($order) && $order->vip_plus_amount && $order->vip_plus_amount > 0)
+    <div class="flex w-full justify-between gap-x-5">
+        <p>
+            @lang('member::app.member.discount')
+        </p>
+
+        <p>
+            + {{ core()->formatBasePrice($order->base_vip_plus_amount) }}
+        </p>
+    </div>
+@endif
+
+{!! view_render_event('bagisto.admin.sales.order.view.vip_plus.after') !!}
+
+{!! view_render_event('bagisto.admin.sales.order.view.vip_discount_amount.before') !!}
+{{-- VIP Discount (3%) --}}
+@if (isset($order) && $order->vip_discount_amount && $order->vip_discount_amount > 0)
+    <div class="flex w-full justify-between gap-x-5">
+        <p>
+            @lang('member::app.member.vip_discount')
+        </p>
+
+        <p>
+            - {{ core()->formatBasePrice($order->base_vip_discount_amount) }}
+        </p>
+    </div>
+@endif
+{!! view_render_event('bagisto.admin.sales.order.view.vip_discount_amount.after') !!}

+ 7 - 1
packages/Longyi/Member/src/Routes/shop-routes.php

@@ -2,7 +2,13 @@
 
 use Illuminate\Support\Facades\Route;
 use Longyi\Member\Http\Controllers\Shop\MemberController;
+use Longyi\Member\Http\Controllers\Shop\MemberDiscountController;
 
 Route::group(['middleware' => ['web', 'theme', 'locale', 'currency'], 'prefix' => 'member'], function () {
     Route::get('', [MemberController::class, 'index'])->name('shop.member.index');
-});
+});
+
+Route::group(['middleware' => ['web', 'theme', 'locale', 'currency'], 'prefix' => 'api/checkout/cart/member'], function () {
+    Route::post('apply', [MemberDiscountController::class, 'apply'])->name('shop.api.checkout.cart.member.apply');
+    Route::post('remove', [MemberDiscountController::class, 'remove'])->name('shop.api.checkout.cart.member.remove');
+});

+ 9 - 0
packages/Webkul/Sales/src/Transformers/OrderResource.php

@@ -77,6 +77,15 @@ class OrderResource extends JsonResource
                 'giftcard_amount'           =>  $this->giftcard_amount,
                 'base_giftcard_amount'           =>  $this->base_giftcard_amount,
             ]),
+            $this->mergeWhen($this->vip_plus_amount && $this->vip_plus_amount > 0, [
+                'vip_plus_amount'           => $this->vip_plus_amount,
+                'base_vip_plus_amount'      => $this->base_vip_plus_amount,
+            ]),
+            $this->mergeWhen($this->vip_discount_amount && $this->vip_discount_amount > 0, [
+                'vip_discount_amount'           => $this->vip_discount_amount,
+                'base_vip_discount_amount'      => $this->base_vip_discount_amount,
+                'formatted_vip_discount_amount' => core()->formatPrice($this->vip_discount_amount),
+            ]),
         ];
     }
 }

+ 51 - 0
packages/Webkul/Shop/src/Http/Resources/CartResource.php

@@ -4,6 +4,7 @@ namespace Webkul\Shop\Http\Resources;
 
 use Illuminate\Http\Resources\Json\JsonResource;
 use Webkul\Tax\Facades\Tax;
+use Carbon\Carbon;
 
 class CartResource extends JsonResource
 {
@@ -48,6 +49,7 @@ class CartResource extends JsonResource
             'have_stockable_items'               => $this->haveStockableItems(),
             'payment_method'                     => $this->payment?->method,
             'payment_method_title'               => core()->getConfigData('sales.payment_methods.'.$this->payment?->method.'.title'),
+            'vip_status'                         => $this->getVipStatus(),
             $this->mergeWhen($this->giftcard_number, [
                 'giftcard_number'           => $this->giftcard_number,
                 'giftcard_amount'           => $this->giftcard_amount,
@@ -56,6 +58,55 @@ class CartResource extends JsonResource
                 'remaining_giftcard_amount' => $this->remaining_giftcard_amount,
                 'formatted_remaining_giftcard_amount' => core()->formatPrice($this->remaining_giftcard_amount),
             ]),
+            $this->mergeWhen($this->vip_plus_amount && $this->vip_plus_amount > 0, [
+                'vip_plus_amount'           => $this->vip_plus_amount,
+                'base_vip_plus_amount'      => $this->base_vip_plus_amount,
+                'formatted_vip_plus_amount' => core()->formatPrice($this->vip_plus_amount),
+            ]),
+            $this->mergeWhen($this->vip_discount_amount && $this->vip_discount_amount > 0, [
+                'vip_discount_amount'           => $this->vip_discount_amount,
+                'base_vip_discount_amount'      => $this->base_vip_discount_amount,
+                'formatted_vip_discount_amount' => core()->formatPrice($this->vip_discount_amount),
+            ]),
+        ];
+
+    }
+    /**
+     * Get VIP status for current customer.
+     *
+     * @return array
+     */
+    protected function getVipStatus(): array
+    {
+        if (!auth()->guard('customer')->check()) {
+            return [
+                'is_vip' => false,
+                'vip_expire_date' => null,
+                'days_remaining' => 0,
+                'can_show_discount' => true,
+            ];
+        }
+
+        $customer = auth()->guard('customer')->user();
+
+        if (!$customer->vip_expire_date) {
+            return [
+                'is_vip' => false,
+                'vip_expire_date' => null,
+                'days_remaining' => 0,
+                'can_show_discount' => true,
+            ];
+        }
+
+        $expireDate = Carbon::parse($customer->vip_expire_date);
+        $isExpired = $expireDate->isPast();
+        $daysRemaining = $isExpired ? 0 : now()->diffInDays($expireDate, false);
+
+        return [
+            'is_vip' => !$isExpired,
+            'vip_expire_date' => $customer->vip_expire_date,
+            'days_remaining' => $daysRemaining,
+            'can_show_discount' => $isExpired,
         ];
     }
 }