bianjunhui 1 месяц назад
Родитель
Сommit
edab3f0ba9
27 измененных файлов с 2446 добавлено и 0 удалено
  1. 1 0
      bootstrap/providers.php
  2. 1 0
      composer.json
  3. 31 0
      packages/Longyi/RewardPoints/composer.json
  4. 43 0
      packages/Longyi/RewardPoints/src/Config/rewardpoints.php
  5. 43 0
      packages/Longyi/RewardPoints/src/Console/Commands/CheckExpiredPoints.php
  6. 88 0
      packages/Longyi/RewardPoints/src/Database/Migrations/2026_01_01_000001_create_points_tables.php
  7. 18 0
      packages/Longyi/RewardPoints/src/Facades/CartRewardPoints.php
  8. 266 0
      packages/Longyi/RewardPoints/src/Http/Controllers/Admin/CustomerController.php
  9. 167 0
      packages/Longyi/RewardPoints/src/Http/Controllers/Admin/ReportController.php
  10. 237 0
      packages/Longyi/RewardPoints/src/Http/Controllers/Admin/RuleController.php
  11. 200 0
      packages/Longyi/RewardPoints/src/Http/Controllers/RewardPointsController.php
  12. 33 0
      packages/Longyi/RewardPoints/src/Listeners/CustomerEvents.php
  13. 86 0
      packages/Longyi/RewardPoints/src/Listeners/OrderEvents.php
  14. 33 0
      packages/Longyi/RewardPoints/src/Listeners/ReviewEvents.php
  15. 40 0
      packages/Longyi/RewardPoints/src/Models/RewardActiveRule.php
  16. 50 0
      packages/Longyi/RewardPoints/src/Models/RewardPointCustomer.php
  17. 33 0
      packages/Longyi/RewardPoints/src/Models/RewardPointCustomerSign.php
  18. 45 0
      packages/Longyi/RewardPoints/src/Models/RewardPointHistory.php
  19. 23 0
      packages/Longyi/RewardPoints/src/Providers/EventServiceProvider.php
  20. 54 0
      packages/Longyi/RewardPoints/src/Providers/RewardPointsServiceProvider.php
  21. 137 0
      packages/Longyi/RewardPoints/src/Repositories/RewardPointRepository.php
  22. 111 0
      packages/Longyi/RewardPoints/src/Resources/views/admin/rules/create.blade.php
  23. 74 0
      packages/Longyi/RewardPoints/src/Resources/views/admin/rules/index.blade.php
  24. 84 0
      packages/Longyi/RewardPoints/src/Resources/views/lang/en/rewardpoints.php
  25. 173 0
      packages/Longyi/RewardPoints/src/Resources/views/shop/customer/index.blade.php
  26. 112 0
      packages/Longyi/RewardPoints/src/Routes/routes.php
  27. 263 0
      packages/Longyi/RewardPoints/src/Services/CartRewardPoints.php

+ 1 - 0
bootstrap/providers.php

@@ -12,6 +12,7 @@ return [
     Webkul\Admin\Providers\AdminServiceProvider::class,
     Longyi\Core\Providers\LongyiCoreServiceProvider::class,
     Longyi\DynamicMenu\Providers\DynamicMenuServiceProvider::class,
+    Longyi\RewardPoints\Providers\RewardPointsServiceProvider::class,
     Webkul\Attribute\Providers\AttributeServiceProvider::class,
     Webkul\BookingProduct\Providers\BookingProductServiceProvider::class,
     Webkul\CMS\Providers\CMSServiceProvider::class,

+ 1 - 0
composer.json

@@ -65,6 +65,7 @@
             "Database\\Seeders\\": "database/seeders/",
             "Longyi\\Core\\": "packages/Longyi/Core/src/",
             "Longyi\\DynamicMenu\\": "packages/Longyi/DynamicMenu/src/",
+            "Longyi\\RewardPoints\\": "packages/Longyi/RewardPoints/src/",
             "Webkul\\Admin\\": "packages/Webkul/Admin/src",
             "Webkul\\Attribute\\": "packages/Webkul/Attribute/src",
             "Webkul\\BookingProduct\\": "packages/Webkul/BookingProduct/src",

+ 31 - 0
packages/Longyi/RewardPoints/composer.json

@@ -0,0 +1,31 @@
+{
+    "name": "longyi/reward-points",
+    "description": "Reward Points System for Bagisto",
+    "keywords": ["bagisto", "reward", "points", "loyalty"],
+    "license": "MIT",
+    "type": "library",
+    "version": "1.0.0",
+    "authors": [
+        {
+            "name": "Longyi Team",
+            "email": "dev@longyi.com"
+        }
+    ],
+    "require": {
+        "php": "^7.4|^8.0",
+        "bagisto/bagisto": "^2.3.11"
+    },
+    "autoload": {
+        "psr-4": {
+            "Longyi\\RewardPoints\\": "src/"
+        }
+    },
+    "extra": {
+        "laravel": {
+            "providers": [
+                "Longyi\\RewardPoints\\Providers\\RewardPointsServiceProvider"
+            ],
+            "aliases": {}
+        }
+    }
+}

+ 43 - 0
packages/Longyi/RewardPoints/src/Config/rewardpoints.php

@@ -0,0 +1,43 @@
+<?php
+
+return [
+    'general' => [
+        'point_value' => 0.01,
+        'max_discount_percentage' => 100,
+        'min_points_to_redeem' => 100,
+        'enabled' => true,
+    ],
+
+    'expiration' => [
+        'default_days' => 365,
+        'enable_notification' => true,
+        'notification_days_before' => 7,
+    ],
+
+    'sign_in' => [
+        'base_points' => 10,
+        'week_bonus_points' => 20,
+        'month_bonus_points' => 100,
+    ],
+
+    'order' => [
+        'points_per_currency' => 10,
+        'min_order_amount' => 0,
+        'exclude_shipping' => true,
+        'exclude_tax' => false,
+    ],
+
+    'review' => [
+        'points_per_review' => 50,
+        'require_approval' => true,
+    ],
+
+    'registration' => [
+        'points_per_registration' => 100,
+    ],
+
+    'referral' => [
+        'points_per_referral' => 200,
+        'require_first_order' => true,
+    ],
+];

+ 43 - 0
packages/Longyi/RewardPoints/src/Console/Commands/CheckExpiredPoints.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Longyi\RewardPoints\Console\Commands;
+
+use Illuminate\Console\Command;
+use Longyi\RewardPoints\Repositories\RewardPointRepository;
+use Carbon\Carbon;
+
+class CheckExpiredPoints extends Command
+{
+    protected $signature = 'reward-points:check-expired';
+
+    protected $description = 'Check and process expired reward points';
+
+    protected $rewardPointRepository;
+
+    public function __construct(RewardPointRepository $rewardPointRepository)
+    {
+        parent::__construct();
+        $this->rewardPointRepository = $rewardPointRepository;
+    }
+
+    public function handle()
+    {
+        $this->info('Checking expired reward points...');
+        
+        $startTime = Carbon::now();
+        
+        try {
+            $this->rewardPointRepository->checkExpiredPoints();
+            
+            $endTime = Carbon::now();
+            $duration = $startTime->diffInSeconds($endTime);
+            
+            $this->info("Successfully processed expired points. Duration: {$duration} seconds");
+            return 0;
+            
+        } catch (\Exception $e) {
+            $this->error('Error processing expired points: ' . $e->getMessage());
+            return 1;
+        }
+    }
+}

+ 88 - 0
packages/Longyi/RewardPoints/src/Database/Migrations/2026_01_01_000001_create_points_tables.php

@@ -0,0 +1,88 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        if (!Schema::hasTable('mw_reward_active_rules')) {
+            Schema::create('mw_reward_active_rules', function (Blueprint $table) {
+                $table->increments('rule_id');
+                $table->string('rule_name', 255)->default('');
+                $table->integer('type_of_transaction')->default(0);
+                $table->string('store_view', 255)->default('0');
+                $table->string('customer_group_ids', 255)->default('');
+                $table->integer('default_expired')->nullable()->default(1);
+                $table->integer('expired_day')->nullable()->default(0);
+                $table->string('date_event', 255)->default('');
+                $table->string('comment', 255)->default('');
+                $table->string('coupon_code', 255)->nullable()->default('');
+                $table->string('reward_point', 255)->default('0');
+                $table->integer('status')->default(0);
+            });
+        }
+
+        if (!Schema::hasTable('mw_reward_point_customer')) {
+            Schema::create('mw_reward_point_customer', function (Blueprint $table) {
+                $table->unsignedInteger('customer_id')->primary();
+                $table->unsignedInteger('mw_reward_point');
+                $table->unsignedInteger('mw_friend_id');
+                $table->integer('subscribed_balance_update')->nullable()->default(1);
+                $table->integer('subscribed_point_expiration')->nullable()->default(1);
+                $table->dateTime('last_checkout');
+            });
+        }
+
+        if (!Schema::hasTable('mw_reward_point_customer_sign')) {
+            Schema::create('mw_reward_point_customer_sign', function (Blueprint $table) {
+                $table->increments('id');
+                $table->unsignedInteger('customer_id');
+                $table->string('sign_date', 255)->default('');
+                $table->unsignedInteger('count_date');
+                $table->unsignedInteger('point');
+                $table->string('code', 255)->default('');
+                $table->dateTime('created')->nullable();
+                $table->dateTime('updated')->nullable();
+                
+                $table->index('customer_id', 'customer_id_index');
+            });
+        }
+
+        if (!Schema::hasTable('mw_reward_point_history')) {
+            Schema::create('mw_reward_point_history', function (Blueprint $table) {
+                $table->increments('history_id');
+                $table->unsignedInteger('customer_id');
+                $table->unsignedInteger('type_of_transaction');
+                $table->unsignedInteger('amount');
+                $table->integer('balance');
+                $table->text('transaction_detail')->nullable();
+                $table->dateTime('transaction_time')->nullable();
+                $table->integer('history_order_id')->nullable()->default(0);
+                $table->integer('expired_day')->nullable()->default(0);
+                $table->dateTime('expired_time')->nullable();
+                $table->integer('point_remaining')->nullable()->default(0);
+                $table->integer('check_time')->nullable()->default(1);
+                $table->integer('status');
+                
+                $table->index('customer_id', 'customer_id_index');
+                $table->index('transaction_time', 'transaction_time_index');
+                $table->index(['customer_id', 'status'], 'customer_status_index');
+                $table->index(['customer_id', 'history_order_id'], 'customer_order_index');
+                $table->index('expired_time', 'expired_time_index');
+            });
+        }
+    }
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('product_options');
+    }
+};

+ 18 - 0
packages/Longyi/RewardPoints/src/Facades/CartRewardPoints.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Longyi\RewardPoints\Facades;
+
+use Illuminate\Support\Facades\Facade;
+
+class CartRewardPoints extends Facade
+{
+    /**
+     * Get the registered name of the component.
+     *
+     * @return string
+     */
+    protected static function getFacadeAccessor()
+    {
+        return 'cartrewardpoints';
+    }
+}

+ 266 - 0
packages/Longyi/RewardPoints/src/Http/Controllers/Admin/CustomerController.php

@@ -0,0 +1,266 @@
+<?php
+
+namespace Longyi\RewardPoints\Http\Controllers\Admin;
+
+use Illuminate\Http\Request;
+use Webkul\Admin\Http\Controllers\Controller;
+use Longyi\RewardPoints\Repositories\RewardPointRepository;
+use Longyi\RewardPoints\Models\RewardPointCustomer;
+use Longyi\RewardPoints\Models\RewardActiveRule;
+use Webkul\Customer\Models\Customer;
+use Carbon\Carbon;
+
+class CustomerController extends Controller
+{
+    protected $rewardPointRepository;
+    protected $_config;
+
+    public function __construct(RewardPointRepository $rewardPointRepository)
+    {
+        $this->rewardPointRepository = $rewardPointRepository;
+        $this->_config = request('_config');
+    }
+
+    /**
+     * Display a listing of customers with reward points
+     */
+    public function index()
+    {
+        $customers = RewardPointCustomer::with('customer')
+            ->orderBy('mw_reward_point', 'desc')
+            ->paginate(15);
+
+        $totalPoints = RewardPointCustomer::sum('mw_reward_point');
+        $totalCustomers = RewardPointCustomer::count();
+        $averagePoints = $totalCustomers > 0 ? $totalPoints / $totalCustomers : 0;
+
+        return view($this->_config['view'], compact('customers', 'totalPoints', 'totalCustomers', 'averagePoints'));
+    }
+
+    /**
+     * Show customer reward points details
+     */
+    public function show($customerId)
+    {
+        $customer = Customer::findOrFail($customerId);
+        $rewardData = RewardPointCustomer::where('customer_id', $customerId)->first();
+
+        if (!$rewardData) {
+            $rewardData = new RewardPointCustomer();
+            $rewardData->customer_id = $customerId;
+            $rewardData->mw_reward_point = 0;
+            $rewardData->mw_friend_id = 0;
+            $rewardData->last_checkout = Carbon::now();
+        }
+
+        $history = $this->rewardPointRepository->getHistory($customerId, 20);
+        $signRecords = $rewardData->signRecords()->orderBy('sign_date', 'desc')->limit(30)->get();
+
+        return view($this->_config['view'], compact('customer', 'rewardData', 'history', 'signRecords'));
+    }
+
+    /**
+     * Add points to customer
+     */
+    public function addPoints(Request $request)
+    {
+        $this->validate($request, [
+            'customer_id' => 'required|integer|exists:customers,id',
+            'points' => 'required|integer|min:1',
+            'type' => 'required|integer|in:1,2,3,4,5,6',
+            'reason' => 'nullable|string|max:500'
+        ]);
+
+        try {
+            $customerId = $request->input('customer_id');
+            $points = $request->input('points');
+            $type = $request->input('type');
+            $reason = $request->input('reason', 'Admin adjustment');
+
+            $history = $this->rewardPointRepository->addPoints(
+                $customerId,
+                $type,
+                $points,
+                null,
+                "Admin: " . $reason
+            );
+
+            if ($history) {
+                session()->flash('success', "Successfully added {$points} points to customer.");
+            } else {
+                session()->flash('error', 'Failed to add points.');
+            }
+
+            return redirect()->back();
+
+        } catch (\Exception $e) {
+            session()->flash('error', 'Error adding points: ' . $e->getMessage());
+            return redirect()->back()->withInput();
+        }
+    }
+
+    /**
+     * Deduct points from customer
+     */
+    public function deductPoints(Request $request)
+    {
+        $this->validate($request, [
+            'customer_id' => 'required|integer|exists:customers,id',
+            'points' => 'required|integer|min:1',
+            'reason' => 'nullable|string|max:500'
+        ]);
+
+        try {
+            $customerId = $request->input('customer_id');
+            $points = $request->input('points');
+            $reason = $request->input('reason', 'Admin adjustment');
+
+            $result = $this->rewardPointRepository->deductPoints(
+                $customerId,
+                $points,
+                null,
+                "Admin: " . $reason
+            );
+
+            if ($result) {
+                session()->flash('success', "Successfully deducted {$points} points from customer.");
+            } else {
+                session()->flash('error', 'Insufficient points or customer not found.');
+            }
+
+            return redirect()->back();
+
+        } catch (\Exception $e) {
+            session()->flash('error', 'Error deducting points: ' . $e->getMessage());
+            return redirect()->back()->withInput();
+        }
+    }
+
+    /**
+     * Export customer points report
+     */
+    public function export(Request $request)
+    {
+        $customers = RewardPointCustomer::with('customer')
+            ->orderBy('mw_reward_point', 'desc')
+            ->get();
+
+        $filename = "reward_points_report_" . Carbon::now()->format('Y-m-d_H-i-s') . ".csv";
+
+        $headers = [
+            'Content-Type' => 'text/csv',
+            'Content-Disposition' => "attachment; filename={$filename}",
+        ];
+
+        $callback = function() use ($customers) {
+            $file = fopen('php://output', 'w');
+
+            // Add CSV headers
+            fputcsv($file, [
+                'Customer ID',
+                'Customer Email',
+                'Customer Name',
+                'Reward Points',
+                'Friend ID',
+                'Subscribed Balance Update',
+                'Subscribed Point Expiration',
+                'Last Checkout'
+            ]);
+
+            // Add data rows
+            foreach ($customers as $customer) {
+                fputcsv($file, [
+                    $customer->customer_id,
+                    $customer->customer->email ?? '',
+                    $customer->customer->first_name . ' ' . $customer->customer->last_name,
+                    $customer->mw_reward_point,
+                    $customer->mw_friend_id,
+                    $customer->subscribed_balance_update ? 'Yes' : 'No',
+                    $customer->subscribed_point_expiration ? 'Yes' : 'No',
+                    $customer->last_checkout
+                ]);
+            }
+
+            fclose($file);
+        };
+
+        return response()->stream($callback, 200, $headers);
+    }
+
+    /**
+     * Bulk update points
+     */
+    public function bulkUpdate(Request $request)
+    {
+        $this->validate($request, [
+            'customer_ids' => 'required|array',
+            'customer_ids.*' => 'integer|exists:customers,id',
+            'points' => 'required|integer',
+            'action' => 'required|in:add,set'
+        ]);
+
+        try {
+            $customerIds = $request->input('customer_ids');
+            $points = $request->input('points');
+            $action = $request->input('action');
+            $reason = $request->input('reason', 'Admin bulk update');
+
+            $updatedCount = 0;
+
+            foreach ($customerIds as $customerId) {
+                if ($action === 'add') {
+                    $result = $this->rewardPointRepository->addPoints(
+                        $customerId,
+                        0, // 0 for admin action
+                        $points,
+                        null,
+                        "Admin bulk: " . $reason
+                    );
+                } else {
+                    // Set points to specific value
+                    $customerPoints = RewardPointCustomer::firstOrCreate(
+                        ['customer_id' => $customerId],
+                        [
+                            'mw_reward_point' => 0,
+                            'mw_friend_id' => 0,
+                            'last_checkout' => Carbon::now()
+                        ]
+                    );
+
+                    $oldPoints = $customerPoints->mw_reward_point;
+                    $customerPoints->mw_reward_point = $points;
+                    $customerPoints->save();
+
+                    // Add history record
+                    $this->rewardPointRepository->create([
+                        'customer_id' => $customerId,
+                        'type_of_transaction' => 0,
+                        'amount' => $points - $oldPoints,
+                        'balance' => $points,
+                        'transaction_detail' => "Admin bulk set: " . $reason,
+                        'transaction_time' => Carbon::now(),
+                        'history_order_id' => 0,
+                        'expired_day' => 0,
+                        'expired_time' => null,
+                        'point_remaining' => 0,
+                        'check_time' => 1,
+                        'status' => 1
+                    ]);
+
+                    $result = true;
+                }
+
+                if ($result) {
+                    $updatedCount++;
+                }
+            }
+
+            session()->flash('success', "Successfully updated {$updatedCount} customers.");
+            return redirect()->back();
+
+        } catch (\Exception $e) {
+            session()->flash('error', 'Error in bulk update: ' . $e->getMessage());
+            return redirect()->back()->withInput();
+        }
+    }
+}

+ 167 - 0
packages/Longyi/RewardPoints/src/Http/Controllers/Admin/ReportController.php

@@ -0,0 +1,167 @@
+<?php
+
+namespace Longyi\RewardPoints\Http\Controllers\Admin;
+
+use Illuminate\Http\Request;
+use Webkul\Admin\Http\Controllers\Controller;
+use Longyi\RewardPoints\Models\RewardPointHistory;
+use Longyi\RewardPoints\Models\RewardPointCustomer;
+use Longyi\RewardPoints\Models\RewardPointCustomerSign;
+use Carbon\Carbon;
+
+class ReportController extends Controller
+{
+    protected $_config;
+
+    public function __construct()
+    {
+        $this->_config = request('_config');
+    }
+
+    /**
+     * Display reward points reports
+     */
+    public function index()
+    {
+        $startDate = request()->get('start_date', Carbon::now()->subDays(30)->format('Y-m-d'));
+        $endDate = request()->get('end_date', Carbon::now()->format('Y-m-d'));
+
+        $data = $this->getReportData($startDate, $endDate);
+
+        return view($this->_config['view'], compact('data', 'startDate', 'endDate'));
+    }
+
+    /**
+     * Get report data
+     */
+    protected function getReportData($startDate, $endDate)
+    {
+        $startDateTime = Carbon::parse($startDate)->startOfDay();
+        $endDateTime = Carbon::parse($endDate)->endOfDay();
+
+        // Points earned by type
+        $pointsByType = RewardPointHistory::whereBetween('transaction_time', [$startDateTime, $endDateTime])
+            ->where('amount', '>', 0)
+            ->selectRaw('type_of_transaction, SUM(amount) as total_points, COUNT(*) as total_transactions')
+            ->groupBy('type_of_transaction')
+            ->get();
+
+        // Daily sign-in statistics
+        $signIns = RewardPointCustomerSign::whereBetween('created', [$startDateTime, $endDateTime])
+            ->selectRaw('DATE(created) as date, COUNT(*) as total_sign_ins, SUM(point) as total_points_earned')
+            ->groupBy('date')
+            ->orderBy('date', 'desc')
+            ->limit(30)
+            ->get();
+
+        // Points redeemed
+        $pointsRedeemed = RewardPointHistory::whereBetween('transaction_time', [$startDateTime, $endDateTime])
+            ->where('amount', '<', 0)
+            ->sum('amount');
+
+        // Top customers by points
+        $topCustomers = RewardPointCustomer::with('customer')
+            ->orderBy('mw_reward_point', 'desc')
+            ->limit(10)
+            ->get();
+
+        // Overall statistics
+        $totalPointsEarned = RewardPointHistory::whereBetween('transaction_time', [$startDateTime, $endDateTime])
+            ->where('amount', '>', 0)
+            ->sum('amount');
+
+        $totalTransactions = RewardPointHistory::whereBetween('transaction_time', [$startDateTime, $endDateTime])
+            ->count();
+
+        $totalCustomersWithPoints = RewardPointCustomer::count();
+
+        return [
+            'points_by_type' => $pointsByType,
+            'sign_ins' => $signIns,
+            'points_redeemed' => abs($pointsRedeemed),
+            'top_customers' => $topCustomers,
+            'total_points_earned' => $totalPointsEarned,
+            'total_transactions' => $totalTransactions,
+            'total_customers' => $totalCustomersWithPoints
+        ];
+    }
+
+    /**
+     * Export report
+     */
+    public function export(Request $request)
+    {
+        $startDate = $request->get('start_date', Carbon::now()->subDays(30)->format('Y-m-d'));
+        $endDate = $request->get('end_date', Carbon::now()->format('Y-m-d'));
+
+        $data = $this->getReportData($startDate, $endDate);
+
+        $filename = "reward_points_report_{$startDate}_{$endDate}.csv";
+
+        $headers = [
+            'Content-Type' => 'text/csv',
+            'Content-Disposition' => "attachment; filename={$filename}",
+        ];
+
+        $callback = function() use ($data, $startDate, $endDate) {
+            $file = fopen('php://output', 'w');
+
+            // Summary
+            fputcsv($file, ['Reward Points Report']);
+            fputcsv($file, ['Date Range', $startDate, $endDate]);
+            fputcsv($file, []);
+
+            fputcsv($file, ['Summary Statistics']);
+            fputcsv($file, ['Total Points Earned', $data['total_points_earned']]);
+            fputcsv($file, ['Points Redeemed', $data['points_redeemed']]);
+            fputcsv($file, ['Total Transactions', $data['total_transactions']]);
+            fputcsv($file, ['Total Customers with Points', $data['total_customers']]);
+            fputcsv($file, []);
+
+            // Points by type
+            fputcsv($file, ['Points by Transaction Type']);
+            fputcsv($file, ['Type', 'Total Points', 'Number of Transactions']);
+
+            $typeNames = [
+                1 => 'Order',
+                2 => 'Registration',
+                3 => 'Product Review',
+                4 => 'Daily Sign In',
+                5 => 'Referral',
+                6 => 'Birthday',
+                0 => 'Admin Adjustment'
+            ];
+
+            foreach ($data['points_by_type'] as $item) {
+                $typeName = $typeNames[$item->type_of_transaction] ?? 'Unknown';
+                fputcsv($file, [$typeName, $item->total_points, $item->total_transactions]);
+            }
+
+            fputcsv($file, []);
+
+            // Top customers
+            fputcsv($file, ['Top 10 Customers by Points']);
+            fputcsv($file, ['Customer ID', 'Customer Name', 'Email', 'Points Balance']);
+
+            foreach ($data['top_customers'] as $customer) {
+                $name = $customer->customer ? $customer->customer->first_name . ' ' . $customer->customer->last_name : 'N/A';
+                $email = $customer->customer ? $customer->customer->email : 'N/A';
+                fputcsv($file, [$customer->customer_id, $name, $email, $customer->mw_reward_point]);
+            }
+
+            fputcsv($file, []);
+
+            // Daily sign-ins
+            fputcsv($file, ['Daily Sign-In Statistics']);
+            fputcsv($file, ['Date', 'Total Sign-Ins', 'Points Earned']);
+
+            foreach ($data['sign_ins'] as $sign) {
+                fputcsv($file, [$sign->date, $sign->total_sign_ins, $sign->total_points_earned]);
+            }
+
+            fclose($file);
+        };
+
+        return response()->stream($callback, 200, $headers);
+    }
+}

+ 237 - 0
packages/Longyi/RewardPoints/src/Http/Controllers/Admin/RuleController.php

@@ -0,0 +1,237 @@
+<?php
+
+namespace Longyi\RewardPoints\Http\Controllers\Admin;
+
+use Illuminate\Http\Request;
+use Webkul\Admin\Http\Controllers\Controller;
+use Longyi\RewardPoints\Models\RewardActiveRule;
+use Longyi\RewardPoints\Repositories\RewardPointRepository;
+
+class RuleController extends Controller
+{
+    protected $rewardPointRepository;
+    protected $_config;
+
+    public function __construct(RewardPointRepository $rewardPointRepository)
+    {
+        $this->rewardPointRepository = $rewardPointRepository;
+        $this->_config = request('_config');
+    }
+
+    /**
+     * Display a listing of reward rules
+     */
+    public function index()
+    {
+        $rules = RewardActiveRule::orderBy('rule_id', 'desc')->paginate(15);
+
+        return view($this->_config['view'], compact('rules'));
+    }
+
+    /**
+     * Show the form for creating a new reward rule
+     */
+    public function create()
+    {
+        $transactionTypes = [
+            1 => 'Order',
+            2 => 'Registration',
+            3 => 'Product Review',
+            4 => 'Daily Sign In',
+            5 => 'Referral',
+            6 => 'Birthday'
+        ];
+
+        $storeViews = $this->getStoreViews();
+        $customerGroups = $this->getCustomerGroups();
+
+        return view($this->_config['view'], compact('transactionTypes', 'storeViews', 'customerGroups'));
+    }
+
+    /**
+     * Store a newly created reward rule
+     */
+    public function store(Request $request)
+    {
+        $this->validate($request, [
+            'rule_name' => 'required|string|max:255',
+            'type_of_transaction' => 'required|integer|in:1,2,3,4,5,6',
+            'reward_point' => 'required|integer|min:0',
+            'status' => 'required|boolean',
+            'default_expired' => 'nullable|boolean',
+            'expired_day' => 'nullable|integer|min:0',
+            'coupon_code' => 'nullable|string|max:255'
+        ]);
+
+        try {
+            $data = $request->all();
+
+            // Handle customer group IDs
+            if (isset($data['customer_group_ids']) && is_array($data['customer_group_ids'])) {
+                $data['customer_group_ids'] = implode(',', $data['customer_group_ids']);
+            }
+
+            // Handle store views
+            if (isset($data['store_view']) && is_array($data['store_view'])) {
+                $data['store_view'] = implode(',', $data['store_view']);
+            }
+
+            $rule = RewardActiveRule::create($data);
+
+            session()->flash('success', 'Reward rule created successfully.');
+            return redirect()->route('admin.reward-points.rules.index');
+
+        } catch (\Exception $e) {
+            session()->flash('error', 'Error creating reward rule: ' . $e->getMessage());
+            return redirect()->back()->withInput();
+        }
+    }
+
+    /**
+     * Show the form for editing the specified reward rule
+     */
+    public function edit($id)
+    {
+        $rule = RewardActiveRule::findOrFail($id);
+
+        $transactionTypes = [
+            1 => 'Order',
+            2 => 'Registration',
+            3 => 'Product Review',
+            4 => 'Daily Sign In',
+            5 => 'Referral',
+            6 => 'Birthday'
+        ];
+
+        $storeViews = $this->getStoreViews();
+        $customerGroups = $this->getCustomerGroups();
+
+        // Parse stored values
+        $selectedStoreViews = $rule->store_view ? explode(',', $rule->store_view) : [];
+        $selectedCustomerGroups = $rule->customer_group_ids ? explode(',', $rule->customer_group_ids) : [];
+
+        return view($this->_config['view'], compact(
+            'rule',
+            'transactionTypes',
+            'storeViews',
+            'customerGroups',
+            'selectedStoreViews',
+            'selectedCustomerGroups'
+        ));
+    }
+
+    /**
+     * Update the specified reward rule
+     */
+    public function update(Request $request, $id)
+    {
+        $this->validate($request, [
+            'rule_name' => 'required|string|max:255',
+            'type_of_transaction' => 'required|integer|in:1,2,3,4,5,6',
+            'reward_point' => 'required|integer|min:0',
+            'status' => 'required|boolean',
+            'default_expired' => 'nullable|boolean',
+            'expired_day' => 'nullable|integer|min:0',
+            'coupon_code' => 'nullable|string|max:255'
+        ]);
+
+        try {
+            $rule = RewardActiveRule::findOrFail($id);
+            $data = $request->all();
+
+            // Handle customer group IDs
+            if (isset($data['customer_group_ids']) && is_array($data['customer_group_ids'])) {
+                $data['customer_group_ids'] = implode(',', $data['customer_group_ids']);
+            } else {
+                $data['customer_group_ids'] = '';
+            }
+
+            // Handle store views
+            if (isset($data['store_view']) && is_array($data['store_view'])) {
+                $data['store_view'] = implode(',', $data['store_view']);
+            } else {
+                $data['store_view'] = '0';
+            }
+
+            $rule->update($data);
+
+            session()->flash('success', 'Reward rule updated successfully.');
+            return redirect()->route('admin.reward-points.rules.index');
+
+        } catch (\Exception $e) {
+            session()->flash('error', 'Error updating reward rule: ' . $e->getMessage());
+            return redirect()->back()->withInput();
+        }
+    }
+
+    /**
+     * Remove the specified reward rule
+     */
+    public function destroy($id)
+    {
+        try {
+            $rule = RewardActiveRule::findOrFail($id);
+            $rule->delete();
+
+            session()->flash('success', 'Reward rule deleted successfully.');
+            return response()->json([
+                'status' => true,
+                'message' => 'Reward rule deleted successfully.'
+            ]);
+
+        } catch (\Exception $e) {
+            session()->flash('error', 'Error deleting reward rule: ' . $e->getMessage());
+            return response()->json([
+                'status' => false,
+                'message' => 'Error deleting reward rule.'
+            ]);
+        }
+    }
+
+    /**
+     * Update rule status
+     */
+    public function updateStatus($id, Request $request)
+    {
+        try {
+            $rule = RewardActiveRule::findOrFail($id);
+            $rule->status = $request->input('status', 0);
+            $rule->save();
+
+            return response()->json([
+                'status' => true,
+                'message' => 'Rule status updated successfully.'
+            ]);
+
+        } catch (\Exception $e) {
+            return response()->json([
+                'status' => false,
+                'message' => 'Error updating rule status.'
+            ]);
+        }
+    }
+
+    /**
+     * Get all store views
+     */
+    protected function getStoreViews()
+    {
+        try {
+            return \Webkul\Core\Models\Channel::pluck('name', 'code')->toArray();
+        } catch (\Exception $e) {
+            return ['0' => 'All Stores'];
+        }
+    }
+
+    /**
+     * Get all customer groups
+     */
+    protected function getCustomerGroups()
+    {
+        try {
+            return \Webkul\Customer\Models\CustomerGroup::pluck('name', 'id')->toArray();
+        } catch (\Exception $e) {
+            return ['0' => 'All Groups'];
+        }
+    }
+}

+ 200 - 0
packages/Longyi/RewardPoints/src/Http/Controllers/RewardPointsController.php

@@ -0,0 +1,200 @@
+<?php
+
+namespace Longyi\RewardPoints\Http\Controllers;
+
+use Longyi\RewardPoints\Repositories\RewardPointRepository;
+use Longyi\RewardPoints\Models\RewardPointCustomerSign;
+use Longyi\RewardPoints\Models\RewardActiveRule;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Routing\Controller;
+use Webkul\User\Repositories\RoleRepository;
+use Illuminate\Support\Facades\DB;
+class RewardPointsController extends Controller
+{
+    protected $rewardPointRepository;
+    protected $_config;
+
+    public function __construct(RewardPointRepository $rewardPointRepository)
+    {
+        $this->rewardPointRepository = $rewardPointRepository;
+        $this->_config = request('_config');
+    }
+
+    public function index()
+    {
+        $customer = auth()->guard('customer')->user();
+
+        if (!$customer) {
+            return redirect()->route('customer.session.index');
+        }
+
+        $points = $this->rewardPointRepository->getCustomerPoints($customer->id);
+        $history = $this->rewardPointRepository->getHistory($customer->id);
+
+        return view($this->_config['view'], compact('points', 'history'));
+    }
+
+    public function signIn()
+    {
+        $customer = auth()->guard('customer')->user();
+
+        if (!$customer) {
+            return response()->json(['error' => 'Please login first'], 401);
+        }
+
+        $today = Carbon::now()->format('Y-m-d');
+
+        $existingSign = RewardPointCustomerSign::where('customer_id', $customer->id)
+            ->where('sign_date', $today)
+            ->first();
+
+        if ($existingSign) {
+            return response()->json(['error' => 'Already signed in today'], 400);
+        }
+
+        $lastSign = RewardPointCustomerSign::where('customer_id', $customer->id)
+            ->orderBy('sign_date', 'desc')
+            ->first();
+
+        $countDate = 1;
+        if ($lastSign) {
+            $lastSignDate = Carbon::parse($lastSign->sign_date);
+            $yesterday = Carbon::yesterday();
+            
+            if ($lastSignDate->toDateString() === $yesterday->toDateString()) {
+                $countDate = $lastSign->count_date + 1;
+            } elseif ($lastSignDate->toDateString() !== $today) {
+                $countDate = 1;
+            }
+        }
+
+        $rule = RewardActiveRule::where('type_of_transaction', RewardActiveRule::TYPE_SIGN_IN)
+            ->where('status', 1)
+            ->first();
+
+        $basePoints = config('rewardpoints.sign_in.base_points', 10);
+        $points = $rule ? $rule->reward_point : $basePoints;
+
+        if ($countDate % 30 == 0) {
+            $bonusPoints = config('rewardpoints.sign_in.month_bonus_points', 100);
+            $points += $bonusPoints;
+        } elseif ($countDate % 7 == 0) {
+            $bonusPoints = config('rewardpoints.sign_in.week_bonus_points', 20);
+            $points += $bonusPoints;
+        }
+
+        $sign = RewardPointCustomerSign::create([
+            'customer_id' => $customer->id,
+            'sign_date' => $today,
+            'count_date' => $countDate,
+            'point' => $points,
+            'code' => uniqid('SIGN_'),
+            'created' => Carbon::now(),
+            'updated' => Carbon::now()
+        ]);
+
+        $this->rewardPointRepository->addPoints(
+            $customer->id,
+            RewardActiveRule::TYPE_SIGN_IN,
+            $points,
+            null,
+            "Daily sign-in bonus (Day {$countDate})"
+        );
+
+        return response()->json([
+            'success' => true,
+            'points' => $points,
+            'streak' => $countDate,
+            'total_points' => $this->rewardPointRepository->getCustomerPoints($customer->id)
+        ]);
+    }
+    public function getSignStatus()
+    {
+        $customer = auth()->guard('customer')->user();
+
+        if (!$customer) {
+            return response()->json(['error' => 'Please login first'], 401);
+        }
+
+        $today = Carbon::now()->format('Y-m-d');
+        $signedToday = RewardPointCustomerSign::where('customer_id', $customer->id)
+            ->where('sign_date', $today)
+            ->exists();
+
+        $lastSign = RewardPointCustomerSign::where('customer_id', $customer->id)
+            ->orderBy('sign_date', 'desc')
+            ->first();
+
+        return response()->json([
+            'signed_today' => $signedToday,
+            'current_streak' => $lastSign ? $lastSign->count_date : 0,
+            'total_points' => $this->rewardPointRepository->getCustomerPoints($customer->id)
+        ]);
+    }
+
+    /**
+     * Apply reward points to cart
+     */
+    public function applyPoints(Request $request)
+    {
+        $points = $request->input('points', 0);
+        
+        $cartRewardPoints = app('cartrewardpoints');
+        $validation = $cartRewardPoints->validatePoints($points);
+        
+        if ($validation !== true) {
+            return response()->json([
+                'success' => false,
+                'message' => $validation
+            ], 400);
+        }
+        
+        $result = $cartRewardPoints->applyPoints($points);
+        
+        if ($result) {
+            $discountDetails = $cartRewardPoints->getDiscountDetails();
+            
+            return response()->json([
+                'success' => true,
+                'message' => 'Reward points applied successfully',
+                'discount' => $discountDetails
+            ]);
+        }
+        
+        return response()->json([
+            'success' => false,
+            'message' => 'Failed to apply reward points'
+        ], 400);
+    }
+
+    /**
+     * Remove reward points from cart
+     */
+    public function removePoints()
+    {
+        $cartRewardPoints = app('cartrewardpoints');
+        $cartRewardPoints->removePoints();
+        
+        return response()->json([
+            'success' => true,
+            'message' => 'Reward points removed successfully'
+        ]);
+    }
+
+    /**
+     * Get points information for cart
+     */
+    public function getPointsInfo()
+    {
+        $cartRewardPoints = app('cartrewardpoints');
+        
+        return response()->json([
+            'available_points' => $cartRewardPoints->getAvailablePoints(),
+            'points_used' => $cartRewardPoints->getPointsUsed(),
+            'discount_amount' => $cartRewardPoints->getDiscountAmount(),
+            'points_value' => $cartRewardPoints->getPointsValue(1),
+            'max_points_allowed' => $cartRewardPoints->getMaxPointsByCartTotal()
+        ]);
+    }
+}

+ 33 - 0
packages/Longyi/RewardPoints/src/Listeners/CustomerEvents.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace Longyi\RewardPoints\Listeners;
+
+use Longyi\RewardPoints\Repositories\RewardPointRepository;
+use Longyi\RewardPoints\Models\RewardActiveRule;
+
+class CustomerEvents
+{
+    protected $rewardPointRepository;
+
+    public function __construct(RewardPointRepository $rewardPointRepository)
+    {
+        $this->rewardPointRepository = $rewardPointRepository;
+    }
+
+    public function handleCustomerRegistration($customer)
+    {
+        $rule = RewardActiveRule::where('type_of_transaction', RewardActiveRule::TYPE_REGISTRATION)
+            ->where('status', 1)
+            ->first();
+
+        if ($rule && $rule->reward_point > 0) {
+            $this->rewardPointRepository->addPoints(
+                $customer->id,
+                RewardActiveRule::TYPE_REGISTRATION,
+                $rule->reward_point,
+                null,
+                'Registration bonus'
+            );
+        }
+    }
+}

+ 86 - 0
packages/Longyi/RewardPoints/src/Listeners/OrderEvents.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace Longyi\RewardPoints\Listeners;
+
+use Longyi\RewardPoints\Repositories\RewardPointRepository;
+use Longyi\RewardPoints\Models\RewardActiveRule;
+
+class OrderEvents
+{
+    protected $rewardPointRepository;
+
+    public function __construct(RewardPointRepository $rewardPointRepository)
+    {
+        $this->rewardPointRepository = $rewardPointRepository;
+    }
+
+    public function handleOrderPlacement($order)
+    {
+        if (!$order->customer_id) {
+            return;
+        }
+
+        $rule = RewardActiveRule::where('type_of_transaction', RewardActiveRule::TYPE_ORDER)
+            ->where('status', 1)
+            ->first();
+
+        if (!$rule) {
+            return;
+        }
+
+        $points = $this->calculateOrderPoints($order, $rule);
+
+        if ($points > 0) {
+            $this->rewardPointRepository->addPoints(
+                $order->customer_id,
+                RewardActiveRule::TYPE_ORDER,
+                $points,
+                $order->id,
+                "Points earned from order #{$order->increment_id}"
+            );
+        }
+    }
+
+    public function handleOrderCancellation($order)
+    {
+        if (!$order->customer_id) {
+            return;
+        }
+
+        $histories = $this->rewardPointRepository->findWhere([
+            'customer_id' => $order->customer_id,
+            'history_order_id' => $order->id,
+            'type_of_transaction' => RewardActiveRule::TYPE_ORDER,
+            'status' => RewardPointHistory::STATUS_COMPLETED
+        ])->get();
+
+        if ($histories->isEmpty()) {
+            return;
+        }
+
+        foreach ($histories as $history) {
+            if ($history->amount > 0 && $history->point_remaining > 0) {
+                $result = $this->rewardPointRepository->deductPoints(
+                    $order->customer_id,
+                    $history->point_remaining,
+                    $order->id,
+                    "Points deducted due to order cancellation #{$order->increment_id}"
+                );
+
+                if ($result) {
+                    $history->status = RewardPointHistory::STATUS_CANCELLED;
+                    $history->point_remaining = 0;
+                    $history->save();
+                }
+            }
+        }
+    }
+
+    protected function calculateOrderPoints($order, $rule)
+    {
+        // Simple calculation based on order grand total
+        // You can implement more complex logic based on your requirements
+        $pointsPerCurrency = 10; // 10 points per currency unit
+        return floor($order->grand_total * $pointsPerCurrency);
+    }
+}

+ 33 - 0
packages/Longyi/RewardPoints/src/Listeners/ReviewEvents.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace Longyi\RewardPoints\Listeners;
+
+use Longyi\RewardPoints\Repositories\RewardPointRepository;
+use Longyi\RewardPoints\Models\RewardActiveRule;
+
+class ReviewEvents
+{
+    protected $rewardPointRepository;
+
+    public function __construct(RewardPointRepository $rewardPointRepository)
+    {
+        $this->rewardPointRepository = $rewardPointRepository;
+    }
+
+    public function handleReviewCreation($review)
+    {
+        $rule = RewardActiveRule::where('type_of_transaction', RewardActiveRule::TYPE_REVIEW)
+            ->where('status', 1)
+            ->first();
+
+        if ($rule && $rule->reward_point > 0 && $review->customer_id) {
+            $this->rewardPointRepository->addPoints(
+                $review->customer_id,
+                RewardActiveRule::TYPE_REVIEW,
+                $rule->reward_point,
+                null,
+                "Points earned for product review"
+            );
+        }
+    }
+}

+ 40 - 0
packages/Longyi/RewardPoints/src/Models/RewardActiveRule.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace Longyi\RewardPoints\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class RewardActiveRule extends Model
+{
+    protected $table = 'mw_reward_active_rules';
+    protected $primaryKey = 'rule_id';
+    public $timestamps = false;
+
+    protected $fillable = [
+        'rule_name',
+        'type_of_transaction',
+        'store_view',
+        'customer_group_ids',
+        'default_expired',
+        'expired_day',
+        'date_event',
+        'comment',
+        'coupon_code',
+        'reward_point',
+        'status'
+    ];
+
+    protected $casts = [
+        'type_of_transaction' => 'integer',
+        'default_expired' => 'integer',
+        'expired_day' => 'integer',
+        'status' => 'integer'
+    ];
+
+    const TYPE_ORDER = 1;
+    const TYPE_REGISTRATION = 2;
+    const TYPE_REVIEW = 3;
+    const TYPE_SIGN_IN = 4;
+    const TYPE_REFERRAL = 5;
+    const TYPE_BIRTHDAY = 6;
+}

+ 50 - 0
packages/Longyi/RewardPoints/src/Models/RewardPointCustomer.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace Longyi\RewardPoints\Models;
+
+use Illuminate\Database\Eloquent\Model;
+use Webkul\Customer\Models\Customer;
+
+class RewardPointCustomer extends Model
+{
+    protected $table = 'mw_reward_point_customer';
+    protected $primaryKey = 'customer_id';
+    public $timestamps = false;
+
+    protected $fillable = [
+        'customer_id',
+        'mw_reward_point',
+        'mw_friend_id',
+        'subscribed_balance_update',
+        'subscribed_point_expiration',
+        'last_checkout'
+    ];
+
+    protected $casts = [
+        'mw_reward_point' => 'integer',
+        'mw_friend_id' => 'integer',
+        'subscribed_balance_update' => 'integer',
+        'subscribed_point_expiration' => 'integer',
+        'last_checkout' => 'datetime'
+    ];
+
+    public function customer()
+    {
+        return $this->belongsTo(Customer::class, 'customer_id');
+    }
+
+    public function friend()
+    {
+        return $this->belongsTo(Customer::class, 'mw_friend_id');
+    }
+
+    public function history()
+    {
+        return $this->hasMany(RewardPointHistory::class, 'customer_id', 'customer_id');
+    }
+
+    public function signRecords()
+    {
+        return $this->hasMany(RewardPointCustomerSign::class, 'customer_id', 'customer_id');
+    }
+}

+ 33 - 0
packages/Longyi/RewardPoints/src/Models/RewardPointCustomerSign.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace Longyi\RewardPoints\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class RewardPointCustomerSign extends Model
+{
+    protected $table = 'mw_reward_point_customer_sign';
+    protected $primaryKey = 'id';
+
+    protected $fillable = [
+        'customer_id',
+        'sign_date',
+        'count_date',
+        'point',
+        'code',
+        'created',
+        'updated'
+    ];
+
+    protected $casts = [
+        'count_date' => 'integer',
+        'point' => 'integer'
+    ];
+
+    protected $dates = ['created', 'updated'];
+
+    public function customer()
+    {
+        return $this->belongsTo(RewardPointCustomer::class, 'customer_id');
+    }
+}

+ 45 - 0
packages/Longyi/RewardPoints/src/Models/RewardPointHistory.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace Longyi\RewardPoints\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class RewardPointHistory extends Model
+{
+    protected $table = 'mw_reward_point_history';
+    protected $primaryKey = 'history_id';
+    public $timestamps = false;
+
+    protected $fillable = [
+        'customer_id',
+        'type_of_transaction',
+        'amount',
+        'balance',
+        'transaction_detail',
+        'transaction_time',
+        'history_order_id',
+        'expired_day',
+        'expired_time',
+        'point_remaining',
+        'check_time',
+        'status'
+    ];
+
+    protected $casts = [
+        'type_of_transaction' => 'integer',
+        'amount' => 'integer',
+        'balance' => 'integer',
+        'history_order_id' => 'integer',
+        'expired_day' => 'integer',
+        'point_remaining' => 'integer',
+        'check_time' => 'integer',
+        'status' => 'integer'
+    ];
+
+    protected $dates = ['transaction_time', 'expired_time'];
+
+    const STATUS_PENDING = 0;
+    const STATUS_COMPLETED = 1;
+    const STATUS_CANCELLED = 2;
+    const STATUS_EXPIRED = 3;
+}

+ 23 - 0
packages/Longyi/RewardPoints/src/Providers/EventServiceProvider.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace Longyi\RewardPoints\Providers;
+
+use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
+
+class EventServiceProvider extends ServiceProvider
+{
+    protected $listen = [
+        'customer.registration.after' => [
+            'Longyi\RewardPoints\Listeners\CustomerEvents@handleCustomerRegistration'
+        ],
+        'checkout.order.save.after' => [
+            'Longyi\RewardPoints\Listeners\OrderEvents@handleOrderPlacement'
+        ],
+        'sales.order.cancel.after' => [
+            'Longyi\RewardPoints\Listeners\OrderEvents@handleOrderCancellation'
+        ],
+        'customer.review.create.after' => [
+            'Longyi\RewardPoints\Listeners\ReviewEvents@handleReviewCreation'
+        ],
+    ];
+}

+ 54 - 0
packages/Longyi/RewardPoints/src/Providers/RewardPointsServiceProvider.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace Longyi\RewardPoints\Providers;
+
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Routing\Router;
+use Longyi\RewardPoints\Providers\EventServiceProvider;
+use Longyi\RewardPoints\Services\CartRewardPoints;
+
+class RewardPointsServiceProvider extends ServiceProvider
+{
+    public function boot(Router $router)
+    {
+        $this->loadMigrationsFrom(__DIR__ . '/../Database/Migrations');
+        $this->loadRoutesFrom(__DIR__ . '/../Routes/routes.php');
+        $this->loadViewsFrom(__DIR__ . '/../Resources/views', 'rewardpoints');
+        $this->loadTranslationsFrom(__DIR__ . '/../Resources/lang', 'rewardpoints');
+        
+        $this->publishes([
+            __DIR__ . '/../../publishable/assets' => public_path('vendor/longyi/rewardpoints'),
+        ], 'public');
+        
+        // Publish configuration
+        $this->publishes([
+            __DIR__ . '/../Config/rewardpoints.php' => config_path('rewardpoints.php'),
+        ], 'config');
+    }
+
+    public function register()
+    {
+        $this->mergeConfigFrom(
+            __DIR__ . '/../Config/rewardpoints.php', 'rewardpoints'
+        );
+        
+        $this->app->singleton('cartrewardpoints', function ($app) {
+            return new CartRewardPoints();
+        });
+        
+        $this->app->bind(
+            'Longyi\RewardPoints\Repositories\RewardPointRepository',
+            'Longyi\RewardPoints\Repositories\RewardPointRepository'
+        );
+        
+        $this->app->register(EventServiceProvider::class);
+        
+        $this->app->alias('cartrewardpoints', CartRewardPoints::class);
+        
+        if ($this->app->runningInConsole()) {
+            $this->commands([
+                \Longyi\RewardPoints\Console\Commands\CheckExpiredPoints::class,
+            ]);
+        }
+    }
+}

+ 137 - 0
packages/Longyi/RewardPoints/src/Repositories/RewardPointRepository.php

@@ -0,0 +1,137 @@
+<?php
+
+namespace Longyi\RewardPoints\Repositories;
+
+use Webkul\Core\Eloquent\Repository;
+use Longyi\RewardPoints\Models\RewardPointHistory;
+use Longyi\RewardPoints\Models\RewardPointCustomer;
+use Longyi\RewardPoints\Models\RewardActiveRule;
+use Carbon\Carbon;
+
+class RewardPointRepository extends Repository
+{
+    public function model()
+    {
+        return RewardPointHistory::class;
+    }
+
+    public function getCustomerPoints($customerId)
+    {
+        $customerPoints = RewardPointCustomer::where('customer_id', $customerId)->first();
+
+        if (!$customerPoints) {
+            return 0;
+        }
+
+        return $customerPoints->mw_reward_point;
+    }
+
+    public function addPoints($customerId, $type, $amount, $orderId = null, $detail = null)
+    {
+        $customerPoints = RewardPointCustomer::firstOrCreate(
+            ['customer_id' => $customerId],
+            [
+                'mw_reward_point' => 0,
+                'mw_friend_id' => 0,
+                'subscribed_balance_update' => 1,
+                'subscribed_point_expiration' => 1,
+                'last_checkout' => Carbon::now()
+            ]
+        );
+
+        $currentBalance = $customerPoints->mw_reward_point;
+        $newBalance = $currentBalance + $amount;
+
+        // Get rule for expiration
+        $rule = RewardActiveRule::where('type_of_transaction', $type)
+            ->where('status', 1)
+            ->first();
+
+        $expiredDay = $rule ? $rule->expired_day : 0;
+        $expiredTime = $expiredDay > 0 ? Carbon::now()->addDays($expiredDay) : null;
+
+        // Create history
+        $history = $this->create([
+            'customer_id' => $customerId,
+            'type_of_transaction' => $type,
+            'amount' => $amount,
+            'balance' => $newBalance,
+            'transaction_detail' => $detail,
+            'transaction_time' => Carbon::now(),
+            'history_order_id' => $orderId ?? 0,
+            'expired_day' => $expiredDay,
+            'expired_time' => $expiredTime,
+            'point_remaining' => $amount,
+            'check_time' => 1,
+            'status' => RewardPointHistory::STATUS_COMPLETED
+        ]);
+
+        // Update customer points
+        $customerPoints->mw_reward_point = $newBalance;
+        $customerPoints->save();
+
+        return $history;
+    }
+
+    public function deductPoints($customerId, $amount, $orderId = null, $detail = null)
+    {
+        $customerPoints = RewardPointCustomer::where('customer_id', $customerId)->first();
+
+        if (!$customerPoints || $customerPoints->mw_reward_point < $amount) {
+            return false;
+        }
+
+        $currentBalance = $customerPoints->mw_reward_point;
+        $newBalance = $currentBalance - $amount;
+
+        // Create history for deduction (using negative amount)
+        $history = $this->create([
+            'customer_id' => $customerId,
+            'type_of_transaction' => 0, // 0 for deduction
+            'amount' => -$amount,
+            'balance' => $newBalance,
+            'transaction_detail' => $detail,
+            'transaction_time' => Carbon::now(),
+            'history_order_id' => $orderId ?? 0,
+            'expired_day' => 0,
+            'expired_time' => null,
+            'point_remaining' => 0,
+            'check_time' => 1,
+            'status' => RewardPointHistory::STATUS_COMPLETED
+        ]);
+
+        // Update customer points
+        $customerPoints->mw_reward_point = $newBalance;
+        $customerPoints->save();
+
+        return $history;
+    }
+
+    public function getHistory($customerId, $limit = 20)
+    {
+        return $this->where('customer_id', $customerId)
+            ->orderBy('transaction_time', 'desc')
+            ->paginate($limit);
+    }
+
+    public function checkExpiredPoints()
+    {
+        $expiredHistories = $this->where('expired_time', '<', Carbon::now())
+            ->where('status', RewardPointHistory::STATUS_COMPLETED)
+            ->where('point_remaining', '>', 0)
+            ->get();
+
+        foreach ($expiredHistories as $history) {
+            $customerPoints = RewardPointCustomer::where('customer_id', $history->customer_id)->first();
+
+            if ($customerPoints) {
+                $customerPoints->mw_reward_point -= $history->point_remaining;
+                $customerPoints->save();
+            }
+
+            $history->status = RewardPointHistory::STATUS_EXPIRED;
+            $history->point_remaining = 0;
+            $history->save();
+        }
+    }
+}

+ 111 - 0
packages/Longyi/RewardPoints/src/Resources/views/admin/rules/create.blade.php

@@ -0,0 +1,111 @@
+@extends('admin::layouts.master')
+
+@section('page_title')
+    Create Reward Rule
+@stop
+
+@section('content-wrapper')
+    <div class="content full-page">
+        <div class="page-header">
+            <div class="page-title">
+                <h1>Create Reward Rule</h1>
+            </div>
+            <div class="page-action">
+                <a href="{{ route('admin.reward-points.rules.index') }}" class="btn btn-lg btn-primary">
+                    Back
+                </a>
+            </div>
+        </div>
+
+        <div class="page-content">
+            <form method="POST" action="{{ route('admin.reward-points.rules.store') }}">
+                @csrf
+
+                <div class="form-group">
+                    <label>Rule Name *</label>
+                    <input type="text" name="rule_name" class="form-control" required>
+                    @error('rule_name')
+                    <span class="text-danger">{{ $message }}</span>
+                    @enderror
+                </div>
+
+                <div class="form-group">
+                    <label>Transaction Type *</label>
+                    <select name="type_of_transaction" class="form-control" required>
+                        <option value="">Select Type</option>
+                        @foreach($transactionTypes as $key => $value)
+                            <option value="{{ $key }}">{{ $value }}</option>
+                        @endforeach
+                    </select>
+                    @error('type_of_transaction')
+                    <span class="text-danger">{{ $message }}</span>
+                    @enderror
+                </div>
+
+                <div class="form-group">
+                    <label>Store View</label>
+                    <select name="store_view[]" class="form-control" multiple>
+                        @foreach($storeViews as $key => $value)
+                            <option value="{{ $key }}">{{ $value }}</option>
+                        @endforeach
+                    </select>
+                    <small class="form-text text-muted">Leave empty for all stores</small>
+                </div>
+
+                <div class="form-group">
+                    <label>Customer Groups</label>
+                    <select name="customer_group_ids[]" class="form-control" multiple>
+                        @foreach($customerGroups as $key => $value)
+                            <option value="{{ $key }}">{{ $value }}</option>
+                        @endforeach
+                    </select>
+                    <small class="form-text text-muted">Leave empty for all customer groups</small>
+                </div>
+
+                <div class="form-group">
+                    <label>Reward Points *</label>
+                    <input type="number" name="reward_point" class="form-control" required min="0">
+                    @error('reward_point')
+                    <span class="text-danger">{{ $message }}</span>
+                    @enderror
+                </div>
+
+                <div class="form-group">
+                    <label>Status</label>
+                    <select name="status" class="form-control">
+                        <option value="1">Active</option>
+                        <option value="0">Inactive</option>
+                    </select>
+                </div>
+
+                <div class="form-group">
+                    <label>Default Expired</label>
+                    <select name="default_expired" class="form-control">
+                        <option value="1">Yes</option>
+                        <option value="0">No</option>
+                    </select>
+                </div>
+
+                <div class="form-group">
+                    <label>Expired Day</label>
+                    <input type="number" name="expired_day" class="form-control" value="0">
+                    <small class="form-text text-muted">0 means never expires</small>
+                </div>
+
+                <div class="form-group">
+                    <label>Coupon Code</label>
+                    <input type="text" name="coupon_code" class="form-control">
+                </div>
+
+                <div class="form-group">
+                    <label>Comment</label>
+                    <textarea name="comment" class="form-control" rows="3"></textarea>
+                </div>
+
+                <div class="form-group">
+                    <button type="submit" class="btn btn-lg btn-primary">Create Rule</button>
+                </div>
+            </form>
+        </div>
+    </div>
+@stop

+ 74 - 0
packages/Longyi/RewardPoints/src/Resources/views/admin/rules/index.blade.php

@@ -0,0 +1,74 @@
+@extends('admin::layouts.master')
+
+@section('page_title')
+    Reward Rules
+@stop
+
+@section('content-wrapper')
+    <div class="content full-page">
+        <div class="page-header">
+            <div class="page-title">
+                <h1>Reward Rules</h1>
+            </div>
+            <div class="page-action">
+                <a href="{{ route('admin.reward-points.rules.create') }}" class="btn btn-lg btn-primary">
+                    Add Rule
+                </a>
+            </div>
+        </div>
+
+        <div class="page-content">
+            <div class="table">
+                <table class="table table-bordered">
+                    <thead>
+                    <tr>
+                        <th>ID</th>
+                        <th>Rule Name</th>
+                        <th>Transaction Type</th>
+                        <th>Reward Points</th>
+                        <th>Status</th>
+                        <th>Actions</th>
+                    </tr>
+                    </thead>
+                    <tbody>
+                    @foreach($rules as $rule)
+                        <tr>
+                            <td>{{ $rule->rule_id }}</td>
+                            <td>{{ $rule->rule_name }}</td>
+                            <td>
+                                @switch($rule->type_of_transaction)
+                                    @case(1) Order @break
+                                    @case(2) Registration @break
+                                    @case(3) Product Review @break
+                                    @case(4) Daily Sign In @break
+                                    @case(5) Referral @break
+                                    @case(6) Birthday @break
+                                @endswitch
+                            </td>
+                            <td>{{ $rule->reward_point }}</td>
+                            <td>
+                                <span class="badge badge-{{ $rule->status ? 'success' : 'danger' }}">
+                                    {{ $rule->status ? 'Active' : 'Inactive' }}
+                                </span>
+                            </td>
+                            <td>
+                                <a href="{{ route('admin.reward-points.rules.edit', $rule->rule_id) }}" class="btn btn-sm btn-primary">
+                                    Edit
+                                </a>
+                                <form action="{{ route('admin.reward-points.rules.delete', $rule->rule_id) }}" method="POST" style="display:inline">
+                                    @csrf
+                                    @method('DELETE')
+                                    <button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure?')">
+                                        Delete
+                                    </button>
+                                </form>
+                            </td>
+                        </tr>
+                    @endforeach
+                    </tbody>
+                </table>
+                {{ $rules->links() }}
+            </div>
+        </div>
+    </div>
+@stop

+ 84 - 0
packages/Longyi/RewardPoints/src/Resources/views/lang/en/rewardpoints.php

@@ -0,0 +1,84 @@
+<?php
+
+return [
+    'customer' => [
+        'my-reward-points' => 'My Reward Points',
+        'available-points' => 'Available Points',
+        'daily-sign-in' => 'Daily Sign In',
+        'sign-in-now' => 'Sign In Now',
+        'signed-today' => 'Signed In Today',
+        'sign-in-success' => 'Sign in successful! You earned',
+        'sign-in-failed' => 'Failed to sign in',
+        'sign-in-error' => 'An error occurred during sign in',
+        'current-streak' => 'Current Streak',
+        'days' => 'days',
+        'points-history' => 'Points History',
+        'date' => 'Date',
+        'type' => 'Type',
+        'description' => 'Description',
+        'points' => 'Points',
+        'balance' => 'Balance',
+        'status' => 'Status',
+        'order' => 'Order',
+        'registration' => 'Registration',
+        'review' => 'Review',
+        'sign-in' => 'Sign In',
+        'referral' => 'Referral',
+        'birthday' => 'Birthday',
+        'other' => 'Other',
+        'pending' => 'Pending',
+        'completed' => 'Completed',
+        'cancelled' => 'Cancelled',
+        'expired' => 'Expired',
+        'no-history' => 'No points history yet',
+    ],
+    
+    'admin' => [
+        'reward-points' => 'Reward Points',
+        'rules' => 'Rules',
+        'customers' => 'Customers',
+        'reports' => 'Reports',
+        'add-rule' => 'Add Rule',
+        'edit-rule' => 'Edit Rule',
+        'delete-rule' => 'Delete Rule',
+        'rule-name' => 'Rule Name',
+        'transaction-type' => 'Transaction Type',
+        'reward-points' => 'Reward Points',
+        'status' => 'Status',
+        'active' => 'Active',
+        'inactive' => 'Inactive',
+        'actions' => 'Actions',
+        'edit' => 'Edit',
+        'delete' => 'Delete',
+        'create-rule' => 'Create Rule',
+        'update-rule' => 'Update Rule',
+        'back' => 'Back',
+        'save' => 'Save',
+        'cancel' => 'Cancel',
+        'customer-list' => 'Customer List',
+        'customer-name' => 'Customer Name',
+        'email' => 'Email',
+        'points-balance' => 'Points Balance',
+        'add-points' => 'Add Points',
+        'deduct-points' => 'Deduct Points',
+        'reason' => 'Reason',
+        'total-points' => 'Total Points',
+        'total-customers' => 'Total Customers',
+        'average-points' => 'Average Points',
+        'export' => 'Export',
+        'bulk-update' => 'Bulk Update',
+        'date-range' => 'Date Range',
+        'start-date' => 'Start Date',
+        'end-date' => 'End Date',
+        'points-earned' => 'Points Earned',
+        'points-redeemed' => 'Points Redeemed',
+        'transactions' => 'Transactions',
+    ],
+    
+    'validation' => [
+        'points-required' => 'Points are required',
+        'points-invalid' => 'Invalid points amount',
+        'customer-not-found' => 'Customer not found',
+        'rule-not-found' => 'Rule not found',
+    ],
+];

+ 173 - 0
packages/Longyi/RewardPoints/src/Resources/views/shop/customer/index.blade.php

@@ -0,0 +1,173 @@
+@extends('shop::layouts.master')
+
+@section('page_title')
+    My Reward Points
+@stop
+
+@section('content-wrapper')
+    <div class="account-grid">
+        <div class="account-for">
+            <h1>My Reward Points</h1>
+        </div>
+
+        <div class="account-table-content">
+            <div class="row">
+                <div class="col-12 col-md-4">
+                    <div class="card points-summary">
+                        <div class="card-body text-center">
+                            <h3 class="points-balance">{{ $points }}</h3>
+                            <p class="text-muted">Available Points</p>
+                        </div>
+                    </div>
+                </div>
+                
+                <div class="col-12 col-md-8">
+                    <div class="card">
+                        <div class="card-header">
+                            <h4>Daily Sign In</h4>
+                        </div>
+                        <div class="card-body text-center">
+                            <button 
+                                id="signInBtn" 
+                                class="btn btn-primary btn-lg"
+                                onclick="handleSignIn()"
+                            >
+                                Sign In Now
+                            </button>
+                            <p class="mt-3" id="streakInfo"></p>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="card mt-4">
+                <div class="card-header">
+                    <h4>Points History</h4>
+                </div>
+                <div class="card-body table-responsive">
+                    <table class="table">
+                        <thead>
+                            <tr>
+                                <th>Date</th>
+                                <th>Type</th>
+                                <th>Description</th>
+                                <th>Points</th>
+                                <th>Balance</th>
+                                <th>Status</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            @forelse($history as $item)
+                                <tr>
+                                    <td>{{ $item->transaction_time->format('Y-m-d H:i') }}</td>
+                                    <td>
+                                        @switch($item->type_of_transaction)
+                                            @case(1)
+                                                <span class="badge badge-info">Order</span>
+                                                @break
+                                            @case(2)
+                                                <span class="badge badge-success">Registration</span>
+                                                @break
+                                            @case(3)
+                                                <span class="badge badge-warning">Review</span>
+                                                @break
+                                            @case(4)
+                                                <span class="badge badge-primary">Sign In</span>
+                                                @break
+                                            @case(5)
+                                                <span class="badge badge-secondary">Referral</span>
+                                                @break
+                                            @case(6)
+                                                <span class="badge badge-danger">Birthday</span>
+                                                @break
+                                            @default
+                                                <span class="badge badge-dark">Other</span>
+                                        @endswitch
+                                    </td>
+                                    <td>{{ $item->transaction_detail ?? '-' }}</td>
+                                    <td class="{{ $item->amount > 0 ? 'text-success' : 'text-danger' }}">
+                                        {{ $item->amount > 0 ? '+' : '' }}{{ $item->amount }}
+                                    </td>
+                                    <td>{{ $item->balance }}</td>
+                                    <td>
+                                        @switch($item->status)
+                                            @case(0)
+                                                <span class="badge badge-warning">Pending</span>
+                                                @break
+                                            @case(1)
+                                                <span class="badge badge-success">Completed</span>
+                                                @break
+                                            @case(2)
+                                                <span class="badge badge-danger">Cancelled</span>
+                                                @break
+                                            @case(3)
+                                                <span class="badge badge-dark">Expired</span>
+                                                @break
+                                        @endswitch
+                                    </td>
+                                </tr>
+                            @empty
+                                <tr>
+                                    <td colspan="6" class="text-center">No points history yet</td>
+                                </tr>
+                            @endforelse
+                        </tbody>
+                    </table>
+                    
+                    @if($history->hasPages())
+                        <div class="pagination-wrapper mt-3">
+                            {{ $history->links() }}
+                        </div>
+                    @endif
+                </div>
+            </div>
+        </div>
+    </div>
+@stop
+
+@push('scripts')
+    <script>
+        function handleSignIn() {
+            const signInBtn = document.getElementById('signInBtn');
+            signInBtn.disabled = true;
+            
+            fetch('{{ route('customer.reward-points.sign-in') }}', {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                    'X-CSRF-TOKEN': '{{ csrf_token() }}'
+                }
+            })
+            .then(response => response.json())
+            .then(data => {
+                if (data.success) {
+                    alert('Sign in successful! You earned ' + data.points + ' points!');
+                    location.reload();
+                } else {
+                    alert(data.error || 'Failed to sign in');
+                    signInBtn.disabled = false;
+                }
+            })
+            .catch(error => {
+                console.error('Error:', error);
+                alert('An error occurred during sign in');
+                signInBtn.disabled = false;
+            });
+        }
+        
+        fetch('{{ route('customer.reward-points.sign-status') }}')
+            .then(response => response.json())
+            .then(data => {
+                if (data.signed_today) {
+                    document.getElementById('signInBtn').disabled = true;
+                    document.getElementById('signInBtn').textContent = 'Signed In Today';
+                }
+                
+                if (data.current_streak > 0) {
+                    document.getElementById('streakInfo').innerHTML = 
+                        'Current Streak: <strong>' + data.current_streak + '</strong> days';
+                }
+            })
+            .catch(error => console.error('Error checking sign status:', error));
+    </script>
+@endpush

+ 112 - 0
packages/Longyi/RewardPoints/src/Routes/routes.php

@@ -0,0 +1,112 @@
+<?php
+use Illuminate\Support\Facades\Route;
+
+Route::group(['middleware' => ['web', 'customer'], 'prefix' => 'customer'], function () {
+    Route::get('reward-points', [
+        'as' => 'customer.reward-points.index',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@index',
+        'view' => 'rewardpoints::shop.customer.index'
+    ]);
+
+    Route::post('reward-points/sign-in', [
+        'as' => 'customer.reward-points.sign-in',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@signIn'
+    ]);
+
+    Route::get('reward-points/sign-status', [
+        'as' => 'customer.reward-points.sign-status',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@getSignStatus'
+    ]);
+        Route::post('apply-reward-points', [
+        'as' => 'checkout.apply-reward-points',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@applyPoints'
+    ]);
+    
+    Route::post('remove-reward-points', [
+        'as' => 'checkout.remove-reward-points',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@removePoints'
+    ]);
+    
+    Route::get('reward-points-info', [
+        'as' => 'checkout.reward-points-info',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\RewardPointsController@getPointsInfo'
+    ]);
+});
+
+// Admin routes
+Route::group(['middleware' => ['web', 'admin'], 'prefix' => 'admin/reward-points'], function () {
+    Route::get('rules', [
+        'as' => 'admin.reward-points.rules.index',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\RuleController@index'
+    ]);
+
+    Route::get('rules/create', [
+        'as' => 'admin.reward-points.rules.create',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\RuleController@create'
+    ]);
+
+    Route::post('rules', [
+        'as' => 'admin.reward-points.rules.store',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\RuleController@store'
+    ]);
+
+    Route::get('rules/{id}/edit', [
+        'as' => 'admin.reward-points.rules.edit',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\RuleController@edit'
+    ]);
+
+    Route::put('rules/{id}', [
+        'as' => 'admin.reward-points.rules.update',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\RuleController@update'
+    ]);
+
+    Route::delete('rules/{id}', [
+        'as' => 'admin.reward-points.rules.delete',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\RuleController@destroy'
+    ]);
+
+    Route::post('rules/{id}/update-status', [
+        'as' => 'admin.reward-points.rules.update-status',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\RuleController@updateStatus'
+    ]);
+
+    Route::get('customers', [
+        'as' => 'admin.reward-points.customers.index',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@index'
+    ]);
+
+    Route::get('customers/{customerId}', [
+        'as' => 'admin.reward-points.customers.show',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@show'
+    ]);
+
+    Route::post('customers/add-points', [
+        'as' => 'admin.reward-points.customers.add-points',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@addPoints'
+    ]);
+
+    Route::post('customers/deduct-points', [
+        'as' => 'admin.reward-points.customers.deduct-points',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@deductPoints'
+    ]);
+
+    Route::post('customers/bulk-update', [
+        'as' => 'admin.reward-points.customers.bulk-update',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@bulkUpdate'
+    ]);
+
+    Route::get('customers/export', [
+        'as' => 'admin.reward-points.customers.export',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\CustomerController@export'
+    ]);
+
+    Route::get('reports', [
+        'as' => 'admin.reward-points.reports.index',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\ReportController@index'
+    ]);
+
+    Route::get('reports/export', [
+        'as' => 'admin.reward-points.reports.export',
+        'uses' => 'Longyi\RewardPoints\Http\Controllers\Admin\ReportController@export'
+    ]);
+});

+ 263 - 0
packages/Longyi/RewardPoints/src/Services/CartRewardPoints.php

@@ -0,0 +1,263 @@
+<?php
+
+namespace Longyi\RewardPoints\Services;
+
+use Longyi\RewardPoints\Repositories\RewardPointRepository;
+use Longyi\RewardPoints\Models\RewardPointCustomer;
+use Webkul\Checkout\Facades\Cart;
+use Webkul\Customer\Facades\Customer;
+
+class CartRewardPoints
+{
+    /**
+     * Session key for points used in cart
+     */
+    const POINTS_USED_KEY = 'reward_points_used';
+    
+    /**
+     * @var RewardPointRepository
+     */
+    protected $rewardPointRepository;
+    
+    /**
+     * CartRewardPoints constructor.
+     */
+    public function __construct()
+    {
+        $this->rewardPointRepository = app(RewardPointRepository::class);
+    }
+    
+    /**
+     * Get available points for current customer
+     */
+    public function getAvailablePoints()
+    {
+        if (!Customer::check()) {
+            return 0;
+        }
+        
+        return $this->rewardPointRepository->getCustomerPoints(Customer::getCustomerId());
+    }
+    
+    /**
+     * Get points used in current cart
+     */
+    public function getPointsUsed()
+    {
+        if (!session()->has(self::POINTS_USED_KEY)) {
+            return 0;
+        }
+        
+        return session()->get(self::POINTS_USED_KEY);
+    }
+    
+    /**
+     * Get discount amount from points
+     */
+    public function getDiscountAmount($points = null)
+    {
+        $pointsUsed = $points ?: $this->getPointsUsed();
+        
+        if ($pointsUsed <= 0) {
+            return 0;
+        }
+        
+        $pointValue = config('rewardpoints.general.point_value', 0.01);
+        
+        return $pointsUsed * $pointValue;
+    }
+    
+    /**
+     * Apply points to cart
+     */
+    public function applyPoints($points)
+    {
+        $cart = Cart::getCart();
+        
+        if (!$cart) {
+            return false;
+        }
+        
+        $availablePoints = $this->getAvailablePoints();
+        
+        if ($points <= 0) {
+            $this->removePoints();
+            return false;
+        }
+        
+        if ($points > $availablePoints) {
+            return false;
+        }
+        
+        // Calculate maximum points that can be used based on cart total
+        $maxPointsByAmount = $this->getMaxPointsByCartTotal();
+        
+        if ($points > $maxPointsByAmount) {
+            return false;
+        }
+        
+        // Save points to session
+        session()->put(self::POINTS_USED_KEY, $points);
+        
+        // Recalculate cart
+        Cart::collectTotals();
+        
+        return true;
+    }
+    
+    /**
+     * Remove points from cart
+     */
+    public function removePoints()
+    {
+        session()->forget(self::POINTS_USED_KEY);
+        Cart::collectTotals();
+        
+        return true;
+    }
+    
+    /**
+     * Calculate discount based on points
+     */
+    public function calculateDiscount()
+    {
+        $pointsUsed = $this->getPointsUsed();
+        
+        if ($pointsUsed <= 0) {
+            return 0;
+        }
+        
+        $discountAmount = $this->getDiscountAmount($pointsUsed);
+        
+        return $discountAmount;
+    }
+    
+    /**
+     * Get maximum points that can be used based on cart total
+     */
+    protected function getMaxPointsByCartTotal()
+    {
+        $cart = Cart::getCart();
+        
+        if (!$cart) {
+            return 0;
+        }
+        
+        $cartTotal = $cart->grand_total;
+        
+        // Get maximum discount percentage (default: 100%)
+        $maxDiscountPercentage = core()->getConfigData('rewardpoints.general.max_discount_percentage', 100);
+        
+        // Calculate maximum discount amount
+        $maxDiscountAmount = ($cartTotal * $maxDiscountPercentage) / 100;
+        
+        // Convert discount amount to points
+        $pointValue = core()->getConfigData('rewardpoints.general.point_value', 0.01);
+        $maxPoints = $pointValue > 0 ? floor($maxDiscountAmount / $pointValue) : 0;
+        
+        return $maxPoints;
+    }
+    
+    /**
+     * Get discount details for cart
+     */
+    public function getDiscountDetails()
+    {
+        $pointsUsed = $this->getPointsUsed();
+        
+        if ($pointsUsed <= 0) {
+            return null;
+        }
+        
+        $discountAmount = $this->getDiscountAmount();
+        
+        return [
+            'points_used' => $pointsUsed,
+            'discount_amount' => $discountAmount,
+            'formatted_discount' => core()->formatPrice($discountAmount),
+            'available_points' => $this->getAvailablePoints(),
+            'points_remaining' => $this->getAvailablePoints() - $pointsUsed
+        ];
+    }
+    
+    /**
+     * Process order points
+     */
+    public function processOrderPoints($order)
+    {
+        $pointsUsed = $this->getPointsUsed();
+        
+        if ($pointsUsed <= 0) {
+            return false;
+        }
+        
+        $customerId = $order->customer_id;
+        
+        if (!$customerId) {
+            return false;
+        }
+        
+        // Deduct points from customer
+        $discountAmount = $this->getDiscountAmount($pointsUsed);
+        
+        $result = $this->rewardPointRepository->deductPoints(
+            $customerId,
+            $pointsUsed,
+            $order->id,
+            "Points redeemed for order #{$order->increment_id} (Discount: " . core()->formatPrice($discountAmount) . ")"
+        );
+        
+        // Clear session
+        $this->removePoints();
+        
+        return $result;
+    }
+    
+    /**
+     * Get points value in currency
+     */
+    public function getPointsValue($points)
+    {
+        $pointValue = core()->getConfigData('rewardpoints.general.point_value', 0.01);
+        return $points * $pointValue;
+    }
+    
+    /**
+     * Get currency value in points
+     */
+    public function getPointsFromValue($value)
+    {
+        $pointValue = core()->getConfigData('rewardpoints.general.point_value', 0.01);
+        return $pointValue > 0 ? floor($value / $pointValue) : 0;
+    }
+    
+    /**
+     * Validate if points can be applied to cart
+     */
+    public function validatePoints($points)
+    {
+        $cart = Cart::getCart();
+        
+        if (!$cart) {
+            return 'Cart not found';
+        }
+        
+        $availablePoints = $this->getAvailablePoints();
+        
+        if ($points <= 0) {
+            return 'Points must be greater than 0';
+        }
+        
+        if ($points > $availablePoints) {
+            return "You only have {$availablePoints} points available";
+        }
+        
+        $maxPoints = $this->getMaxPointsByCartTotal();
+        
+        if ($points > $maxPoints) {
+            return "Maximum {$maxPoints} points can be used for this order";
+        }
+        
+        return true;
+    }
+}