llp 1 неделя назад
Родитель
Сommit
41c0b8d5c2
42 измененных файлов с 1902 добавлено и 0 удалено
  1. 1 0
      .gitignore
  2. 1 0
      bootstrap/providers.php
  3. 1 0
      composer.json
  4. 23 0
      packages/Longyi/Gift/package.json
  5. 6 0
      packages/Longyi/Gift/postcss.config.js
  6. 28 0
      packages/Longyi/Gift/src/Config/acl.php
  7. 11 0
      packages/Longyi/Gift/src/Config/admin-menu.php
  8. 7 0
      packages/Longyi/Gift/src/Contracts/GiftCards.php
  9. 204 0
      packages/Longyi/Gift/src/DataGrids/GiftCards/GiftCardsDataGrid.php
  10. 35 0
      packages/Longyi/Gift/src/Database/Migrations/2026_04_01_184903_create_gift_cards_table.php
  11. 31 0
      packages/Longyi/Gift/src/Database/Migrations/2026_04_01_230815_add_giftcard_fields_to_cart_table.php
  12. 29 0
      packages/Longyi/Gift/src/Database/Migrations/2026_04_01_230829_add_giftcard_fields_to_orders_table.php
  13. 29 0
      packages/Longyi/Gift/src/Database/Migrations/2026_04_01_230841_add_giftcard_info_to_invoices_table.php
  14. 171 0
      packages/Longyi/Gift/src/Http/Controllers/Admin/GiftController.php
  15. 158 0
      packages/Longyi/Gift/src/Http/Controllers/Shop/GiftController.php
  16. 25 0
      packages/Longyi/Gift/src/Http/Resources/CustomCartResource.php
  17. 67 0
      packages/Longyi/Gift/src/Listeners/CartClearHandler.php
  18. 36 0
      packages/Longyi/Gift/src/Listeners/Gift.php
  19. 36 0
      packages/Longyi/Gift/src/Listeners/OrderPlacedHandler.php
  20. 93 0
      packages/Longyi/Gift/src/Models/GiftCards.php
  21. 9 0
      packages/Longyi/Gift/src/Models/GiftCardsProxy.php
  22. 24 0
      packages/Longyi/Gift/src/Providers/EventServiceProvider.php
  23. 56 0
      packages/Longyi/Gift/src/Providers/GiftServiceProvider.php
  24. 15 0
      packages/Longyi/Gift/src/Providers/ModuleServiceProvider.php
  25. 16 0
      packages/Longyi/Gift/src/Repositories/GiftCardsRepository.php
  26. 23 0
      packages/Longyi/Gift/src/Resources/assets/css/app.css
  27. 12 0
      packages/Longyi/Gift/src/Resources/assets/images/icon-temp-active.svg
  28. 12 0
      packages/Longyi/Gift/src/Resources/assets/images/icon-temp.svg
  29. 4 0
      packages/Longyi/Gift/src/Resources/assets/js/app.js
  30. 51 0
      packages/Longyi/Gift/src/Resources/lang/en/app.php
  31. 51 0
      packages/Longyi/Gift/src/Resources/lang/zh_CN/app.php
  32. 230 0
      packages/Longyi/Gift/src/Resources/views/admin/create.blade.php
  33. 25 0
      packages/Longyi/Gift/src/Resources/views/admin/index.blade.php
  34. 1 0
      packages/Longyi/Gift/src/Resources/views/admin/layouts/style.blade.php
  35. 279 0
      packages/Longyi/Gift/src/Resources/views/components/giftcard-cartsummary.blade.php
  36. 11 0
      packages/Longyi/Gift/src/Resources/views/shop/index.blade.php
  37. 18 0
      packages/Longyi/Gift/src/Routes/admin-routes.php
  38. 13 0
      packages/Longyi/Gift/src/Routes/shop-routes.php
  39. 4 0
      packages/Longyi/Gift/tailwind.config.js
  40. 46 0
      packages/Longyi/Gift/vite.config.js
  41. 5 0
      packages/Webkul/Sales/src/Transformers/OrderResource.php
  42. 5 0
      packages/Webkul/Shop/src/Http/Resources/CartResource.php

+ 1 - 0
.gitignore

@@ -33,3 +33,4 @@ yarn.lock
 yarn-error.log
 /test-results
 /playwright-report
+public/nginx.htaccess

+ 1 - 0
bootstrap/providers.php

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

+ 1 - 0
composer.json

@@ -68,6 +68,7 @@
             "Longyi\\Core\\": "packages/Longyi/Core/src/",
             "Longyi\\DynamicMenu\\": "packages/Longyi/DynamicMenu/src/",
             "Longyi\\RewardPoints\\": "packages/Longyi/RewardPoints/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",

+ 23 - 0
packages/Longyi/Gift/package.json

@@ -0,0 +1,23 @@
+{
+  "private": true,
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build"
+  },
+  "devDependencies": {
+    "autoprefixer": "^10.4.14",
+    "axios": "^1.4.0",
+    "laravel-vite-plugin": "^0.7.2",
+    "postcss": "^8.4.23",
+    "tailwindcss": "^3.3.2",
+    "vite": "^4.0.0",
+    "vue": "^3.2.47"
+  },
+  "dependencies": {
+    "@vee-validate/i18n": "^4.9.1",
+    "@vee-validate/rules": "^4.9.1",
+    "@vitejs/plugin-vue": "^4.2.3",
+    "mitt": "^3.0.1",
+    "vee-validate": "^4.9.1"
+  }
+}

+ 6 - 0
packages/Longyi/Gift/postcss.config.js

@@ -0,0 +1,6 @@
+module.exports = {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+  },
+}

+ 28 - 0
packages/Longyi/Gift/src/Config/acl.php

@@ -0,0 +1,28 @@
+<?php
+
+return [
+    [
+        'key'   => 'gift',
+        'name'  => '礼品卡管理',
+        'route' => 'admin.gift.index',
+        'sort'  => 2
+    ],
+    [
+        'key'   => 'gift.create',
+        'name'  => '创建礼品卡',
+        'route' => 'admin.gift.store',
+        'sort'  => 1
+    ],
+    [
+        'key'   => 'gift.edit',
+        'name'  => '编辑礼品卡',
+        'route' => 'admin.gift.update',
+        'sort'  => 2
+    ],
+    [
+        'key'   => 'gift.delete',
+        'name'  => '删除礼品卡',
+        'route' => 'admin.gift.destroy',
+        'sort'  => 3
+    ]
+];

+ 11 - 0
packages/Longyi/Gift/src/Config/admin-menu.php

@@ -0,0 +1,11 @@
+<?php
+
+return [
+    [
+        'key'   => 'gift',
+        'name'  => 'Gift',
+        'route' => 'admin.gift.index',
+        'sort'  => 100,
+        'icon'  => 'icon-sales',
+    ]
+];

+ 7 - 0
packages/Longyi/Gift/src/Contracts/GiftCards.php

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

+ 204 - 0
packages/Longyi/Gift/src/DataGrids/GiftCards/GiftCardsDataGrid.php

@@ -0,0 +1,204 @@
+<?php
+
+namespace Longyi\Gift\DataGrids\GiftCards;
+
+use Illuminate\Support\Facades\DB;
+use Webkul\DataGrid\DataGrid;
+
+class GiftCardsDataGrid extends DataGrid
+{
+    /**
+     * Prepare query builder.
+     *
+     * @return \Illuminate\Database\Query\Builder
+     */
+    public function prepareQueryBuilder()
+    {
+        $queryBuilder = DB::table('gift_cards')
+            ->leftJoin('customers', 'gift_cards.customer_id', '=', 'customers.id')
+            ->select(
+                'gift_cards.id',
+                'gift_cards.giftcard_number',
+                'gift_cards.giftcard_amount',
+                'gift_cards.used_giftcard_amount',
+                'gift_cards.remaining_giftcard_amount',
+                'gift_cards.customer_id',
+                'customers.email as customer_email',
+                'customers.first_name',
+                'customers.last_name',
+                'gift_cards.expirationdate',
+                'gift_cards.giftcard_status',
+                'gift_cards.created_at',
+                'gift_cards.updated_at'
+            );
+
+        $this->addFilter('id', 'gift_cards.id');
+        $this->addFilter('giftcard_number', 'gift_cards.giftcard_number');
+        $this->addFilter('giftcard_amount', 'gift_cards.giftcard_amount');
+        $this->addFilter('giftcard_status', 'gift_cards.giftcard_status');
+        $this->addFilter('created_at', 'gift_cards.created_at');
+        $this->addFilter('customer_email', 'customers.email');
+
+        return $queryBuilder;
+    }
+
+    /**
+     * Add columns.
+     *
+     * @return void
+     */
+    public function prepareColumns()
+    {
+        $this->addColumn([
+            'index'      => 'id',
+            'label'      => trans('gift::app.admin.datagrid.id'),
+            'type'       => 'integer',
+            'filterable' => true,
+            'sortable'   => true,
+        ]);
+
+//        $this->addColumn([
+//            'index'      => 'giftcard_number',
+//            'label'      => trans('gift::app.admin.datagrid.giftcard-number'),
+//            'type'       => 'string',
+//            'searchable' => true,
+//            'filterable' => true,
+//            'sortable'   => true,
+//        ]);
+
+        $this->addColumn([
+            'index'      => 'giftcard_amount',
+            'label'      => trans('gift::app.admin.datagrid.giftcard-amount'),
+            'type'       => 'aggregate',
+            'filterable' => false,
+            'sortable'   => true,
+            'closure'    => function ($row) {
+                return core()->formatPrice($row->giftcard_amount);
+            },
+        ]);
+
+        $this->addColumn([
+            'index'      => 'used_giftcard_amount',
+            'label'      => trans('gift::app.admin.datagrid.used-amount'),
+            'type'       => 'aggregate',
+            'filterable' => false,
+            'sortable'   => true,
+            'closure'    => function ($row) {
+                return core()->formatPrice($row->used_giftcard_amount);
+            },
+        ]);
+
+        $this->addColumn([
+            'index'      => 'remaining_giftcard_amount',
+            'label'      => trans('gift::app.admin.datagrid.remaining-amount'),
+            'type'       => 'aggregate',
+            'filterable' => false,
+            'sortable'   => true,
+            'closure'    => function ($row) {
+                return core()->formatPrice($row->remaining_giftcard_amount);
+            },
+        ]);
+        $this->addColumn([
+            'index'      => 'customer_email',
+            'label'      => '用户邮箱',
+            'type'       => 'string',
+            'filterable' => true,
+            'sortable'   => true,
+            'searchable' => true,
+            'closure'    => function ($row) {
+                if ($row->customer_email) {
+                    $customerName = '';
+                    if ($row->first_name || $row->last_name) {
+                        $customerName = ' (' . trim($row->first_name . ' ' . $row->last_name) . ')';
+                    }
+                    return $row->customer_email . $customerName;
+                }
+                return '<span class="text-gray-400">未绑定</span>';
+            },
+        ]);
+//        $this->addColumn([
+//            'index'      => 'customer_id',
+//            'label'      => trans('gift::app.admin.datagrid.customer-id'),
+//            'type'       => 'integer',
+//            'filterable' => true,
+//            'sortable'   => true,
+//        ]);
+
+        $this->addColumn([
+            'index'      => 'expirationdate',
+            'label'      => trans('gift::app.admin.datagrid.expiration-date'),
+            'type'       => 'date',
+            'filterable' => true,
+            'sortable'   => true,
+        ]);
+
+        $this->addColumn([
+            'index'      => 'giftcard_status',
+            'label'      => trans('gift::app.admin.datagrid.status'),
+            'type'       => 'boolean',
+            'filterable' => true,
+            'sortable'   => true,
+            'closure'    => function ($row) {
+                if ($row->giftcard_status == 1) {
+                    return '<span class="badge badge-md badge-success">未使用</span>';
+                } else {
+                    return '<span class="badge badge-md badge-warning">已使用</span>';
+                }
+            },
+        ]);
+
+        $this->addColumn([
+            'index'      => 'created_at',
+            'label'      => trans('gift::app.admin.datagrid.created-at'),
+            'type'       => 'datetime',
+            'filterable' => true,
+            'sortable'   => true,
+        ]);
+    }
+
+    /**
+     * Prepare actions.
+     *
+     * @return void
+     */
+    public function prepareActions()
+    {
+//        if (bouncer()->hasPermission('gift.edit')) {
+//            $this->addAction([
+//                'icon'   => 'icon-edit',
+//                'title'  => trans('gift::app.admin.datagrid.edit'),
+//                'method' => 'GET',
+//                'url'    => function ($row) {
+//                    return route('admin.gift.edit', $row->id);
+//                },
+//            ]);
+//        }
+
+        if (bouncer()->hasPermission('gift.delete')) {
+            $this->addAction([
+                'icon'   => 'icon-delete',
+                'title'  => trans('gift::app.admin.datagrid.delete'),
+                'method' => 'DELETE',
+                'url'    => function ($row) {
+                    return route('admin.gift.destroy', $row->id);
+                },
+            ]);
+        }
+    }
+
+    /**
+     * Prepare mass actions.
+     *
+     * @return void
+     */
+    public function prepareMassActions()
+    {
+        if (bouncer()->hasPermission('gift.delete')) {
+            $this->addMassAction([
+                'title'  => trans('gift::app.admin.datagrid.delete'),
+                'method' => 'POST',
+                'url'    => route('admin.gift.mass_delete'),
+            ]);
+        }
+    }
+}

+ 35 - 0
packages/Longyi/Gift/src/Database/Migrations/2026_04_01_184903_create_gift_cards_table.php

@@ -0,0 +1,35 @@
+<?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
+    {
+        Schema::create('gift_cards', function (Blueprint $table) {
+            $table->id();
+            $table->string('giftcard_number', 100)->index();
+            $table->decimal('giftcard_amount', 10, 2)->default(0)->nullable()->comment('金额');
+            $table->decimal('used_giftcard_amount', 10, 2)->default(0)->nullable()->comment('使用金额');
+            $table->decimal('remaining_giftcard_amount', 10, 2)->default(0)->nullable()->comment('剩余金额');
+            $table->integer('customer_id')->index()->default(0)->nullable();
+            $table->tinyInteger('type')->default(1)->nullable()->comment('1:普通 2:other');
+            $table->dateTime('expirationdate')->nullable()->comment('过期时间');
+            $table->string('giftcard_status', 2)->default(1)->nullable()->comment('1:未使用 2:已使用');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('gift_cards');
+    }
+};

+ 31 - 0
packages/Longyi/Gift/src/Database/Migrations/2026_04_01_230815_add_giftcard_fields_to_cart_table.php

@@ -0,0 +1,31 @@
+<?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
+    {
+        Schema::table('cart', function (Blueprint $table) {
+            $table->string('giftcard_number', 191)->nullable()->after('applied_cart_rule_ids');
+            $table->decimal('giftcard_amount', 10, 2)->nullable()->after('giftcard_number');
+            $table->decimal('remaining_giftcard_amount', 10, 2)->default(0.00)->after('giftcard_amount');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::table('cart', function (Blueprint $table) {
+            $table->dropColumn('giftcard_number');
+            $table->dropColumn('giftcard_amount');
+        });
+    }
+};

+ 29 - 0
packages/Longyi/Gift/src/Database/Migrations/2026_04_01_230829_add_giftcard_fields_to_orders_table.php

@@ -0,0 +1,29 @@
+<?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
+    {
+        Schema::table('orders', function (Blueprint $table) {
+            $table->string('giftcard_number', 191)->nullable()->after('coupon_code');
+            $table->decimal('giftcard_amount', 10, 2)->nullable()->after('giftcard_number');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::table('orders', function (Blueprint $table) {
+            $table->dropColumn(['giftcard_number', 'giftcard_amount']);
+        });
+    }
+};

+ 29 - 0
packages/Longyi/Gift/src/Database/Migrations/2026_04_01_230841_add_giftcard_info_to_invoices_table.php

@@ -0,0 +1,29 @@
+<?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
+    {
+        Schema::table('invoices', function (Blueprint $table) {
+            $table->string('giftcard_number', 191)->nullable()->after('grand_total'); // Choose the appropriate position for the column
+            $table->decimal('giftcard_amount', 10, 2)->nullable()->after('giftcard_number'); // Using 'nullable' because not all invoices may use a giftcard
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::table('invoices', function (Blueprint $table) {
+            $table->dropColumn(['giftcard_number', 'giftcard_amount']);
+        });
+    }
+};

+ 171 - 0
packages/Longyi/Gift/src/Http/Controllers/Admin/GiftController.php

@@ -0,0 +1,171 @@
+<?php
+
+namespace Longyi\Gift\Http\Controllers\Admin;
+
+use Illuminate\Http\JsonResponse;
+use Illuminate\View\View;
+use Longyi\Gift\Models\GiftCards;
+use Webkul\Admin\Http\Controllers\Controller;
+use Longyi\Gift\DataGrids\GiftCards\GiftCardsDataGrid;
+use Longyi\Gift\Repositories\GiftCardsRepository;
+use Illuminate\Support\Str;
+use Webkul\Customer\Models\Customer;
+
+
+class GiftController extends Controller
+{
+
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct(
+        protected GiftCardsRepository $giftCardsRepository
+    ) {
+    }
+    /**
+     * Display a listing of the resource.
+     */
+    public function index(): View|JsonResponse
+    {
+        if (request()->ajax()) {
+             return datagrid(GiftCardsDataGrid::class)->process();
+        }
+
+        return view('gift::admin.index');
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     */
+    public function create(): View
+    {
+        return view('gift::admin.create');
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     */
+    public function store(): JsonResponse|\Illuminate\Http\RedirectResponse
+    {
+        $data = request()->validate([
+            'giftcard_amount' => 'required|numeric|min:0',
+            'customer_id' => 'required|integer',
+            'expirationdate' => 'required|date_format:Y-m-d',
+            'giftcard_status' => 'required|in:1,2',
+        ]);
+
+        $data['giftcard_number'] = $this->generateUniqueGiftCardNumber();
+        $data['used_giftcard_amount'] = 0;
+        $data['type'] = 1;
+        $data['remaining_giftcard_amount'] = $data['giftcard_amount'] ?? 0;
+
+        $this->giftCardsRepository->create($data);
+
+        if (request()->expectsJson()) {
+            return new JsonResponse([
+                'message' => '礼品卡创建成功',
+            ]);
+        }
+        return redirect()->route('admin.gift.index');
+    }
+
+    /**
+     * Validate customer email via AJAX and return customer_id
+     */
+    public function validateEmail(): JsonResponse
+    {
+        $email = request()->input('email');
+
+        if (empty($email)) {
+            return new JsonResponse([
+                'valid' => true,
+                'customer_id' => null,
+                'message' => '',
+            ]);
+        }
+
+        $customer = Customer::where('email', $email)->first();
+
+        if (!$customer) {
+            return new JsonResponse([
+                'valid' => false,
+                'customer_id' => null,
+                'message' => '该邮箱对应的用户不存在',
+            ], 422);
+        }
+
+        return new JsonResponse([
+            'valid' => true,
+            'customer_id' => $customer->id,
+            'message' => '邮箱验证通过',
+            'customer_name' => $customer->first_name . ' ' . $customer->last_name,
+        ]);
+    }
+    /**
+     * Generate a unique 16-digit gift card number
+     *
+     * @return string
+     */
+    private function generateUniqueGiftCardNumber()
+    {
+        do {
+            // Generate a random string of characters
+            $randomString = Str::upper(Str::random(16));
+
+            // Format the string into groups of four separated by dashes
+            $code = implode('-', str_split($randomString, 4));
+
+            // Check if the code already exists in the database
+            $exists = GiftCards::where('giftcard_number', $code)->exists();
+        } while ($exists);
+
+        return $code;
+    }
+    /**
+     * Remove the specified resource from storage.
+     */
+    public function destroy(int $id): JsonResponse
+    {
+        try {
+            $this->giftCardsRepository->delete($id);
+
+            return new JsonResponse([
+                'message' => '礼品卡删除成功',
+            ]);
+        } catch (\Exception $e) {
+            return new JsonResponse([
+                'message' => '礼品卡删除失败',
+            ], 400);
+        }
+    }
+
+    /**
+     * Mass delete resources.
+     */
+    public function massDelete(): JsonResponse
+    {
+        $indices = request()->input('indices');
+
+        if (empty($indices)) {
+            return new JsonResponse([
+                'message' => '请选择要删除的礼品卡',
+            ], 422);
+        }
+
+        try {
+            foreach ($indices as $id) {
+                $this->giftCardsRepository->delete($id);
+            }
+
+            return new JsonResponse([
+                'message' => '选中的礼品卡删除成功',
+            ]);
+        } catch (\Exception $e) {
+            return new JsonResponse([
+                'message' => '批量删除失败',
+            ], 400);
+        }
+    }
+}

+ 158 - 0
packages/Longyi/Gift/src/Http/Controllers/Shop/GiftController.php

@@ -0,0 +1,158 @@
+<?php
+
+namespace Longyi\Gift\Http\Controllers\Shop;
+
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+use Illuminate\Http\Response;
+use Webkul\Checkout\Facades\Cart;
+use Webkul\Shop\Http\Controllers\Controller;
+use Longyi\Gift\Repositories\GiftCardsRepository;
+use Longyi\Gift\Models\GiftCards;
+use Longyi\Gift\Http\Resources\CustomCartResource;
+
+class GiftController extends Controller
+{
+    public function __construct(
+        protected GiftCardsRepository $giftCardRepository
+    ) {
+    }
+
+    public function index()
+    {
+        echo 111;exit;
+        return view('giftcard::shop.index');
+    }
+    /**
+     * Get customer's gift cards list
+     */
+    public function getCustomerGiftCards(): JsonResponse
+    {
+        try {
+            $customerId = auth()->user()->id;
+
+            $giftCards = GiftCards::where('customer_id', $customerId)
+                ->where('remaining_giftcard_amount', '>', 0)
+                ->where('expirationdate', '>=', now())
+                ->orderBy('created_at', 'desc')
+                ->get();
+
+            return response()->json([
+                'success' => true,
+                'data' => $giftCards->map(function ($giftCard) {
+                    return [
+                        'id' => $giftCard->id,
+                        'giftcard_number' => $giftCard->giftcard_number,
+                        'giftcard_amount' => $giftCard->giftcard_amount,
+                        'remaining_giftcard_amount' => $giftCard->remaining_giftcard_amount,
+                        'used_giftcard_amount' => $giftCard->used_giftcard_amount,
+                        'expirationdate' => $giftCard->expirationdate->format('Y-m-d'),
+                        'giftcard_status' => $giftCard->giftcard_status,
+                    ];
+                }),
+            ]);
+        } catch (\Exception $e) {
+            return response()->json([
+                'success' => false,
+                'message' => 'Failed to fetch gift cards',
+                'error' => $e->getMessage(),
+            ], Response::HTTP_INTERNAL_SERVER_ERROR);
+        }
+    }
+    /**
+     * Activate Giftcard.
+     */
+    public function activateGiftCard(Request $request)
+   {
+       $customerId = auth()->user()->id;
+       try {
+           $validatedData = $this->validate($request, [
+               'giftcard_number' => 'required',
+           ]);
+           $giftCard = GiftCards::where('giftcard_number', $validatedData['giftcard_number'])
+               ->where('customer_id', $customerId)->first();
+           $cart = Cart::getCart();
+           if (!$giftCard) {
+               return (new JsonResource([
+                   'data'     => new CustomCartResource (Cart::getCart()),
+                   'message'  => trans('Coupon not found.'),
+               ]))->response()->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
+           }
+           if ($giftCard->remaining_giftcard_amount <= 0) {
+               return (new JsonResource([
+                   'data'     => new CustomCartResource(Cart::getCart()),
+                   'message'  => trans('Gift card already used.'),
+               ]))->response()->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
+           }
+           if (!empty($cart->giftcard_amount)) {
+               return (new JsonResource([
+                   'data'     => new CustomCartResource(Cart::getCart()),
+                   'message'  => trans('The shopping cart has been paid with a gift card.'),
+               ]))->response()->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
+           }
+
+
+           $cartTotal = $cart->grand_total;
+           $remainingAmount = $giftCard->remaining_giftcard_amount - $cartTotal;
+
+           if ($remainingAmount >= 0) {
+               // Apply the gift card to the cart
+               GiftCards::setGiftCardCode($giftCard);
+               Cart::collectTotals();
+
+               return response()->json([
+                   'message' => 'Gift card applied successfully',
+                   'remaining_amount' => $remainingAmount,
+                   'giftcard_number' => $giftCard->giftcard_number,
+               ]);
+           } else {
+               // Apply the gift card to the cart
+               GiftCards::setGiftCardCode($giftCard);
+               Cart::collectTotals();
+               // Gift card amount becomes zero as it's fully used
+               return response()->json([
+                   'message' => 'Gift card applied successfully',
+                   'remaining_amount' => 0,
+                   'giftcard_number' => $giftCard->giftcard_number,
+               ]);
+           }
+       } catch (\Exception $e) {
+           return (new JsonResource([
+               'data'    => new CustomCartResource(Cart::getCart()),
+               'message' => trans('error'),
+               'error' => $e->getMessage()
+           ]))->response()->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
+       }
+   }
+    /**
+     * Remove applied Giftcard from the cart.
+     */
+    public function destroyGiftCard(): JsonResource
+    {
+        // Get the cart instance
+        $cart = Cart::getCart();
+
+        // Remove gift card related fields from the cart
+        GiftCards::removeGiftCardCode();
+        Cart::collectTotals();
+        // If a gift card was applied, update GiftCardBalance accordingly
+        if ($cart->giftcard_number) {
+            $giftCards = GiftCards::where('giftcard_number', $cart->giftcard_number)
+                ->where('customer_id', auth()->user()->id)
+                ->first();
+            if ($giftCards) {
+                // Reset used and remaining amounts
+                $giftCards->used_giftcard_amount = 0;
+                $giftCards->remaining_giftcard_amount = $giftCards->giftcard_amount;
+                $giftCards->save();
+            }
+        }
+
+        // Return response with updated cart data
+        return new JsonResource([
+            'data'     => new CustomCartResource(Cart::getCart()),
+            'message'  => trans('gift::app.giftcard.remove'),
+        ]);
+    }
+}

+ 25 - 0
packages/Longyi/Gift/src/Http/Resources/CustomCartResource.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Longyi\Gift\Http\Resources;
+
+use Webkul\Shop\Http\Resources\CartResource;
+
+class CustomCartResource extends CartResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array
+     */
+    public function toArray($request)
+    {
+        $data = parent::toArray($request);
+
+        $data['giftcard_number'] = $this->giftcard_number;
+        $data['giftcard_amount'] = $this->giftcard_amount;
+        $data['remaining_giftcard_amount'] = $this->remaining_giftcard_amount;
+
+        return $data;
+    }
+}

+ 67 - 0
packages/Longyi/Gift/src/Listeners/CartClearHandler.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace Longyi\Gift\Listeners;
+
+use Webkul\Checkout\Facades\Cart;
+use Longyi\Gift\Models\GiftCards;
+
+class CartClearHandler
+{
+    /**
+     * Handle cart clear events.
+     *
+     * @param  int  $itemId
+     * @return void
+     */
+    public function afterDeleteItem($itemId)
+    {
+        $cart = Cart::getCart();
+
+        if (!$cart) {
+            return;
+        }
+
+        // Check if cart has no more items after deletion
+        // Refresh the cart to get latest items count
+        $cart->refresh();
+
+        // If cart is empty or has no active items, restore gift card
+        if ($cart->items->count() === 0) {
+            $this->restoreGiftCardBalance($cart);
+        }
+    }
+
+    /**
+     * Restore gift card balance when cart is cleared
+     */
+    protected function restoreGiftCardBalance($cart)
+    {
+        if (!$cart->giftcard_number) {
+            return;
+        }
+
+        $giftCardNumber = $cart->giftcard_number;
+        $giftCardAmount = $cart->giftcard_amount;
+
+        // Clear cart gift card info
+        $cart->giftcard_number = null;
+        $cart->giftcard_amount = null;
+        $cart->remaining_giftcard_amount = null;
+        $cart->save();
+
+        // Restore gift card balance
+        if ($giftCardNumber && $giftCardAmount > 0) {
+            $customerId = auth()->user()?->id ?? $cart->customer_id;
+
+            $giftCard = GiftCards::where('giftcard_number', $giftCardNumber)
+                ->where('customer_id', $customerId)
+                ->first();
+
+            if ($giftCard) {
+                $giftCard->used_giftcard_amount -= $giftCardAmount;
+                $giftCard->remaining_giftcard_amount += $giftCardAmount;
+                $giftCard->save();
+            }
+        }
+    }
+}

+ 36 - 0
packages/Longyi/Gift/src/Listeners/Gift.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Longyi\Gift\Listeners;
+
+use Webkul\Paypal\Payment\SmartButton;
+use Webkul\Sales\Repositories\OrderTransactionRepository;
+use Longyi\Gift\Models\GiftCards;
+
+class Gift
+{
+    /**
+     * Create a new listener instance.
+     *
+     * @return void
+     */
+    public function __construct(
+        protected SmartButton $smartButton,
+        protected OrderTransactionRepository $orderTransactionRepository
+    ) {}
+
+    /**
+     * Save the transaction data for online payment.
+     *
+     * @param  \Webkul\Sales\Models\Invoice  $invoice
+     * @return void
+     */
+    public function applyGiftCard($cart)
+    {
+        $giftCardAmount = $cart->giftcard_amount ?? 0;
+        if ($giftCardAmount) {
+            $cart->grand_total = max(0, $cart->grand_total - $giftCardAmount);
+            $cart->base_grand_total = max(0, $cart->base_grand_total - $giftCardAmount);
+            $cart->save();
+        }
+    }
+}

+ 36 - 0
packages/Longyi/Gift/src/Listeners/OrderPlacedHandler.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Longyi\Gift\Listeners;
+
+use Longyi\Gift\Models\GiftCards;
+
+class OrderPlacedHandler
+{
+    /**
+     * Handle order placed event.
+     *
+     * @param  \Webkul\Sales\Contracts\Order  $order
+     * @return void
+     */
+    public function afterPlaceOrder($order)
+    {
+        if (!$order || !$order->cart) {
+            return;
+        }
+
+        $cart = $order->cart;
+
+        if (!$cart->giftcard_number) {
+            return;
+        }
+
+        $giftCardNumber = $cart->giftcard_number;
+
+        $giftCard = GiftCards::where('giftcard_number', $giftCardNumber)->first();
+
+        if ($giftCard) {
+            $giftCard->giftcard_status = 2;
+            $giftCard->save();
+        }
+    }
+}

+ 93 - 0
packages/Longyi/Gift/src/Models/GiftCards.php

@@ -0,0 +1,93 @@
+<?php
+
+namespace Longyi\Gift\Models;
+
+use Brainstream\Giftcard\Models\GiftCard;
+use Brainstream\Giftcard\Models\GiftCardBalance;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Longyi\Gift\Contracts\GiftCards as GiftCardsContract;
+
+class GiftCards extends Model implements GiftCardsContract
+{
+    use HasFactory;
+
+    protected $table = 'gift_cards';
+
+    protected $fillable = [
+        'giftcard_number',
+        'giftcard_amount',
+        'used_giftcard_amount',
+        'remaining_giftcard_amount',
+        'customer_id',
+        'expirationdate',
+        'giftcard_status',
+    ];
+
+    protected $casts = [
+        'giftcard_amount' => 'decimal:2',
+        'used_giftcard_amount' => 'decimal:2',
+        'remaining_giftcard_amount' => 'decimal:2',
+        'customer_id' => 'integer',
+        'expirationdate' => 'date',
+    ];
+
+    public static function setGiftCardCode($giftcard)
+    {
+        $customerId = auth()->user()->id;
+        if (!$giftcard) {
+            return false;
+        }
+        $cart = cart()->getCart();
+        $cart->giftcard_number = $giftcard->giftcard_number;
+        $cart->giftcard_amount = $giftcard->remaining_giftcard_amount;
+
+        $giftCards = static::where('giftcard_number', $giftcard->giftcard_number)->where('customer_id', $customerId)->first();
+
+        if ($giftCards) {
+            if ($cart->grand_total <= $giftcard->remaining_giftcard_amount) {
+                $cart->giftcard_amount = $cart->grand_total;
+                $giftCards->used_giftcard_amount += $cart->grand_total;
+                $giftCards->remaining_giftcard_amount -= $cart->grand_total;
+                $cart->remaining_giftcard_amount = $giftCards->remaining_giftcard_amount;
+                $cart->grand_total = 0;
+            } else {
+                $giftCards->used_giftcard_amount += $giftcard->remaining_giftcard_amount;
+                $giftCards->remaining_giftcard_amount = 0;
+                $cart->remaining_giftcard_amount = 0;
+                $cart->grand_total -= $giftcard->remaining_giftcard_amount;
+            }
+
+            $giftCards->save();
+        }
+
+        $cart->save();
+
+        return true;
+    }
+
+    public static function removeGiftCardCode()
+    {
+        $cart = cart()->getCart();
+        $giftCardNumber = $cart->giftcard_number;
+        $giftcardAmount = $cart->giftcard_amount;
+
+        $cart->giftcard_number = null;
+        $cart->giftcard_amount = null;
+        $cart->remaining_giftcard_amount = null;
+
+        $cart->save();
+
+        if ($giftCardNumber) {
+            $giftCards = static::where('giftcard_number', $giftCardNumber)->first();
+
+            if ($giftCards) {
+                $giftCards->used_giftcard_amount -= $giftcardAmount;
+                $giftCards->remaining_giftcard_amount += $giftcardAmount;
+                $giftCards->save();
+            }
+        }
+
+        return true;
+    }
+}

+ 9 - 0
packages/Longyi/Gift/src/Models/GiftCardsProxy.php

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

+ 24 - 0
packages/Longyi/Gift/src/Providers/EventServiceProvider.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace Longyi\Gift\Providers;
+
+use Illuminate\Support\Facades\Event;
+use Illuminate\Support\ServiceProvider;
+
+class EventServiceProvider extends ServiceProvider
+{
+    /**
+     * Bootstrap services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        Event::listen('checkout.cart.collect.totals.after', 'Longyi\Gift\Listeners\Gift@applyGiftCard');
+        Event::listen('checkout.cart.delete.after', 'Longyi\Gift\Listeners\CartClearHandler@afterDeleteItem');
+        Event::listen('checkout.order.save.after', 'Longyi\Gift\Listeners\OrderPlacedHandler@afterPlaceOrder');
+        Event::listen('bagisto.shop.checkout.onepage.summary.coupon.after', function($viewRenderEventManager) {
+            $viewRenderEventManager->addTemplate('gift::components.giftcard-cartsummary');
+        });
+    }
+}

+ 56 - 0
packages/Longyi/Gift/src/Providers/GiftServiceProvider.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace Longyi\Gift\Providers;
+
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Support\Facades\Event;
+use Longyi\Gift\Providers\EventServiceProvider;
+use Longyi\Gift\Repositories\GiftCardsRepository;
+
+class GiftServiceProvider extends ServiceProvider
+{
+    /**
+     * Register services.
+     */
+    public function register(): void
+    {
+        $this->registerConfig();
+    }
+
+    /**
+     * Bootstrap services.
+     */
+    public function boot(): void
+    {
+        $this->loadMigrationsFrom(__DIR__ . '/../Database/Migrations');
+
+        $this->loadRoutesFrom(__DIR__ . '/../Routes/admin-routes.php');
+
+        $this->loadRoutesFrom(__DIR__ . '/../Routes/shop-routes.php');
+
+        $this->loadTranslationsFrom(__DIR__ . '/../Resources/lang', 'gift');
+
+        $this->loadViewsFrom(__DIR__ . '/../Resources/views', 'gift');
+
+        Event::listen('bagisto.admin.layout.head', function($viewRenderEventManager) {
+            $viewRenderEventManager->addTemplate('gift::admin.layouts.style');
+        });
+        $this->app->register(EventServiceProvider::class);
+    }
+
+    /**
+     * Register package config.
+     *
+     * @return void
+     */
+    protected function registerConfig()
+    {
+        $this->mergeConfigFrom(
+            dirname(__DIR__) . '/Config/admin-menu.php', 'menu.admin'
+        );
+
+        $this->mergeConfigFrom(
+            dirname(__DIR__) . '/Config/acl.php', 'acl'
+        );
+    }
+}

+ 15 - 0
packages/Longyi/Gift/src/Providers/ModuleServiceProvider.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Longyi\Gift\Providers;
+
+use Konekt\Concord\BaseModuleServiceProvider;
+
+class ModuleServiceProvider extends BaseModuleServiceProvider
+{
+    /**
+     * Models.
+     *
+     * @var array
+     */
+    protected $models = [];
+}

+ 16 - 0
packages/Longyi/Gift/src/Repositories/GiftCardsRepository.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace Longyi\Gift\Repositories;
+
+use Webkul\Core\Eloquent\Repository;
+
+class GiftCardsRepository extends Repository
+{
+    /**
+     * Specify model class name.
+     */
+    public function model(): string
+    {
+        return 'Longyi\Gift\Models\GiftCards';
+    }
+}

+ 23 - 0
packages/Longyi/Gift/src/Resources/assets/css/app.css

@@ -0,0 +1,23 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer components {
+    .temp-icon {
+        width: 48px;
+        height: 48px;
+        display: inline-block;
+        background-size: cover;
+        background-image: url("../images/icon-temp.svg");
+    }
+
+    .active {
+        .temp-icon {
+            background-image: url("../images/icon-temp-active.svg");
+        }
+
+        &.temp-icon {
+            background-image: url("../images/icon-temp-active.svg");
+        }
+    }
+}

+ 12 - 0
packages/Longyi/Gift/src/Resources/assets/images/icon-temp-active.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Icon-Catalog-Active</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Icon-Catalog-Active" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+        <g transform="translate(9.000000, 7.000000)" stroke="#0041FF" stroke-width="2">
+            <rect id="Rectangle-2" x="0" y="0" width="30" height="34"></rect>
+        </g>
+    </g>
+</svg>

+ 12 - 0
packages/Longyi/Gift/src/Resources/assets/images/icon-temp.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Icon-Catalog</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Icon-Catalog" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+        <g transform="translate(9.000000, 7.000000)" stroke-width="2">
+            <rect id="Rectangle-2" stroke="#8E8E8E" x="0" y="0" width="30" height="34"></rect>
+        </g>
+    </g>
+</svg>

+ 4 - 0
packages/Longyi/Gift/src/Resources/assets/js/app.js

@@ -0,0 +1,4 @@
+/**
+ * This will track all the images and fonts for publishing.
+ */
+import.meta.glob(["../images/**"]);

+ 51 - 0
packages/Longyi/Gift/src/Resources/lang/en/app.php

@@ -0,0 +1,51 @@
+<?php
+
+return [
+    'admin' => [
+        'title' => '礼品卡管理',
+
+        'datagrid' => [
+            'id' => 'ID',
+            'giftcard-number' => '礼品卡号',
+            'giftcard-amount' => '卡金额',
+            'used-amount' => '已用金额',
+            'remaining-amount' => '剩余金额',
+            'customer-id' => '客户 ID',
+            'expiration-date' => '过期日期',
+            'status' => '状态',
+            'created-at' => '创建时间',
+            'edit' => '编辑',
+            'delete' => '删除',
+            'delete-success' => '选中的礼品卡删除成功',
+        ],
+
+        'create' => [
+            'title' => '创建礼品卡',
+            'save-btn' => '保存',
+            'back-btn' => '返回',
+            'general' => '常规信息',
+            'giftcard-number' => '礼品卡号',
+            'giftcard-amount' => '卡金额',
+            'customer-id' => '客户 ID',
+            'expirationdate' => '过期日期',
+            'giftcard-status' => '状态',
+            'unused' => '未使用',
+            'used' => '已使用',
+        ],
+
+        'edit' => [
+            'title' => '编辑礼品卡',
+            'save-btn' => '保存',
+            'back-btn' => '返回',
+        ],
+    ],
+    'giftcard' => [
+        'giftcard_amount' => 'Giftcard Amount',
+        'applied'   =>  'Giftcard Applied',
+        'discount'   =>  'Giftcard Discount',
+        'apply'   =>  'Apply Giftcard',
+        'enter-your-code' => 'Enter your code',
+        'remove'          => 'Remove Giftcard',
+        'remaining_giftcard_amount' => 'Remaining Amount',
+    ]
+];

+ 51 - 0
packages/Longyi/Gift/src/Resources/lang/zh_CN/app.php

@@ -0,0 +1,51 @@
+<?php
+
+return [
+    'admin' => [
+        'title' => '礼品卡管理',
+
+        'datagrid' => [
+            'id' => 'ID',
+            'giftcard-number' => '礼品卡号',
+            'giftcard-amount' => '卡金额',
+            'used-amount' => '已用金额',
+            'remaining-amount' => '剩余金额',
+            'customer-id' => '客户 ID',
+            'expiration-date' => '过期日期',
+            'status' => '状态',
+            'created-at' => '创建时间',
+            'edit' => '编辑',
+            'delete' => '删除',
+            'delete-success' => '选中的礼品卡删除成功',
+        ],
+
+        'create' => [
+            'title' => '创建礼品卡',
+            'save-btn' => '保存',
+            'back-btn' => '返回',
+            'general' => '常规信息',
+            'giftcard-number' => '礼品卡号',
+            'giftcard-amount' => '卡金额',
+            'customer-id' => '客户 ID',
+            'expirationdate' => '过期日期',
+            'giftcard-status' => '状态',
+            'unused' => '未使用',
+            'used' => '已使用',
+        ],
+
+        'edit' => [
+            'title' => '编辑礼品卡',
+            'save-btn' => '保存',
+            'back-btn' => '返回',
+        ],
+    ],
+    'giftcard' => [
+        'giftcard_amount' => 'Giftcard Amount',
+        'applied'   =>  'Giftcard Applied',
+        'discount'   =>  'Giftcard Discount',
+        'apply'   =>  'Apply Giftcard',
+        'enter-your-code' => 'Enter your code',
+        'remove'          => 'Remove Giftcard',
+        'remaining_giftcard_amount' => 'Remaining Amount',
+    ],
+];

+ 230 - 0
packages/Longyi/Gift/src/Resources/views/admin/create.blade.php

@@ -0,0 +1,230 @@
+
+<x-admin::layouts>
+    <x-slot:title>
+        创建礼品卡
+    </x-slot>
+
+    <div class="flex justify-between items-center">
+        <p class="text-xl text-gray-800 font-bold">
+            创建礼品卡
+        </p>
+
+        <div class="flex gap-x-2.5">
+            <a
+                href="{{ route('admin.gift.index') }}"
+                class="px-4 py-2 bg-white border border-gray-300 rounded-lg hover:bg-gray-100"
+            >
+                返回
+            </a>
+
+            <button
+                type="submit"
+                form="gift-form"
+                class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
+            >
+                保存
+            </button>
+        </div>
+    </div>
+
+    <x-admin::form
+        :action="route('admin.gift.store')"
+        id="gift-form"
+    >
+        {{-- CSRF Token --}}
+        @csrf
+
+        <div class="flex flex-col gap-1.5 mt-2.5">
+            {{-- General Section --}}
+            <x-admin::accordion>
+                <x-slot:header>
+                    <p class="p-2.5 text-gray-800 text-base font-semibold">
+                        常规信息
+                    </p>
+                </x-slot>
+
+                <x-slot:content>
+                    <x-admin::form.control-group>
+                        <x-admin::form.control-group.label>
+                            卡金额
+                        </x-admin::form.control-group.label>
+
+                        <x-admin::form.control-group.control
+                            type="text"
+                            name="giftcard_amount"
+                            :value="old('giftcard_amount')"
+                            rules="numeric|min:0"
+                            label="卡金额"
+                            placeholder="0.00"
+                        />
+
+                        <x-admin::form.control-group.error
+                            control-name="giftcard_amount"
+                        />
+                    </x-admin::form.control-group>
+
+                    <x-admin::form.control-group>
+                        <x-admin::form.control-group.label>
+                            用户邮箱
+                        </x-admin::form.control-group.label>
+
+                        <x-admin::form.control-group.control
+                            type="text"
+                            name="customer_email_display"
+                            :value="old('customer_email_display')"
+                            rules="email"
+                            label="用户邮箱"
+                            placeholder="请输入用户邮箱"
+                            id="customer_email_create"
+                            class="customer-email-input"
+                        />
+
+                        <input type="hidden" name="customer_id" id="customer_id_create" value="{{ old('customer_id') }}" />
+
+                        <div id="email-validation-message-create" class="mt-1 text-sm"></div>
+
+                        <x-admin::form.control-group.error
+                            control-name="customer_email_display"
+                        />
+                    </x-admin::form.control-group>
+
+                    <x-admin::form.control-group>
+                        <x-admin::form.control-group.label class="required">
+                            过期日期
+                        </x-admin::form.control-group.label>
+
+                        <x-admin::form.control-group.control
+                            type="date"
+                            name="expirationdate"
+                            :value="old('expirationdate')"
+                            rules="required"
+                            label="过期日期"
+                        />
+
+                        <x-admin::form.control-group.error
+                            control-name="expirationdate"
+                        />
+                    </x-admin::form.control-group>
+
+                    <x-admin::form.control-group>
+                        <x-admin::form.control-group.label>
+                            状态
+                        </x-admin::form.control-group.label>
+
+                        <x-admin::form.control-group.control
+                            type="select"
+                            name="giftcard_status"
+                            :value="old('giftcard_status', 1)"
+                            label="状态"
+                        >
+                            <option value="1">未使用</option>
+                            <option value="2">已使用</option>
+                        </x-admin::form.control-group.control>
+
+                        <x-admin::form.control-group.error
+                            control-name="giftcard_status"
+                        />
+                    </x-admin::form.control-group>
+                </x-slot>
+            </x-admin::accordion>
+        </div>
+    </x-admin::form>
+
+    <script>
+        document.addEventListener('DOMContentLoaded', function() {
+            // 等待一小段时间确保所有组件都渲染完成
+            setTimeout(() => {
+                // 尝试多种方式获取邮箱输入框
+                const emailInput = document.getElementById('customer_email_create') ||
+                    document.querySelector('input[name="customer_email_display"]') ||
+                    document.querySelector('.customer-email-input') ||
+                    document.querySelector('input[placeholder="请输入用户邮箱"]');
+                const customerIdInput = document.getElementById('customer_id_create');
+                const validationMessage = document.getElementById('email-validation-message-create');
+                let debounceTimer;
+
+                console.log('=== Debug Info ===');
+                console.log('Email input:', emailInput);
+                console.log('Customer ID input:', customerIdInput);
+                console.log('Validation message:', validationMessage);
+
+                // 打印所有 input 元素,帮助调试
+                const allInputs = document.querySelectorAll('input');
+                console.log('All inputs:', Array.from(allInputs).map(input => ({
+                    name: input.name,
+                    id: input.id,
+                    class: input.className,
+                    placeholder: input.placeholder
+                })));
+
+                if (emailInput) {
+                    console.log('✓ Email input found:', emailInput);
+
+                    emailInput.addEventListener('blur', function() {
+                        console.log('Blur event triggered, value:', this.value);
+                        validateEmail(this.value.trim());
+                    });
+                } else {
+                    console.error('✗ Email input element not found!');
+                }
+
+                function validateEmail(email) {
+                    clearTimeout(debounceTimer);
+
+                    if (!email) {
+                        if (validationMessage) validationMessage.innerHTML = '';
+                        if (customerIdInput) customerIdInput.value = '';
+                        return;
+                    }
+
+                    debounceTimer = setTimeout(() => {
+                        console.log('Validating email:', email);
+                        if (validationMessage) {
+                            validationMessage.innerHTML = '<span class="text-gray-500">正在验证...</span>';
+                        }
+
+                        fetch("{{ route('admin.gift.validate_email') }}", {
+                            method: 'POST',
+                            headers: {
+                                'Content-Type': 'application/json',
+                                'X-CSRF-TOKEN': '{{ csrf_token() }}'
+                            },
+                            body: JSON.stringify({ email: email })
+                        })
+                            .then(response => {
+                                console.log('Response status:', response.status);
+                                return response.json();
+                            })
+                            .then(data => {
+                                console.log('Response data:', data);
+                                if (data.valid) {
+                                    if (validationMessage) {
+                                        validationMessage.innerHTML = `<span class="text-green-600">✓ ${data.message} (${data.customer_name})</span>`;
+                                    }
+                                    if (customerIdInput) {
+                                        customerIdInput.value = data.customer_id || '';
+                                    }
+                                } else {
+                                    if (validationMessage) {
+                                        validationMessage.innerHTML = `<span class="text-red-600">✗ ${data.message}</span>`;
+                                    }
+                                    if (customerIdInput) {
+                                        customerIdInput.value = '';
+                                    }
+                                }
+                            })
+                            .catch(error => {
+                                console.error('Validation error:', error);
+                                if (validationMessage) {
+                                    validationMessage.innerHTML = '<span class="text-red-600">✗ 验证失败,请稍后重试</span>';
+                                }
+                                if (customerIdInput) {
+                                    customerIdInput.value = '';
+                                }
+                            });
+                    }, 500);
+                }
+            }, 1000); // 延迟1秒执行,确保组件渲染完成
+        });
+    </script>
+</x-admin::layouts>

+ 25 - 0
packages/Longyi/Gift/src/Resources/views/admin/index.blade.php

@@ -0,0 +1,25 @@
+<x-admin::layouts>
+    <x-slot:title>
+        礼品卡管理
+    </x-slot>
+
+    @if(session('success'))
+        <div class="mb-4 px-4 py-3 bg-green-500 text-white rounded-lg">
+            {{ session('success') }}
+        </div>
+    @endif
+
+    <div class="flex justify-between items-center">
+        <p class="text-xl text-gray-800 font-bold">
+            礼品卡列表
+        </p>
+
+        <a href="{{ route('admin.gift.create') }}">
+            <button class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
+                添加礼品卡
+            </button>
+        </a>
+    </div>
+
+    <x-admin::datagrid src="{{ route('admin.gift.index') }}"></x-admin::datagrid>
+</x-admin::layouts>

+ 1 - 0
packages/Longyi/Gift/src/Resources/views/admin/layouts/style.blade.php

@@ -0,0 +1 @@
+<link rel="stylesheet" href="{{ asset('themes/default/assets/css/admin.css') }}">

+ 279 - 0
packages/Longyi/Gift/src/Resources/views/components/giftcard-cartsummary.blade.php

@@ -0,0 +1,279 @@
+{!! view_render_event('bagisto.shop.checkout.cart.summary.giftcard.before') !!}
+
+<v-gift-card
+    :cart="cart"
+    @giftcard-applied="getCart"
+    @giftcard-removed="getCart"
+>
+</v-gift-card>
+@pushOnce('scripts')
+    <script
+        type="text/x-template"
+        id="v-gift-card-template"
+    >
+        <div class="flex justify-between text-right">
+            <p class="text-base max-sm:text-sm max-sm:font-normal">
+                @{{ cart.giftcard_number ? ("@lang('gift::app.giftcard.applied')") : "@lang('gift::app.giftcard.discount')" }}
+            </p>
+
+            {!! view_render_event('bagisto.shop.checkout.cart.gift.before') !!}
+
+            <p class="text-base font-medium max-sm:text-sm">
+                <!-- Apply Giftcard modal -->
+                <x-shop::modal ref="giftCardModel">
+                    <!-- Modal Toggler -->
+                    <x-slot:toggle>
+                        <span
+                            class="text-[#0A49A7] cursor-pointer"
+                            role="button"
+                            tabindex="0"
+                            v-if="!cart || !cart.giftcard_number"
+                            @click="loadGiftCards"
+                        >
+                            @lang('gift::app.giftcard.apply')
+                        </span>
+                        <span
+                            v-else
+                            class="text-green-600 font-semibold"
+                        >
+                            @{{ cart.giftcard_number }}
+                        </span>
+                        </x-slot>
+
+                        <!-- Modal Header -->
+                        <x-slot:header>
+                            <h2 class="text-2xl font-medium max-sm:text-xl">
+                                @lang('gift::app.giftcard.apply')
+                            </h2>
+                            </x-slot>
+
+                            <!-- Modal Content -->
+                            <x-slot:content>
+                                <!-- Loading State -->
+                                <div v-if="loading" class="text-center py-4">
+                                    <p class="text-gray-500">加载中...</p>
+                                </div>
+
+                                <!-- Gift Card List -->
+                                <div v-else-if="giftCards.length > 0" class="space-y-3 max-h-96 overflow-y-auto">
+                                    <div
+                                        v-for="giftCard in giftCards"
+                                        :key="giftCard.id"
+                                        class="border rounded-lg p-4 hover:bg-gray-50 cursor-pointer transition-colors"
+                                        @click="selectGiftCard(giftCard)"
+                                        :class="{'bg-blue-50 border-blue-500': giftCardNumber === giftCard.giftcard_number}"
+                                    >
+                                        <div class="flex justify-between items-start">
+                                            <div class="flex-1">
+                                                <p class="font-semibold text-lg text-gray-800">
+                                                    @{{ giftCard.giftcard_number }}
+                                                </p>
+                                                <p class="text-sm text-gray-600 mt-1">
+                                                    余额: <span class="font-bold text-green-600">$@{{ giftCard.remaining_giftcard_amount }}</span>
+                                                </p>
+                                                <p class="text-xs text-gray-500 mt-1">
+                                                    有效期至: @{{ giftCard.expirationdate }}
+                                                </p>
+                                            </div>
+                                            <button
+                                                type="button"
+                                                class="px-4 py-2 bg-blue-600 text-blue-700 rounded-lg hover:bg-blue-700 text-sm"
+                                                @click.stop="applySelectedGiftCard(giftCard)"
+                                            >
+                                                使用
+                                            </button>
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <!-- No Gift Cards -->
+                                <div v-else class="text-center py-8">
+                                    <p class="text-gray-500">您还没有可用的礼品卡</p>
+                                </div>
+                                </x-slot>
+                </x-shop::modal>
+
+                <!-- Applied Coupon Information Container -->
+            <div
+                class="flex justify-between items-center text-xs font-small ml-4"
+                v-if="cart && cart.giftcard_number"
+            >
+                    <span
+                        class="icon-cancel text-2xl cursor-pointer text-red-500 hover:text-red-700"
+                        title="@lang('gift::app.giftcard.remove')"
+                        @click="destroyGiftCard"
+                    >
+                    </span>
+            </div>
+            </p>
+
+            {!! view_render_event('bagisto.shop.checkout.cart.giftcard.after') !!}
+        </div>
+    </script>
+
+    <script type="module">
+        app.component('v-gift-card', {
+            template: '#v-gift-card-template',
+
+            props: ['cart'],
+
+            data() {
+                return {
+                    isStoring: false,
+                    loading: false,
+                    giftCards: [],
+                    giftCardNumber: ''
+                };
+            },
+
+            methods: {
+                // 加载用户的礼品卡列表
+                loadGiftCards() {
+                    console.log('Loading gift cards...');
+                    this.loading = true;
+                    this.$axios.get("{{ route('shop.api.checkout.cart.giftcard.list') }}")
+                        .then((response) => {
+                            console.log('Gift cards response:', response.data);
+                            this.loading = false;
+                            if (response.data.success) {
+                                this.giftCards = response.data.data;
+                                console.log('Gift cards loaded:', this.giftCards.length);
+                            } else {
+                                console.error('Failed to load gift cards:', response.data.message);
+                                this.$emitter.emit('add-flash', {
+                                    type: 'error',
+                                    message: response.data.message || '加载礼品卡失败'
+                                });
+                            }
+                        })
+                        .catch((error) => {
+                            console.error('Failed to load gift cards:', error);
+                            this.loading = false;
+                            this.$emitter.emit('add-flash', {
+                                type: 'error',
+                                message: '加载礼品卡失败,请重试'
+                            });
+                        });
+                },
+
+                // 选择礼品卡(高亮显示)
+                selectGiftCard(giftCard) {
+                    this.giftCardNumber = giftCard.giftcard_number;
+                    console.log('Selected gift card:', this.giftCardNumber);
+                },
+
+                // 应用选中的礼品卡
+                applySelectedGiftCard(giftCard) {
+                    console.log('Applying gift card:', giftCard.giftcard_number);
+                    this.giftCardNumber = giftCard.giftcard_number;
+                    this.activateGiftCard();
+                },
+
+                activateGiftCard() {
+                    if (!this.giftCardNumber) {
+                        this.$emitter.emit('add-flash', {
+                            type: 'warning',
+                            message: '请选择一个礼品卡'
+                        });
+                        return;
+                    }
+
+                    this.isStoring = true;
+                    this.$axios.post("{{ route('shop.api.checkout.cart.giftcard.activate') }}", {
+                        giftcard_number: this.giftCardNumber,
+                        _token: "{{ csrf_token() }}"
+                    })
+                        .then((response) => {
+                            console.log('Gift card activated:', response.data);
+                            this.isStoring = false;
+                            this.$refs.giftCardModel.toggle();
+
+                            // 发送事件通知父组件刷新
+                            this.$emit('giftcard-applied', response.data);
+
+                            this.$emitter.emit('add-flash', {
+                                type: 'success',
+                                message: response.data.message || '礼品卡应用成功'
+                            });
+                            this.resetForm();
+                        })
+                        .catch((error) => {
+                            console.error('Activate gift card error:', error);
+                            this.isStoring = false;
+                            this.$refs.giftCardModel.toggle();
+                            this.$emitter.emit('add-flash', {
+                                type: 'error',
+                                message: error.response?.data?.message || '应用礼品卡失败'
+                            });
+                        });
+                },
+
+                resetForm() {
+                    this.giftCardNumber = '';
+                    this.giftCards = [];
+                },
+
+                destroyGiftCard() {
+                    console.log('Removing gift card...');
+                    this.$axios.delete("{{ route('shop.api.checkout.cart.giftcard.remove') }}", {
+                        headers: {
+                            'X-CSRF-TOKEN': "{{ csrf_token() }}"
+                        }
+                    })
+                        .then((response) => {
+                            console.log('Gift card removed:', response.data);
+                            // Reset form data
+                            this.resetForm();
+
+                            // 发送事件通知父组件刷新
+                            this.$emit('giftcard-removed');
+
+                            this.$emitter.emit('add-flash', {
+                                type: 'success',
+                                message: response.data.message || '礼品卡已移除'
+                            });
+                        })
+                        .catch(error => {
+                            console.error('Remove gift card error:', error);
+                            this.$emitter.emit('add-flash', {
+                                type: 'error',
+                                message: error.response?.data?.message || '移除礼品卡失败'
+                            });
+                        });
+                },
+            }
+        })
+    </script>
+
+@endPushOnce
+{!! view_render_event('bagisto.shop.checkout.cart.summary.giftcard.after') !!}
+
+<!-- Giftcard Discount -->
+{!! view_render_event('bagisto.shop.checkout.onepage.summary.giftcard_amount.before') !!}
+<div
+    class="flex text-right justify-between mt-2"
+    v-if="cart.giftcard_amount && parseFloat(cart.giftcard_amount) > 0"
+>
+    <p class="text-base">
+        @lang('gift::app.giftcard.giftcard_amount')
+    </p>
+
+    <p class="text-base font-medium">
+        - $@{{ cart.giftcard_amount }}
+    </p>
+</div>
+
+{{--<div--}}
+{{--    class="flex text-right justify-between"--}}
+{{--    v-if="cart.giftcard_amount && parseFloat(cart.giftcard_amount) > 0"--}}
+{{-->--}}
+{{--    <p class="text-base">--}}
+{{--        @lang('gift::app.giftcard.remaining_giftcard_amount')--}}
+{{--    </p>--}}
+
+{{--    <p class="text-base font-medium">--}}
+{{--        $@{{ cart.remaining_giftcard_amount }}--}}
+{{--    </p>--}}
+{{--</div>--}}
+{!! view_render_event('bagisto.shop.checkout.onepage.summary.giftcard_amount.after') !!}
+

+ 11 - 0
packages/Longyi/Gift/src/Resources/views/shop/index.blade.php

@@ -0,0 +1,11 @@
+<x-shop::layouts>
+
+    <!-- Title of the page -->
+    <x-slot:title>
+        Package Gift
+    </x-slot>
+
+    <div class="main">
+        Package Gift
+    </div>
+</x-shop::layouts>

+ 18 - 0
packages/Longyi/Gift/src/Routes/admin-routes.php

@@ -0,0 +1,18 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use Longyi\Gift\Http\Controllers\Admin\GiftController;
+
+Route::group(['middleware' => ['web', 'admin'], 'prefix' => 'admin/gift'], function () {
+    Route::controller(GiftController::class)->group(function () {
+        Route::get('', 'index')->name('admin.gift.index');
+        // Create routes
+        Route::get('/create', 'create')->name('admin.gift.create');
+        Route::post('/store', 'store')->name('admin.gift.store');
+        // Delete routes
+        Route::delete('/{id}', 'destroy')->name('admin.gift.destroy');
+        Route::post('/mass-delete', 'massDelete')->name('admin.gift.mass_delete');
+        //validate
+        Route::post('/validate-email', 'validateEmail')->name('admin.gift.validate_email');
+    });
+});

+ 13 - 0
packages/Longyi/Gift/src/Routes/shop-routes.php

@@ -0,0 +1,13 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use Longyi\Gift\Http\Controllers\Shop\GiftController;
+
+Route::group(['middleware' => ['web', 'theme', 'locale', 'currency'], 'prefix' => 'gift'], function () {
+    Route::get('', [GiftController::class, 'index'])->name('shop.gift.index');
+});
+Route::group(['middleware' => ['web', 'theme', 'locale', 'currency', 'customer'], 'prefix' => 'gift'], function () {
+    Route::get('/my-gift-cards', [GiftController::class, 'getCustomerGiftCards'])->name('shop.api.checkout.cart.giftcard.list');
+    Route::post('/activate', [GiftController::class, 'activateGiftCard'])->name('shop.api.checkout.cart.giftcard.activate');
+    Route::delete('/remove', [GiftController::class, 'destroyGiftCard'])->name('shop.api.checkout.cart.giftcard.remove');
+});

+ 4 - 0
packages/Longyi/Gift/tailwind.config.js

@@ -0,0 +1,4 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+    content: ["./src/Resources/**/*.blade.php", "./src/Resources/**/*.js"],
+};

+ 46 - 0
packages/Longyi/Gift/vite.config.js

@@ -0,0 +1,46 @@
+import { defineConfig, loadEnv } from "vite";
+import vue from "@vitejs/plugin-vue";
+import laravel from "laravel-vite-plugin";
+import path from "path";
+
+export default defineConfig(({ mode }) => {
+    const envDir = "../../../";
+
+    Object.assign(process.env, loadEnv(mode, envDir));
+
+    return {
+        build: {
+            emptyOutDir: true,
+        },
+
+        envDir,
+
+        server: {
+            host: process.env.VITE_HOST || "localhost",
+            port: process.env.VITE_PORT || 5173,
+        },
+
+        plugins: [
+            vue(),
+
+            laravel({
+                hotFile: "../../../public/gift-default-vite.hot",
+                publicDirectory: "../../../public",
+                buildDirectory: "themes/gift/default/build",
+                input: [
+                    "src/Resources/assets/css/app.css",
+                    "src/Resources/assets/js/app.js",
+                ],
+                refresh: true,
+            }),
+        ],
+
+        experimental: {
+            renderBuiltUrl(filename, { hostId, hostType, type }) {
+                if (hostType === "css") {
+                    return path.basename(filename);
+                }
+            },
+        },
+    };
+});

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

@@ -72,6 +72,11 @@ class OrderResource extends JsonResource
             $this->mergeWhen($this->haveStockableItems(), $shippingInformation),
             'payment'                  => (new OrderPaymentResource($this->payment))->jsonSerialize(),
             'items'                    => OrderItemResource::collection($this->items)->jsonSerialize(),
+            $this->mergeWhen($this->giftcard_number, [
+                'giftcard_number'           => $this->giftcard_number,
+                'giftcard_amount'           => $this->giftcard_amount,
+                'remaining_giftcard_amount' => $this->remaining_giftcard_amount,
+            ]),
         ];
     }
 }

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

@@ -48,6 +48,11 @@ 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'),
+            $this->mergeWhen($this->giftcard_number, [
+                'giftcard_number'           => $this->giftcard_number,
+                'giftcard_amount'           => $this->giftcard_amount,
+                'remaining_giftcard_amount' => $this->remaining_giftcard_amount,
+            ]),
         ];
     }
 }