瀏覽代碼

更新官方api至6月26日左右

chengwl 3 天之前
父節點
當前提交
d8c4a58b24
共有 100 個文件被更改,包括 3520 次插入225 次删除
  1. 9 0
      composer.json
  2. 5 2
      config/api-platform.php
  3. 2 0
      packages/Webkul/Attribute/src/Models/AttributeFamily.php
  4. 10 2
      packages/Webkul/BagistoApi/composer.json
  5. 6 3
      packages/Webkul/BagistoApi/config/api-platform-vendor.php
  6. 6 1
      packages/Webkul/BagistoApi/src/Console/Commands/InstallApiPlatformCommand.php
  7. 38 0
      packages/Webkul/BagistoApi/src/Console/Commands/OptimizeApiPlatformCommand.php
  8. 35 0
      packages/Webkul/BagistoApi/src/Console/Commands/PruneAuditsCommand.php
  9. 51 0
      packages/Webkul/BagistoApi/src/Console/Commands/WarmApiPlatformCacheCommand.php
  10. 0 0
      packages/Webkul/BagistoApi/src/Database/Migrations/2026_06_09_000000_create_admin_api_audits_table.php
  11. 3 3
      packages/Webkul/BagistoApi/src/Dto/ContactUsInput.php
  12. 17 0
      packages/Webkul/BagistoApi/src/Dto/CreateGdprRequestInput.php
  13. 169 0
      packages/Webkul/BagistoApi/src/Dto/CustomerOrder/CustomerOrderDetailDto.php
  14. 72 0
      packages/Webkul/BagistoApi/src/Dto/CustomerOrder/CustomerOrderListDto.php
  15. 37 0
      packages/Webkul/BagistoApi/src/Dto/CustomerOrder/OrderAddressDto.php
  16. 51 0
      packages/Webkul/BagistoApi/src/Dto/CustomerOrder/OrderItemDto.php
  17. 15 0
      packages/Webkul/BagistoApi/src/Dto/CustomerOrder/OrderPaymentDto.php
  18. 34 0
      packages/Webkul/BagistoApi/src/Dto/CustomerOrder/OrderShipmentDto.php
  19. 17 0
      packages/Webkul/BagistoApi/src/Dto/CustomerOrder/OrderShipmentItemDto.php
  20. 8 1
      packages/Webkul/BagistoApi/src/Dto/CustomerProfileInput.php
  21. 11 0
      packages/Webkul/BagistoApi/src/Dto/DeleteGdprRequestInput.php
  22. 15 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/AttributeFamilySummaryDto.php
  23. 148 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/BookingProductDto.php
  24. 23 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/BundleOptionDto.php
  25. 20 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/BundleOptionProductDto.php
  26. 17 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/CategorySummaryDto.php
  27. 17 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/ChannelSummaryDto.php
  28. 23 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/CustomizableOptionDto.php
  29. 17 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/CustomizableOptionPriceDto.php
  30. 24 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/DownloadableLinkDto.php
  31. 18 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/DownloadableSampleDto.php
  32. 19 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/GroupedProductDto.php
  33. 130 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/ProductDetailDto.php
  34. 18 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/ProductImageDto.php
  35. 18 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/ProductSummaryDto.php
  36. 18 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/ProductVideoDto.php
  37. 22 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/SuperAttributeDto.php
  38. 15 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/SuperAttributeOptionDto.php
  39. 23 0
      packages/Webkul/BagistoApi/src/Dto/ProductDetail/VariantSummaryDto.php
  40. 11 0
      packages/Webkul/BagistoApi/src/Dto/RevokeGdprRequestInput.php
  41. 1 1
      packages/Webkul/BagistoApi/src/Dto/SubscribeToNewsletterInput.php
  42. 47 2
      packages/Webkul/BagistoApi/src/Exception/AuthenticationException.php
  43. 45 12
      packages/Webkul/BagistoApi/src/Exception/ResourceNotFoundException.php
  44. 281 0
      packages/Webkul/BagistoApi/src/GraphQl/QueryScopedSchemaBuilder.php
  45. 92 0
      packages/Webkul/BagistoApi/src/GraphQl/ScopedSchemaBuilder.php
  46. 9 1
      packages/Webkul/BagistoApi/src/GraphQl/Serializer/FixedSerializerContextBuilder.php
  47. 51 0
      packages/Webkul/BagistoApi/src/Http/Middleware/EnsureJsonContentType.php
  48. 1 22
      packages/Webkul/BagistoApi/src/Http/Middleware/HandleInvalidInputException.php
  49. 39 0
      packages/Webkul/BagistoApi/src/Http/Middleware/PaginationHeaders.php
  50. 4 1
      packages/Webkul/BagistoApi/src/Models/AddProductInCart.php
  51. 5 1
      packages/Webkul/BagistoApi/src/Models/ApplyCoupon.php
  52. 41 12
      packages/Webkul/BagistoApi/src/Models/AttributeOption.php
  53. 10 1
      packages/Webkul/BagistoApi/src/Models/AttributeTranslation.php
  54. 21 2
      packages/Webkul/BagistoApi/src/Models/AttributeValue.php
  55. 118 51
      packages/Webkul/BagistoApi/src/Models/BookingProduct.php
  56. 13 2
      packages/Webkul/BagistoApi/src/Models/BookingProductAppointmentSlot.php
  57. 13 2
      packages/Webkul/BagistoApi/src/Models/BookingProductDefaultSlot.php
  58. 12 2
      packages/Webkul/BagistoApi/src/Models/BookingProductEventTicket.php
  59. 12 1
      packages/Webkul/BagistoApi/src/Models/BookingProductRentalSlot.php
  60. 12 1
      packages/Webkul/BagistoApi/src/Models/BookingProductTableSlot.php
  61. 31 3
      packages/Webkul/BagistoApi/src/Models/BookingSlot.php
  62. 25 0
      packages/Webkul/BagistoApi/src/Models/CancelOrder.php
  63. 87 15
      packages/Webkul/BagistoApi/src/Models/CartToken.php
  64. 32 0
      packages/Webkul/BagistoApi/src/Models/Category.php
  65. 21 1
      packages/Webkul/BagistoApi/src/Models/CategoryTree.php
  66. 2 1
      packages/Webkul/BagistoApi/src/Models/Channel.php
  67. 20 1
      packages/Webkul/BagistoApi/src/Models/ChannelTranslation.php
  68. 72 2
      packages/Webkul/BagistoApi/src/Models/CheckoutAddress.php
  69. 39 2
      packages/Webkul/BagistoApi/src/Models/CheckoutOrder.php
  70. 39 2
      packages/Webkul/BagistoApi/src/Models/CheckoutPaymentMethod.php
  71. 47 3
      packages/Webkul/BagistoApi/src/Models/CheckoutShippingMethod.php
  72. 22 0
      packages/Webkul/BagistoApi/src/Models/ContactUs.php
  73. 1 5
      packages/Webkul/BagistoApi/src/Models/Country.php
  74. 55 2
      packages/Webkul/BagistoApi/src/Models/Currency.php
  75. 7 21
      packages/Webkul/BagistoApi/src/Models/Customer.php
  76. 265 3
      packages/Webkul/BagistoApi/src/Models/CustomerAddress.php
  77. 1 6
      packages/Webkul/BagistoApi/src/Models/CustomerAddressAddUpdate.php
  78. 1 4
      packages/Webkul/BagistoApi/src/Models/CustomerAddressDelete.php
  79. 1 6
      packages/Webkul/BagistoApi/src/Models/CustomerAddressGet.php
  80. 5 4
      packages/Webkul/BagistoApi/src/Models/CustomerAddressGetAll.php
  81. 22 0
      packages/Webkul/BagistoApi/src/Models/CustomerInvoice.php
  82. 44 0
      packages/Webkul/BagistoApi/src/Models/CustomerLogin.php
  83. 49 2
      packages/Webkul/BagistoApi/src/Models/CustomerLogout.php
  84. 7 0
      packages/Webkul/BagistoApi/src/Models/CustomerOrder.php
  85. 22 0
      packages/Webkul/BagistoApi/src/Models/CustomerOrderShipment.php
  86. 13 2
      packages/Webkul/BagistoApi/src/Models/CustomerOrderShipmentItem.php
  87. 44 3
      packages/Webkul/BagistoApi/src/Models/CustomerProfile.php
  88. 37 1
      packages/Webkul/BagistoApi/src/Models/CustomerProfileDelete.php
  89. 66 1
      packages/Webkul/BagistoApi/src/Models/CustomerProfileUpdate.php
  90. 9 0
      packages/Webkul/BagistoApi/src/Models/CustomerReview.php
  91. 8 0
      packages/Webkul/BagistoApi/src/Models/DeleteAllCompareItems.php
  92. 2 0
      packages/Webkul/BagistoApi/src/Models/DeleteAllWishlists.php
  93. 0 3
      packages/Webkul/BagistoApi/src/Models/Filter/Attribute.php
  94. 32 1
      packages/Webkul/BagistoApi/src/Models/ForgotPassword.php
  95. 264 0
      packages/Webkul/BagistoApi/src/Models/GdprRequest.php
  96. 15 1
      packages/Webkul/BagistoApi/src/Models/GetCheckoutAddress.php
  97. 30 1
      packages/Webkul/BagistoApi/src/Models/MergeCart.php
  98. 1 3
      packages/Webkul/BagistoApi/src/Models/MoveToWishlist.php
  99. 63 0
      packages/Webkul/BagistoApi/src/Models/MoveWishlistToCart.php
  100. 0 0
      packages/Webkul/BagistoApi/src/Models/Newsletter.php

+ 9 - 0
composer.json

@@ -17,8 +17,17 @@
         "ext-pdo": "*",
         "ext-pdo_mysql": "*",
         "ext-tokenizer": "*",
+        "api-platform/documentation": "v4.3.1",
         "api-platform/graphql": "v4.2.3",
+        "api-platform/hydra": "v4.3.1",
+        "api-platform/json-api": "v4.3.1",
+        "api-platform/json-schema": "v4.3.1",
+        "api-platform/jsonld": "v4.3.1",
         "api-platform/laravel": "v4.1.25",
+        "api-platform/metadata": "v4.3.1",
+        "api-platform/openapi": "v4.3.1",
+        "api-platform/serializer": "v4.3.1",
+        "api-platform/state": "v4.3.1",
         "astrotomic/laravel-translatable": "^11.0.0",
         "bagisto/bagisto-package-generator": "^2.1",
         "bagisto/image-cache": "dev-master",

+ 5 - 2
config/api-platform.php

@@ -44,12 +44,15 @@ return [
             'Webkul\BagistoApi\Http\Middleware\VerifyStorefrontKey',
             'Webkul\BagistoApi\Http\Middleware\BagistoApiDocumentationMiddleware',
             'Webkul\BagistoApi\Http\Middleware\ForceApiJson',
+            'Webkul\BagistoApi\Http\Middleware\PaginationHeaders',
             'Spatie\ResponseCache\Middlewares\CacheResponse',
         ],
     ],
 
     'resources' => [
         base_path('packages/Webkul/BagistoApi/src/Models/'),
+        base_path('packages/Webkul/BagistoApi/src/Dto/ProductDetail/'),
+        base_path('packages/Webkul/BagistoApi/src/Dto/CustomerOrder/'),
     ],
 
     'formats' => [
@@ -73,7 +76,7 @@ return [
         'pagination_enabled'                => true,
         'pagination_partial'                => false,
         'pagination_client_enabled'         => false,
-        'pagination_client_items_per_page'  => false,
+        'pagination_client_items_per_page'  => true,
         'pagination_client_partial'         => false,
         'pagination_items_per_page'         => 10,
         'pagination_maximum_items_per_page' => 50,
@@ -84,7 +87,7 @@ return [
     'pagination' => [
         'page_parameter_name'           => 'page',
         'enabled_parameter_name'        => 'pagination',
-        'items_per_page_parameter_name' => 'itemsPerPage',
+        'items_per_page_parameter_name' => 'per_page',
         'partial_parameter_name'        => 'partial',
     ],
 

+ 2 - 0
packages/Webkul/Attribute/src/Models/AttributeFamily.php

@@ -9,7 +9,9 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
 use Webkul\Attribute\Contracts\AttributeFamily as AttributeFamilyContract;
 use Webkul\Attribute\Database\Factories\AttributeFamilyFactory;
 use Webkul\Product\Models\ProductProxy;
+use ApiPlatform\Metadata\ApiResource;
 
+#[ApiResource(operations: [], graphQlOperations: [])]
 class AttributeFamily extends Model implements AttributeFamilyContract
 {
     use HasFactory;

+ 10 - 2
packages/Webkul/BagistoApi/composer.json

@@ -11,7 +11,16 @@
   ],
   "require": {
     "api-platform/laravel": "v4.1.25",
-    "api-platform/graphql": "v4.2.3"
+    "api-platform/graphql": "v4.2.3",
+    "api-platform/metadata": "v4.3.1",
+    "api-platform/serializer": "v4.3.1",
+    "api-platform/state": "v4.3.1",
+    "api-platform/jsonld": "v4.3.1",
+    "api-platform/hydra": "v4.3.1",
+    "api-platform/openapi": "v4.3.1",
+    "api-platform/json-schema": "v4.3.1",
+    "api-platform/json-api": "v4.3.1",
+    "api-platform/documentation": "v4.3.1"
   },
   "autoload": {
     "psr-4": {
@@ -31,4 +40,3 @@
     ]
   }
 }
-

+ 6 - 3
packages/Webkul/BagistoApi/config/api-platform-vendor.php

@@ -43,12 +43,15 @@ return [
             'Webkul\BagistoApi\Http\Middleware\VerifyStorefrontKey',
             'Webkul\BagistoApi\Http\Middleware\BagistoApiDocumentationMiddleware',
             'Webkul\BagistoApi\Http\Middleware\ForceApiJson',
+            'Webkul\BagistoApi\Http\Middleware\PaginationHeaders',
             'Spatie\ResponseCache\Middlewares\CacheResponse',
         ],
     ],
 
     'resources' => [
-        base_path('vendor/bagisto/bagisto-api/src/Models/')
+        base_path('vendor/bagisto/bagisto-api/src/Models/'),
+        base_path('vendor/bagisto/bagisto-api/src/Dto/ProductDetail/'),
+        base_path('vendor/bagisto/bagisto-api/src/Dto/CustomerOrder/'),
     ],
 
     'formats' => [
@@ -72,7 +75,7 @@ return [
         'pagination_enabled'                => true,
         'pagination_partial'                => false,
         'pagination_client_enabled'         => false,
-        'pagination_client_items_per_page'  => false,
+        'pagination_client_items_per_page'  => true,
         'pagination_client_partial'         => false,
         'pagination_items_per_page'         => 10,
         'pagination_maximum_items_per_page' => 50,
@@ -83,7 +86,7 @@ return [
     'pagination' => [
         'page_parameter_name'           => 'page',
         'enabled_parameter_name'        => 'pagination',
-        'items_per_page_parameter_name' => 'itemsPerPage',
+        'items_per_page_parameter_name' => 'per_page',
         'partial_parameter_name'        => 'partial',
     ],
 

+ 6 - 1
packages/Webkul/BagistoApi/src/Console/Commands/InstallApiPlatformCommand.php

@@ -402,10 +402,15 @@ class InstallApiPlatformCommand extends Command
                 'optimize',
             ]);
 
+            $optimizeProcess->setTimeout(300);
+            
             $optimizeProcess->run();
 
             if (! $optimizeProcess->isSuccessful()) {
-                throw new \Exception(__('bagistoapi::app.graphql.install.cache-optimize-error').' '.$optimizeProcess->getErrorOutput());
+                  // Cache optimization is a performance step, not required for a working
+                // install. Warn and continue instead of failing the whole command.
+                // (The real error is usually on stdout; getErrorOutput() is often empty.)
+                $this->warn(__('bagistoapi::app.graphql.install.cache-optimize-error').' '.trim($optimizeProcess->getErrorOutput().' '.$optimizeProcess->getOutput()));   
             }
 
             $this->line(__('bagistoapi::app.graphql.install.caches-optimized'));

+ 38 - 0
packages/Webkul/BagistoApi/src/Console/Commands/OptimizeApiPlatformCommand.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace Webkul\BagistoApi\Console\Commands;
+
+use Illuminate\Console\Command;
+
+class OptimizeApiPlatformCommand extends Command
+{
+    protected $signature = 'bagisto-api-platform:optimize';
+
+    protected $description = 'Full deploy optimization for the API: clears stale caches, then rebuilds the config + route caches and pre-warms the API Platform metadata cache. Run this after every deploy, package update, or endpoint change so no request pays the per-request route rebuild or the cold-start metadata build.';
+
+    public function handle(): int
+    {
+        $this->components->info('Optimizing the Bagisto API (full optimize + metadata caches)...');
+
+        $this->call('optimize:clear');
+        $this->call('bagisto-api-platform:clear-cache');
+        $this->call('optimize');
+
+        if (! app()->routesAreCached()) {
+            $this->components->error('The route cache was not built. Without it, API Platform re-registers every route on every request (~0.8s slower per call). Check for a route that cannot be serialized (a closure or a duplicate route name), then re-run this command before deploying.');
+
+            return self::FAILURE;
+        }
+
+        $this->call('bagisto-api-platform:warm-cache');
+
+        $this->components->info('Bagisto API optimized. Config, events, routes and views are cached and the metadata cache is warm.');
+
+        if (config('app.debug')) {
+            $this->newLine();
+            $this->components->warn('APP_DEBUG is currently true. For faster responses set APP_DEBUG=false in your .env (debug mode adds error-collector overhead to every request), then re-run this command.');
+        }
+
+        return self::SUCCESS;
+    }
+}

+ 35 - 0
packages/Webkul/BagistoApi/src/Console/Commands/PruneAuditsCommand.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Webkul\BagistoApi\Console\Commands;
+
+use Illuminate\Console\Command;
+use Webkul\BagistoApi\Admin\Models\AdminApiAudit;
+
+/**
+ * Deletes admin-API audit history older than the configured retention period.
+ * Schedule it (or run manually) to keep the audit table from growing forever.
+ */
+class PruneAuditsCommand extends Command
+{
+    protected $signature = 'bagisto-api:prune-audits {--days= : Override bagistoapi.audit.retention_days}';
+
+    protected $description = 'Delete admin-API audit history older than the retention period.';
+
+    public function handle(): int
+    {
+        $days = $this->option('days') ?? config('bagistoapi.audit.retention_days');
+
+        if ($days === null || ! is_numeric($days)) {
+            $this->info('No retention period configured (bagistoapi.audit.retention_days is null). Nothing pruned.');
+
+            return self::SUCCESS;
+        }
+
+        $cutoff = now()->subDays((int) $days);
+        $deleted = AdminApiAudit::where('created_at', '<', $cutoff)->delete();
+
+        $this->info("Pruned {$deleted} audit row(s) older than {$days} day(s).");
+
+        return self::SUCCESS;
+    }
+}

+ 51 - 0
packages/Webkul/BagistoApi/src/Console/Commands/WarmApiPlatformCacheCommand.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace Webkul\BagistoApi\Console\Commands;
+
+use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
+use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
+use Illuminate\Console\Command;
+
+class WarmApiPlatformCacheCommand extends Command
+{
+    protected $signature = 'bagisto-api-platform:warm-cache';
+
+    protected $description = 'Pre-build the API Platform resource-metadata cache so the first request after a deploy/cache-clear does not pay the full metadata rebuild cost.';
+
+    public function handle(): int
+    {
+        $nameFactory = app(ResourceNameCollectionFactoryInterface::class);
+        $metadataFactory = app(ResourceMetadataCollectionFactoryInterface::class);
+
+        $count = 0;
+        $failed = 0;
+
+        foreach ($nameFactory->create() as $resourceClass) {
+            try {
+                $metadataFactory->create($resourceClass);
+                $count++;
+            } catch (\Throwable $e) {
+                $failed++;
+                $this->warn(sprintf('Skipped "%s": %s', $resourceClass, $e->getMessage()));
+            }
+        }
+
+        $this->info(sprintf('Warmed metadata cache for %d resource(s)%s.', $count, $failed ? sprintf(' (%d skipped)', $failed) : ''));
+
+        try {
+            $scopedBuilder = new \Webkul\BagistoApi\GraphQl\QueryScopedSchemaBuilder(
+                $nameFactory,
+                $metadataFactory,
+                app(\ApiPlatform\GraphQl\Type\TypesFactoryInterface::class),
+                app(\ApiPlatform\GraphQl\Type\TypesContainerInterface::class),
+                app(\ApiPlatform\GraphQl\Type\FieldsBuilderEnumInterface::class),
+            );
+
+            $this->info(sprintf('Pre-built the GraphQL query-scope map (%d fields).', $scopedBuilder->warmFieldMap()));
+        } catch (\Throwable $e) {
+            $this->warn(sprintf('Could not pre-build the GraphQL query-scope map: %s', $e->getMessage()));
+        }
+
+        return self::SUCCESS;
+    }
+}

+ 0 - 0
packages/Webkul/BagistoApi/src/Database/Migrations/2026_06_09_000000_create_admin_api_audits_table.php


+ 3 - 3
packages/Webkul/BagistoApi/src/Dto/ContactUsInput.php

@@ -15,14 +15,14 @@ class ContactUsInput
     public ?string $id = null;
 
     #[Groups(['mutation'])]
-    public string $name;
+    public ?string $name = null;
 
     #[Groups(['mutation'])]
-    public string $email;
+    public ?string $email = null;
 
     #[Groups(['mutation'])]
     public ?string $contact = null;
 
     #[Groups(['mutation'])]
-    public string $message;
+    public ?string $message = null;
 }

+ 17 - 0
packages/Webkul/BagistoApi/src/Dto/CreateGdprRequestInput.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+use Symfony\Component\Serializer\Annotation\Groups;
+
+class CreateGdprRequestInput
+{
+    #[ApiProperty(description: 'The request type. Either "delete" or "update".')]
+    #[Groups(['mutation'])]
+    public ?string $type = null;
+
+    #[ApiProperty(description: 'The reason or message for the request.')]
+    #[Groups(['mutation'])]
+    public ?string $message = null;
+}

+ 169 - 0
packages/Webkul/BagistoApi/src/Dto/CustomerOrder/CustomerOrderDetailDto.php

@@ -0,0 +1,169 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\CustomerOrder;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+
+/**
+ * Detail-view shape for /customer-orders/{id}.
+ *
+ * Mirrors all order-level fields the legacy response exposed, but replaces the
+ * dangling IRI relations (items, addresses, payment, shipments) with embedded
+ * objects so a single request returns everything the detail UI needs.
+ */
+#[ApiResource(
+    operations: [],
+    graphQlOperations: [],
+    normalizationContext: ['skip_null_values' => false],
+)]
+class CustomerOrderDetailDto
+{
+    #[ApiProperty(identifier: true)]
+    public ?int $id = null;
+
+    public ?string $increment_id = null;
+
+    public ?string $status = null;
+
+    public ?string $channel_name = null;
+
+    public ?bool $is_guest = null;
+
+    public ?string $customer_email = null;
+
+    public ?string $customer_first_name = null;
+
+    public ?string $customer_last_name = null;
+
+    public ?string $customer_full_name = null;
+
+    public ?string $shipping_method = null;
+
+    public ?string $shipping_title = null;
+
+    public ?string $shipping_description = null;
+
+    public ?string $coupon_code = null;
+
+    public ?bool $is_gift = null;
+
+    public ?int $total_item_count = null;
+
+    public ?int $total_qty_ordered = null;
+
+    public ?string $base_currency_code = null;
+
+    public ?string $channel_currency_code = null;
+
+    public ?string $order_currency_code = null;
+
+    public ?float $grand_total = null;
+
+    public ?float $base_grand_total = null;
+
+    public ?float $grand_total_invoiced = null;
+
+    public ?float $base_grand_total_invoiced = null;
+
+    public ?float $grand_total_refunded = null;
+
+    public ?float $base_grand_total_refunded = null;
+
+    public ?float $sub_total = null;
+
+    public ?float $base_sub_total = null;
+
+    public ?float $sub_total_invoiced = null;
+
+    public ?float $base_sub_total_invoiced = null;
+
+    public ?float $sub_total_refunded = null;
+
+    public ?float $base_sub_total_refunded = null;
+
+    public ?float $discount_percent = null;
+
+    public ?float $discount_amount = null;
+
+    public ?float $base_discount_amount = null;
+
+    public ?float $discount_invoiced = null;
+
+    public ?float $base_discount_invoiced = null;
+
+    public ?float $discount_refunded = null;
+
+    public ?float $base_discount_refunded = null;
+
+    public ?float $tax_amount = null;
+
+    public ?float $base_tax_amount = null;
+
+    public ?float $tax_amount_invoiced = null;
+
+    public ?float $base_tax_amount_invoiced = null;
+
+    public ?float $tax_amount_refunded = null;
+
+    public ?float $base_tax_amount_refunded = null;
+
+    public ?float $shipping_amount = null;
+
+    public ?float $base_shipping_amount = null;
+
+    public ?float $shipping_invoiced = null;
+
+    public ?float $base_shipping_invoiced = null;
+
+    public ?float $shipping_refunded = null;
+
+    public ?float $base_shipping_refunded = null;
+
+    public ?float $shipping_discount_amount = null;
+
+    public ?float $base_shipping_discount_amount = null;
+
+    public ?float $shipping_tax_amount = null;
+
+    public ?float $base_shipping_tax_amount = null;
+
+    public ?float $shipping_tax_refunded = null;
+
+    public ?float $base_shipping_tax_refunded = null;
+
+    public ?float $sub_total_incl_tax = null;
+
+    public ?float $base_sub_total_incl_tax = null;
+
+    public ?float $shipping_amount_incl_tax = null;
+
+    public ?float $base_shipping_amount_incl_tax = null;
+
+    public ?int $customer_id = null;
+
+    public ?int $channel_id = null;
+
+    public ?int $cart_id = null;
+
+    public ?string $applied_cart_rule_ids = null;
+
+    public ?string $created_at = null;
+
+    public ?string $updated_at = null;
+
+    /** @var OrderItemDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $items = [];
+
+    /** @var OrderAddressDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $addresses = [];
+
+    #[ApiProperty(readableLink: true)]
+    public ?OrderPaymentDto $payment = null;
+
+    /** @var OrderShipmentDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $shipments = [];
+}

+ 72 - 0
packages/Webkul/BagistoApi/src/Dto/CustomerOrder/CustomerOrderListDto.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\CustomerOrder;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+
+/**
+ * List-view shape for /customer-orders.
+ *
+ * Mirrors the GraphQL `customerOrders` query field set so REST and GraphQL
+ * lists return the same payload. Heavy or admin-only fields (-invoiced,
+ * -refunded, polymorphic IDs, cart references, dangling relation IRIs) live
+ * only on the detail endpoint.
+ *
+ * Relations (items, addresses, payment, shipments) intentionally omitted —
+ * the detail endpoint serves those.
+ */
+#[ApiResource(
+    operations: [],
+    graphQlOperations: [],
+    normalizationContext: ['skip_null_values' => false],
+)]
+class CustomerOrderListDto
+{
+    #[ApiProperty(identifier: true)]
+    public ?int $id = null;
+
+    public ?string $increment_id = null;
+
+    public ?string $status = null;
+
+    public ?string $channel_name = null;
+
+    public ?string $customer_email = null;
+
+    public ?string $customer_first_name = null;
+
+    public ?string $customer_last_name = null;
+
+    public ?string $shipping_method = null;
+
+    public ?string $shipping_title = null;
+
+    public ?string $coupon_code = null;
+
+    public ?int $total_item_count = null;
+
+    public ?int $total_qty_ordered = null;
+
+    public ?float $grand_total = null;
+
+    public ?float $base_grand_total = null;
+
+    public ?float $sub_total = null;
+
+    public ?float $base_sub_total = null;
+
+    public ?float $tax_amount = null;
+
+    public ?float $shipping_amount = null;
+
+    public ?float $discount_amount = null;
+
+    public ?string $base_currency_code = null;
+
+    public ?string $order_currency_code = null;
+
+    public ?string $created_at = null;
+
+    public ?string $updated_at = null;
+}

+ 37 - 0
packages/Webkul/BagistoApi/src/Dto/CustomerOrder/OrderAddressDto.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\CustomerOrder;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class OrderAddressDto
+{
+    public ?int $id = null;
+
+    public ?string $address_type = null;
+
+    public ?string $first_name = null;
+
+    public ?string $last_name = null;
+
+    public ?string $gender = null;
+
+    public ?string $company_name = null;
+
+    public ?string $address = null;
+
+    public ?string $city = null;
+
+    public ?string $state = null;
+
+    public ?string $country = null;
+
+    public ?string $postcode = null;
+
+    public ?string $email = null;
+
+    public ?string $phone = null;
+
+    public ?string $vat_id = null;
+}

+ 51 - 0
packages/Webkul/BagistoApi/src/Dto/CustomerOrder/OrderItemDto.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\CustomerOrder;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class OrderItemDto
+{
+    public ?int $id = null;
+
+    public ?string $sku = null;
+
+    public ?string $type = null;
+
+    public ?string $name = null;
+
+    public ?int $product_id = null;
+
+    public ?string $product_type = null;
+
+    public ?int $qty_ordered = null;
+
+    public ?int $qty_shipped = null;
+
+    public ?int $qty_invoiced = null;
+
+    public ?int $qty_canceled = null;
+
+    public ?int $qty_refunded = null;
+
+    public ?float $price = null;
+
+    public ?float $base_price = null;
+
+    public ?float $total = null;
+
+    public ?float $base_total = null;
+
+    public ?float $discount_percent = null;
+
+    public ?float $discount_amount = null;
+
+    public ?float $tax_percent = null;
+
+    public ?float $tax_amount = null;
+
+    public ?float $price_incl_tax = null;
+
+    public ?float $total_incl_tax = null;
+}

+ 15 - 0
packages/Webkul/BagistoApi/src/Dto/CustomerOrder/OrderPaymentDto.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\CustomerOrder;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class OrderPaymentDto
+{
+    public ?int $id = null;
+
+    public ?string $method = null;
+
+    public ?string $method_title = null;
+}

+ 34 - 0
packages/Webkul/BagistoApi/src/Dto/CustomerOrder/OrderShipmentDto.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\CustomerOrder;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class OrderShipmentDto
+{
+    public ?int $id = null;
+
+    public ?string $shipping_number = null;
+
+    public ?string $carrier_title = null;
+
+    public ?string $carrier_code = null;
+
+    public ?string $track_number = null;
+
+    public ?int $total_qty = null;
+
+    public ?float $total_weight = null;
+
+    public ?bool $email_sent = null;
+
+    public ?string $created_at = null;
+
+    public ?string $updated_at = null;
+
+    /** @var OrderShipmentItemDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $items = [];
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Dto/CustomerOrder/OrderShipmentItemDto.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\CustomerOrder;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class OrderShipmentItemDto
+{
+    public ?int $id = null;
+
+    public ?int $order_item_id = null;
+
+    public ?int $qty = null;
+
+    public ?float $weight = null;
+}

+ 8 - 1
packages/Webkul/BagistoApi/src/Dto/CustomerProfileInput.php

@@ -57,8 +57,15 @@ class CustomerProfileInput
     #[Groups(['mutation'])]
     public ?string $dateOfBirth = null;
 
+
+
+    /**
+     * Current password — required when changing password
+     */
+    #[Groups(['mutation'])]
+    public ?string $currentPassword = null;
     /**
-     * Current password (for password change verification)
+     * New password
      */
     #[Groups(['mutation'])]
     public ?string $password = null;

+ 11 - 0
packages/Webkul/BagistoApi/src/Dto/DeleteGdprRequestInput.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+
+class DeleteGdprRequestInput
+{
+    #[ApiProperty(description: 'The ID of the GDPR data request to delete')]
+    public ?string $id = null;
+}

+ 15 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/AttributeFamilySummaryDto.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class AttributeFamilySummaryDto
+{
+    public function __construct(
+        public ?int $id = null,
+        public ?string $code = null,
+        public ?string $name = null,
+    ) {}
+}

+ 148 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/BookingProductDto.php

@@ -0,0 +1,148 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [], normalizationContext: ['skip_null_values' => false])]
+class BookingProductDto
+{
+    #[ApiProperty(identifier: true)]
+    public ?int $id = null;
+
+    public ?string $type = null;
+
+    public ?int $qty = null;
+
+    public ?string $location = null;
+
+    public ?bool $available_every_week = null;
+
+    public ?string $available_from = null;
+
+    public ?string $available_to = null;
+
+    /**
+     * Slot configuration. Shape varies by booking type:
+     *  - appointment: { duration, breakTime, sameSlotAllDays, slots: [{from,to,...}] }
+     *  - default:     { bookingType, sameSlotAllDays, slots: [...] }
+     *  - rental:      { rentingType, dailyPrice, hourlyPrice, sameSlotAllDays, slots: [...] }
+     *  - table:       { guestCapacity, prepTime, sameSlotAllDays, slots: [...] }
+     *  - event:       { tickets: [{id,price,qtyAvailable,...}] }
+     */
+    #[ApiProperty(openapiContext: ['type' => 'object'])]
+    public mixed $slots = [];
+
+    public function __construct(
+        ?int $id = null,
+        ?string $type = null,
+        ?int $qty = null,
+        ?string $location = null,
+        ?bool $available_every_week = null,
+        ?string $available_from = null,
+        ?string $available_to = null,
+        mixed $slots = [],
+    ) {
+        $this->id = $id;
+        $this->type = $type;
+        $this->qty = $qty;
+        $this->location = $location;
+        $this->available_every_week = $available_every_week;
+        $this->available_from = $available_from;
+        $this->available_to = $available_to;
+        $this->slots = $slots;
+    }
+
+    /**
+     * Build a DTO from a Bagisto BookingProduct model with its slot relations
+     * already loaded. Shared between ProductDetailProvider (PDP embed) and
+     * BookingProductDetailProvider (standalone /booking-products/{id} endpoint).
+     */
+    public static function fromModel($bp): self
+    {
+        $decode = function ($v) {
+            if (is_string($v)) {
+                $d = json_decode($v, true);
+
+                return is_array($d) ? $d : [];
+            }
+
+            return is_array($v) ? $v : [];
+        };
+
+        $slots = [];
+        switch ($bp->type) {
+            case 'appointment':
+                if ($bp->appointment_slot) {
+                    $as = $bp->appointment_slot;
+                    $slots = [
+                        'duration'           => $as->duration,
+                        'breakTime'          => $as->break_time,
+                        'sameSlotAllDays'    => (bool) $as->same_slot_all_days,
+                        'slots'              => $decode($as->slots ?? null),
+                    ];
+                }
+                break;
+            case 'default':
+                if ($bp->default_slot) {
+                    $ds = $bp->default_slot;
+                    $slots = [
+                        'bookingType'     => $ds->booking_type ?? null,
+                        'sameSlotAllDays' => isset($ds->same_slot_all_days) ? (bool) $ds->same_slot_all_days : null,
+                        'slots'           => $decode($ds->slots ?? null),
+                    ];
+                }
+                break;
+            case 'rental':
+                if ($bp->rental_slot) {
+                    $rs = $bp->rental_slot;
+                    $slots = [
+                        'rentingType'     => $rs->renting_type ?? null,
+                        'dailyPrice'      => isset($rs->daily_price) ? (float) $rs->daily_price : null,
+                        'hourlyPrice'     => isset($rs->hourly_price) ? (float) $rs->hourly_price : null,
+                        'sameSlotAllDays' => isset($rs->same_slot_all_days) ? (bool) $rs->same_slot_all_days : null,
+                        'slots'           => $decode($rs->slots ?? null),
+                    ];
+                }
+                break;
+            case 'table':
+                if ($bp->table_slot) {
+                    $ts = $bp->table_slot;
+                    $slots = [
+                        'guestCapacity'          => $ts->guest_capacity ?? null,
+                        'prepTime'               => $ts->prep_time ?? null,
+                        'allowGuestsOverbooking' => isset($ts->allow_guests_overbooking) ? (bool) $ts->allow_guests_overbooking : null,
+                        'sameSlotAllDays'        => isset($ts->same_slot_all_days) ? (bool) $ts->same_slot_all_days : null,
+                        'slots'                  => $decode($ts->slots ?? null),
+                    ];
+                }
+                break;
+            case 'event':
+                $slots = [
+                    'tickets' => $bp->event_tickets
+                        ? $bp->event_tickets->map(fn ($t) => [
+                            'id'           => (int) $t->id,
+                            'price'        => $t->price !== null ? (float) $t->price : null,
+                            'qty'          => $t->qty ?? null,
+                            'specialPrice' => $t->special_price !== null ? (float) $t->special_price : null,
+                            'name'         => $t->name ?? null,
+                            'description'  => $t->description ?? null,
+                        ])->values()->all()
+                        : [],
+                ];
+                break;
+        }
+
+        return new self(
+            id: (int) $bp->id,
+            type: $bp->type,
+            qty: $bp->qty !== null ? (int) $bp->qty : null,
+            location: $bp->location ?? null,
+            available_every_week: isset($bp->available_every_week) ? (bool) $bp->available_every_week : null,
+            available_from: $bp->available_from ? (is_string($bp->available_from) ? $bp->available_from : $bp->available_from->toAtomString()) : null,
+            available_to: $bp->available_to ? (is_string($bp->available_to) ? $bp->available_to : $bp->available_to->toAtomString()) : null,
+            slots: $slots,
+        );
+    }
+}

+ 23 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/BundleOptionDto.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class BundleOptionDto
+{
+    /**
+     * @param  BundleOptionProductDto[]  $products
+     */
+    public function __construct(
+        public ?int $id = null,
+        public ?string $type = null,
+        public ?bool $is_required = null,
+        public ?int $sort_order = null,
+        public ?string $label = null,
+        #[ApiProperty(readableLink: true)]
+        public array $products = [],
+    ) {}
+}

+ 20 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/BundleOptionProductDto.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class BundleOptionProductDto
+{
+    public function __construct(
+        public ?int $id = null,
+        public ?int $product_id = null,
+        public ?int $qty = null,
+        public ?bool $is_default = null,
+        public ?int $sort_order = null,
+        #[ApiProperty(readableLink: true)]
+        public ?ProductSummaryDto $product = null,
+    ) {}
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/CategorySummaryDto.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class CategorySummaryDto
+{
+    public function __construct(
+        public ?int $id = null,
+        public ?string $code = null,
+        public ?string $name = null,
+        public ?string $slug = null,
+        public ?string $url_path = null,
+    ) {}
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/ChannelSummaryDto.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class ChannelSummaryDto
+{
+    public function __construct(
+        public ?int $id = null,
+        public ?string $code = null,
+        public ?string $hostname = null,
+        public ?string $currency_code = null,
+        public ?string $locale_code = null,
+    ) {}
+}

+ 23 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/CustomizableOptionDto.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class CustomizableOptionDto
+{
+    /**
+     * @param  CustomizableOptionPriceDto[]  $prices
+     */
+    public function __construct(
+        public ?int $id = null,
+        public ?string $type = null,
+        public ?bool $is_required = null,
+        public ?int $sort_order = null,
+        public ?string $label = null,
+        #[ApiProperty(readableLink: true)]
+        public array $prices = [],
+    ) {}
+}

+ 17 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/CustomizableOptionPriceDto.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class CustomizableOptionPriceDto
+{
+    public function __construct(
+        public ?int $id = null,
+        public ?string $label = null,
+        public ?float $price = null,
+        public ?string $price_type = null,
+        public ?int $sort_order = null,
+    ) {}
+}

+ 24 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/DownloadableLinkDto.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class DownloadableLinkDto
+{
+    public function __construct(
+        public ?int $id = null,
+        public ?int $sort_order = null,
+        public ?string $type = null,
+        public ?string $file_type = null,
+        public ?string $url_or_path = null,
+        public ?string $sample_type = null,
+        public ?string $sample_file_type = null,
+        public ?string $sample_url_or_path = null,
+        public ?int $downloads = null,
+        public ?float $price = null,
+        public ?string $formatted_price = null,
+        public ?string $title = null,
+    ) {}
+}

+ 18 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/DownloadableSampleDto.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class DownloadableSampleDto
+{
+    public function __construct(
+        public ?int $id = null,
+        public ?int $sort_order = null,
+        public ?string $type = null,
+        public ?string $file_type = null,
+        public ?string $url_or_path = null,
+        public ?string $title = null,
+    ) {}
+}

+ 19 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/GroupedProductDto.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class GroupedProductDto
+{
+    public function __construct(
+        public ?int $id = null,
+        public ?int $associated_product_id = null,
+        public ?float $qty = null,
+        public ?int $sort_order = null,
+        #[ApiProperty(readableLink: true)]
+        public ?ProductSummaryDto $product = null,
+    ) {}
+}

+ 130 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/ProductDetailDto.php

@@ -0,0 +1,130 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class ProductDetailDto
+{
+    #[ApiProperty(identifier: true)]
+    public ?int $id = null;
+
+    public ?string $sku = null;
+
+    public ?string $type = null;
+
+    public ?string $name = null;
+
+    public ?string $url_key = null;
+
+    public ?bool $status = null;
+
+    public ?string $description = null;
+
+    public ?string $short_description = null;
+
+    public ?float $price = null;
+
+    public ?float $special_price = null;
+
+    public ?bool $new = null;
+
+    public ?bool $featured = null;
+
+    public ?float $minimum_price = null;
+
+    public ?float $maximum_price = null;
+
+    public ?string $formatted_price = null;
+
+    public ?string $formatted_special_price = null;
+
+    public ?string $formatted_minimum_price = null;
+
+    public ?string $formatted_maximum_price = null;
+
+    public ?string $base_image_url = null;
+
+    public ?string $created_at = null;
+
+    public ?string $updated_at = null;
+
+    public ?bool $is_saleable = null;
+    
+     /** Whether this product is in the authenticated customer's wishlist (current channel): 1 = yes, 0 = no (0 for guests). */
+     public ?int $is_in_wishlist = null;
+
+     /** Whether this product is in the authenticated customer's compare list: 1 = yes, 0 = no (0 for guests). */
+     public ?int $is_in_compare = null;
+
+    public ?int $color = null;
+
+    public ?int $size = null;
+
+    public ?int $brand = null;
+
+    /** @var CategorySummaryDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $categories = [];
+
+    /** @var ChannelSummaryDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $channels = [];
+
+    #[ApiProperty(readableLink: true)]
+    public ?AttributeFamilySummaryDto $attribute_family = null;
+
+    /** @var ProductImageDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $images = [];
+
+    /** @var ProductVideoDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $videos = [];
+
+    /** @var SuperAttributeDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $super_attributes = [];
+
+    /** @var VariantSummaryDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $variants = [];
+
+    /** @var BookingProductDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $booking_products = [];
+
+    /** @var BundleOptionDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $bundle_options = [];
+
+    /** @var GroupedProductDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $grouped_products = [];
+
+    /** @var DownloadableLinkDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $downloadable_links = [];
+
+    /** @var DownloadableSampleDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $downloadable_samples = [];
+
+    /** @var CustomizableOptionDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $customizable_options = [];
+
+    /** @var ProductSummaryDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $related_products = [];
+
+    /** @var ProductSummaryDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $up_sells = [];
+
+    /** @var ProductSummaryDto[] */
+    #[ApiProperty(readableLink: true)]
+    public array $cross_sells = [];
+}

+ 18 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/ProductImageDto.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class ProductImageDto
+{
+    public function __construct(
+        public ?int $id = null,
+        public ?string $type = null,
+        public ?string $path = null,
+        public ?int $product_id = null,
+        public ?int $position = null,
+        public ?string $public_path = null,
+    ) {}
+}

+ 18 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/ProductSummaryDto.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class ProductSummaryDto
+{
+    public function __construct(
+        public ?int $id = null,
+        public ?string $sku = null,
+        public ?string $name = null,
+        public ?float $price = null,
+        public ?string $formatted_price = null,
+        public ?string $base_image_url = null,
+    ) {}
+}

+ 18 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/ProductVideoDto.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class ProductVideoDto
+{
+    public function __construct(
+        public ?int $id = null,
+        public ?string $type = null,
+        public ?string $path = null,
+        public ?int $product_id = null,
+        public ?int $position = null,
+        public ?string $public_path = null,
+    ) {}
+}

+ 22 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/SuperAttributeDto.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class SuperAttributeDto
+{
+    /**
+     * @param  SuperAttributeOptionDto[]  $options
+     */
+    public function __construct(
+        public ?int $id = null,
+        public ?string $code = null,
+        public ?string $label = null,
+        public ?string $type = null,
+        #[ApiProperty(readableLink: true)]
+        public array $options = [],
+    ) {}
+}

+ 15 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/SuperAttributeOptionDto.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class SuperAttributeOptionDto
+{
+    public function __construct(
+        public ?int $id = null,
+        public ?string $label = null,
+        public ?string $swatch_value = null,
+    ) {}
+}

+ 23 - 0
packages/Webkul/BagistoApi/src/Dto/ProductDetail/VariantSummaryDto.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto\ProductDetail;
+
+use ApiPlatform\Metadata\ApiResource;
+
+#[ApiResource(operations: [], graphQlOperations: [])]
+class VariantSummaryDto
+{
+    /**
+     * @param  array<string,int>  $super_attribute_options
+     */
+    public function __construct(
+        public ?int $id = null,
+        public ?string $sku = null,
+        public ?string $name = null,
+        public ?float $price = null,
+        public ?string $formatted_price = null,
+        public ?bool $is_saleable = null,
+        public ?string $base_image_url = null,
+        public array $super_attribute_options = [],
+    ) {}
+}

+ 11 - 0
packages/Webkul/BagistoApi/src/Dto/RevokeGdprRequestInput.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace Webkul\BagistoApi\Dto;
+
+use ApiPlatform\Metadata\ApiProperty;
+
+class RevokeGdprRequestInput
+{
+    #[ApiProperty(description: 'The ID of the GDPR data request to revoke')]
+    public ?string $id = null;
+}

+ 1 - 1
packages/Webkul/BagistoApi/src/Dto/SubscribeToNewsletterInput.php

@@ -15,5 +15,5 @@ class SubscribeToNewsletterInput
     public ?string $id = null;
 
     #[Groups(['mutation'])]
-    public string $customerEmail;
+    public ?string $customerEmail = null;
 }

+ 47 - 2
packages/Webkul/BagistoApi/src/Exception/AuthenticationException.php

@@ -2,8 +2,18 @@
 
 namespace Webkul\BagistoApi\Exception;
 
-class AuthenticationException extends \Exception implements \GraphQL\Error\ClientAware
+use ApiPlatform\Metadata\Exception\HttpExceptionInterface;
+use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
+
+/**
+ * Thrown when a request lacks credentials. Maps to HTTP 401 in REST.
+ */
+class AuthenticationException extends \Exception implements \GraphQL\Error\ClientAware, HttpExceptionInterface, ProblemExceptionInterface
 {
+    private int $status = 401;
+
+    private array $headers = [];
+
     public function isClientSafe(): bool
     {
         return true;
@@ -13,4 +23,39 @@ class AuthenticationException extends \Exception implements \GraphQL\Error\Clien
     {
         return 'authentication';
     }
-}
+
+    public function getStatusCode(): int
+    {
+        return $this->status;
+    }
+
+    public function getHeaders(): array
+    {
+        return $this->headers;
+    }
+
+    public function getType(): string
+    {
+        return '/errors/401';
+    }
+
+    public function getTitle(): ?string
+    {
+        return 'Unauthorized';
+    }
+
+    public function getStatus(): ?int
+    {
+        return $this->status;
+    }
+
+    public function getDetail(): ?string
+    {
+        return $this->message;
+    }
+
+    public function getInstance(): ?string
+    {
+        return null;
+    }
+}

+ 45 - 12
packages/Webkul/BagistoApi/src/Exception/ResourceNotFoundException.php

@@ -2,21 +2,19 @@
 
 namespace Webkul\BagistoApi\Exception;
 
+use ApiPlatform\Metadata\Exception\HttpExceptionInterface;
+use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
+
 /**
- * ResourceNotFoundException
- *
  * Thrown when a requested resource (cart, product, item, etc.) is not found.
- * This is for legitimate 404-type errors where the resource simply doesn't exist.
- *
- * Examples:
- * - Cart not found
- * - Product not found
- * - Cart item not found
- *
- * Status Code: 404 Not Found
+ * Maps to HTTP 404 in REST and an `errors[]` entry in GraphQL.
  */
-class ResourceNotFoundException extends \Exception implements \GraphQL\Error\ClientAware
+class ResourceNotFoundException extends \Exception implements \GraphQL\Error\ClientAware, HttpExceptionInterface, ProblemExceptionInterface
 {
+    private int $status = 404;
+
+    private array $headers = [];
+
     public function isClientSafe(): bool
     {
         return true;
@@ -26,4 +24,39 @@ class ResourceNotFoundException extends \Exception implements \GraphQL\Error\Cli
     {
         return 'resource_not_found';
     }
-}
+
+    public function getStatusCode(): int
+    {
+        return $this->status;
+    }
+
+    public function getHeaders(): array
+    {
+        return $this->headers;
+    }
+
+    public function getType(): string
+    {
+        return '/errors/404';
+    }
+
+    public function getTitle(): ?string
+    {
+        return 'Not Found';
+    }
+
+    public function getStatus(): ?int
+    {
+        return $this->status;
+    }
+
+    public function getDetail(): ?string
+    {
+        return $this->message;
+    }
+
+    public function getInstance(): ?string
+    {
+        return null;
+    }
+}

+ 281 - 0
packages/Webkul/BagistoApi/src/GraphQl/QueryScopedSchemaBuilder.php

@@ -0,0 +1,281 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Webkul\BagistoApi\GraphQl;
+
+use ApiPlatform\GraphQl\Type\FieldsBuilderEnumInterface;
+use ApiPlatform\GraphQl\Type\SchemaBuilder;
+use ApiPlatform\GraphQl\Type\SchemaBuilderInterface;
+use ApiPlatform\GraphQl\Type\TypesContainerInterface;
+use ApiPlatform\GraphQl\Type\TypesFactoryInterface;
+use ApiPlatform\Metadata\CollectionOperationInterface;
+use ApiPlatform\Metadata\GraphQl\Query;
+use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
+use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
+use ApiPlatform\Metadata\Resource\ResourceNameCollection;
+use GraphQL\Language\AST\FieldNode;
+use GraphQL\Language\AST\OperationDefinitionNode;
+use GraphQL\Language\Parser;
+use GraphQL\Type\Schema;
+use Illuminate\Support\Facades\Cache;
+
+/**
+ * Per-query scoped schema builder (storefront).
+ *
+ * Reads the incoming GraphQL query and builds a schema scoped to ONLY the
+ * resources whose root query fields the query actually references — every
+ * relation those resources expose resolves automatically. Building the full
+ * storefront schema (~400 types) on every request is the single biggest GraphQL
+ * overhead on a non-persistent runtime; scoping to the handful of types a query
+ * needs cuts the per-request schema build by an order of magnitude.
+ *
+ * Conservative by design — it falls back to the full storefront schema for:
+ *   - mutations / subscriptions
+ *   - introspection (__schema / __type)
+ *   - the Relay `node` field (its target type is only known from the runtime id)
+ *   - any unrecognised root field
+ *   - unparseable queries
+ * so no query can ever be under-scoped into an error.
+ *
+ * The root-field -> resource map is built once from the real schema field names
+ * and cached (keyed by the resource set, so it self-invalidates on deploy).
+ */
+final class QueryScopedSchemaBuilder implements SchemaBuilderInterface
+{
+    private const MAP_CACHE_KEY = 'bagistoapi.graphql.shop_root_field_map';
+
+    private ScopedSchemaBuilder $fullShop;
+
+    /** @var list<class-string>|null */
+    private ?array $frameworkResources = null;
+
+    public function __construct(
+        private readonly ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory,
+        private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
+        private readonly TypesFactoryInterface $typesFactory,
+        private readonly TypesContainerInterface $typesContainer,
+        private readonly FieldsBuilderEnumInterface $fieldsBuilder,
+    ) {
+        $this->fullShop = new ScopedSchemaBuilder(
+            $resourceNameCollectionFactory,
+            $resourceMetadataCollectionFactory,
+            $typesFactory,
+            $typesContainer,
+            $fieldsBuilder,
+            false,
+        );
+    }
+
+    public function getSchema(): Schema
+    {
+        $scope = $this->scopeForRequest();
+
+        if ($scope === null) {
+            return $this->fullShop->getSchema();
+        }
+
+        return (new SchemaBuilder(
+            $this->fixedNameFactory($scope),
+            $this->resourceMetadataCollectionFactory,
+            $this->typesFactory,
+            $this->typesContainer,
+            $this->fieldsBuilder,
+        ))->getSchema();
+    }
+
+    /**
+     * Pre-build and cache the root-field -> resource map so the first request
+     * after a deploy/cache-clear does not pay the map build. Returns the number
+     * of mapped fields. Invoked by the warm-cache command.
+     */
+    public function warmFieldMap(): int
+    {
+        return count($this->fieldMap());
+    }
+
+    /**
+     * Resolve the resource scope for the current request's query, or null to
+     * use the full storefront schema.
+     *
+     * @return list<class-string>|null
+     */
+    private function scopeForRequest(): ?array
+    {
+        $query = request()->input('query');
+
+        if (! is_string($query) || $query === '') {
+            return null;
+        }
+
+        try {
+            $document = Parser::parse($query, ['noLocation' => true]);
+        } catch (\Throwable) {
+            return null;
+        }
+
+        $rootFields = [];
+
+        foreach ($document->definitions as $definition) {
+            if (! $definition instanceof OperationDefinitionNode) {
+                return null;
+            }
+
+            if ($definition->operation !== 'query') {
+                return null;
+            }
+
+            foreach ($definition->selectionSet->selections as $selection) {
+                if (! $selection instanceof FieldNode) {
+                    return null;
+                }
+
+                $rootFields[] = $selection->name->value;
+            }
+        }
+
+        if ($rootFields === []) {
+            return null;
+        }
+
+        $map = $this->fieldMap();
+        $resources = [];
+
+        foreach (array_unique($rootFields) as $field) {
+            if (! isset($map[$field])) {
+                return null;
+            }
+
+            $resources[$map[$field]] = true;
+        }
+
+        return array_values(array_merge($this->frameworkResources(), array_keys($resources)));
+    }
+
+    /**
+     * Field-name -> resource-class map, cached (self-invalidating on resource
+     * set changes).
+     *
+     * @return array<string, class-string>
+     */
+    private function fieldMap(): array
+    {
+        return Cache::rememberForever($this->fieldMapCacheKey(), fn () => $this->buildFieldMap());
+    }
+
+    private function fieldMapCacheKey(): string
+    {
+        $names = [];
+
+        foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
+            $names[] = $resourceClass;
+        }
+
+        sort($names);
+
+        return self::MAP_CACHE_KEY.':'.md5(implode(',', $names));
+    }
+
+    /**
+     * @return array<string, class-string>
+     */
+    private function buildFieldMap(): array
+    {
+        $map = [];
+
+        foreach ($this->shopResourceNames() as $resourceClass) {
+            try {
+                $metadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
+            } catch (\Throwable) {
+                continue;
+            }
+
+            foreach ($metadataCollection as $resourceMetadata) {
+                foreach ($resourceMetadata->getGraphQlOperations() ?? [] as $operation) {
+                    if (! $operation instanceof Query) {
+                        continue;
+                    }
+
+                    $configuration = $operation->getArgs() !== null ? ['args' => $operation->getArgs()] : [];
+
+                    try {
+                        $fields = $operation instanceof CollectionOperationInterface
+                            ? $this->fieldsBuilder->getCollectionQueryFields($resourceClass, $operation, $configuration)
+                            : $this->fieldsBuilder->getItemQueryFields($resourceClass, $operation, $configuration);
+                    } catch (\Throwable) {
+                        continue;
+                    }
+
+                    foreach (array_keys($fields) as $fieldName) {
+                        if ($fieldName === 'node') {
+                            continue;
+                        }
+
+                        $map[(string) $fieldName] = $resourceClass;
+                    }
+                }
+            }
+        }
+
+        return $map;
+    }
+
+    /**
+     * @return list<class-string>
+     */
+    private function shopResourceNames(): array
+    {
+        $keep = [];
+
+        foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
+            if (str_starts_with($resourceClass, 'ApiPlatform\\')) {
+                continue;
+            }
+
+            if (str_contains($resourceClass, '\\Admin\\')) {
+                continue;
+            }
+
+            $keep[] = $resourceClass;
+        }
+
+        return $keep;
+    }
+
+    /**
+     * @return list<class-string>
+     */
+    private function frameworkResources(): array
+    {
+        if ($this->frameworkResources === null) {
+            $this->frameworkResources = [];
+
+            foreach ($this->resourceNameCollectionFactory->create() as $resourceClass) {
+                if (str_starts_with($resourceClass, 'ApiPlatform\\')) {
+                    $this->frameworkResources[] = $resourceClass;
+                }
+            }
+        }
+
+        return $this->frameworkResources;
+    }
+
+    /**
+     * @param  list<class-string>  $classes
+     */
+    private function fixedNameFactory(array $classes): ResourceNameCollectionFactoryInterface
+    {
+        return new class($classes) implements ResourceNameCollectionFactoryInterface
+        {
+            /**
+             * @param  list<class-string>  $classes
+             */
+            public function __construct(private readonly array $classes) {}
+
+            public function create(): ResourceNameCollection
+            {
+                return new ResourceNameCollection($this->classes);
+            }
+        };
+    }
+}

+ 92 - 0
packages/Webkul/BagistoApi/src/GraphQl/ScopedSchemaBuilder.php

@@ -0,0 +1,92 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Webkul\BagistoApi\GraphQl;
+
+use ApiPlatform\GraphQl\Type\FieldsBuilderEnumInterface;
+use ApiPlatform\GraphQl\Type\SchemaBuilder;
+use ApiPlatform\GraphQl\Type\SchemaBuilderInterface;
+use ApiPlatform\GraphQl\Type\TypesContainerInterface;
+use ApiPlatform\GraphQl\Type\TypesFactoryInterface;
+use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
+use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
+use ApiPlatform\Metadata\Resource\ResourceNameCollection;
+use GraphQL\Type\Schema;
+
+/**
+ * Builds a GraphQL schema scoped to ONE API surface (shop OR admin).
+ *
+ * The default API Platform SchemaBuilder iterates every registered #[ApiResource]
+ * (~261 here) and builds query/mutation field definitions for all of them on every
+ * request. Because the storefront (`/api/graphql`) and the admin (`/api/admin/graphql`)
+ * endpoints share one builder, each endpoint pays to build the OTHER surface's ~130
+ * resources too — roughly doubling the per-request schema-build cost.
+ *
+ * This builder wraps the real ResourceNameCollectionFactory with a namespace filter
+ * (admin resources live under a `\Admin\` namespace segment; framework error resources
+ * stay in both schemas) so each endpoint only builds its own surface. It reuses the
+ * shared singleton TypesContainer / TypesFactory / FieldsBuilder so lazily-resolved
+ * GraphQL types still resolve identically to the unscoped builder.
+ */
+final class ScopedSchemaBuilder implements SchemaBuilderInterface
+{
+    private SchemaBuilder $inner;
+
+    public function __construct(
+        ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory,
+        ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
+        TypesFactoryInterface $typesFactory,
+        TypesContainerInterface $typesContainer,
+        FieldsBuilderEnumInterface $fieldsBuilder,
+        bool $adminScope,
+    ) {
+        $this->inner = new SchemaBuilder(
+            $this->scopedNameFactory($resourceNameCollectionFactory, $adminScope),
+            $resourceMetadataCollectionFactory,
+            $typesFactory,
+            $typesContainer,
+            $fieldsBuilder,
+        );
+    }
+
+    public function getSchema(): Schema
+    {
+        return $this->inner->getSchema();
+    }
+
+    private function scopedNameFactory(
+        ResourceNameCollectionFactoryInterface $inner,
+        bool $adminScope,
+    ): ResourceNameCollectionFactoryInterface {
+        return new class($inner, $adminScope) implements ResourceNameCollectionFactoryInterface
+        {
+            public function __construct(
+                private readonly ResourceNameCollectionFactoryInterface $inner,
+                private readonly bool $adminScope,
+            ) {}
+
+            public function create(): ResourceNameCollection
+            {
+                $keep = [];
+
+                foreach ($this->inner->create() as $resourceClass) {
+                    // Framework resources (Error / ValidationError) belong in every schema.
+                    if (str_starts_with($resourceClass, 'ApiPlatform\\')) {
+                        $keep[] = $resourceClass;
+
+                        continue;
+                    }
+
+                    $isAdminResource = str_contains($resourceClass, '\\Admin\\');
+
+                    if ($this->adminScope === $isAdminResource) {
+                        $keep[] = $resourceClass;
+                    }
+                }
+
+                return new ResourceNameCollection($keep);
+            }
+        };
+    }
+}

+ 9 - 1
packages/Webkul/BagistoApi/src/GraphQl/Serializer/FixedSerializerContextBuilder.php

@@ -467,7 +467,15 @@ class FixedSerializerContextBuilder implements SerializerContextBuilderInterface
             return [];
         }
 
-        $isEloquent = $resourceClass && is_subclass_of($resourceClass, Model::class);
+         // Eloquent models AND admin output resources expose non-camelCase
+        // properties (admin DTOs declare snake_case props via AcceptsCamelCaseWrites),
+        // so their inner mutation field names must be denormalized
+        // (incrementId → increment_id) for the serializer to read them. Shop
+        // POPOs with camelCase props keep their field names as-is.
+        $isEloquent = $resourceClass && (
+            is_subclass_of($resourceClass, Model::class)
+            || str_starts_with($resourceClass, 'Webkul\\BagistoApi\\Admin\\')
+        );
 
         /** Denormalize top-level keys to locate the wrap field */
         $topLevel = [];

+ 51 - 0
packages/Webkul/BagistoApi/src/Http/Middleware/EnsureJsonContentType.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace Webkul\BagistoApi\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+
+/**
+ * Injects application/json Content-Type and an empty JSON body for bodyless
+ * POST endpoints where API Platform still requires a Content-Type header.
+ */
+class EnsureJsonContentType
+{
+    /**
+     * POST paths that accept no request body.
+     */
+    private const BODYLESS_POST_PATHS = [
+        'api/shop/delete-all-compare-items',
+        'api/shop/delete-all-wishlists',
+    ];
+
+    public function handle(Request $request, Closure $next)
+    {
+        if (! $request->isMethod('POST')) {
+            return $next($request);
+        }
+
+        if (! in_array(trim($request->path(), '/'), self::BODYLESS_POST_PATHS, true)) {
+            return $next($request);
+        }
+
+        if (! $request->headers->has('Content-Type')) {
+            $request->headers->set('Content-Type', 'application/json');
+        }
+
+        if (trim((string) $request->getContent()) === '') {
+            $request->initialize(
+                $request->query->all(),
+                $request->request->all(),
+                $request->attributes->all(),
+                $request->cookies->all(),
+                $request->files->all(),
+                $request->server->all(),
+                '{}'
+            );
+            $request->headers->set('Content-Type', 'application/json');
+        }
+
+        return $next($request);
+    }
+}

+ 1 - 22
packages/Webkul/BagistoApi/src/Http/Middleware/HandleInvalidInputException.php

@@ -16,27 +16,6 @@ class HandleInvalidInputException
 {
     public function handle(Request $request, Closure $next): Response
     {
-        try {
-            Log::info('HandleInvalidInputException middleware invoked', [
-                'path' => $request->path(),
-            ]);
-
-            return $next($request);
-        } catch (InvalidInputException $e) {
-            Log::info('InvalidInputException caught in middleware', [
-                'message' => $e->getMessage(),
-            ]);
-            // Return proper API error response for REST APIs
-            if ($request->wantsJson() || $request->is('api/*')) {
-                return response()->json([
-                    'type'   => $e->getType(),
-                    'title'  => $e->getTitle(),
-                    'status' => $e->getStatus(),
-                    'detail' => $e->getDetail(),
-                ], $e->getStatusCode(), [], JSON_UNESCAPED_SLASHES);
-            }
-
-            throw $e;
-        }
+        return $next($request);
     }
 }

+ 39 - 0
packages/Webkul/BagistoApi/src/Http/Middleware/PaginationHeaders.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace Webkul\BagistoApi\Http\Middleware;
+
+use Closure;
+use Illuminate\Http\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Reads pagination metadata stashed by PaginationHeaderNormalizer and
+ * adds standard X-* headers to the outgoing response.
+ *
+ *   X-Total-Count   total number of items across all pages
+ *   X-Page          current page (1-based)
+ *   X-Per-Page      items per page
+ *   X-Total-Pages   total number of pages
+ *
+ * Headers are added only when the normalizer captured a Paginator — i.e.
+ * for paginated REST collection responses. Single-resource and non-paginated
+ * collection responses are unchanged.
+ */
+class PaginationHeaders
+{
+    public function handle(Request $request, Closure $next): Response
+    {
+        $response = $next($request);
+
+        $meta = $request->attributes->get('bagistoapi.pagination');
+
+        if (is_array($meta)) {
+            $response->headers->set('X-Total-Count', (string) $meta['total']);
+            $response->headers->set('X-Page', (string) $meta['page']);
+            $response->headers->set('X-Per-Page', (string) $meta['per_page']);
+            $response->headers->set('X-Total-Pages', (string) $meta['total_pages']);
+        }
+
+        return $response;
+    }
+}

+ 4 - 1
packages/Webkul/BagistoApi/src/Models/AddProductInCart.php

@@ -11,6 +11,7 @@ use Webkul\BagistoApi\Dto\CartData;
 use Webkul\BagistoApi\Dto\CartInput;
 use Webkul\BagistoApi\State\CartTokenMutationProvider;
 use Webkul\BagistoApi\State\CartTokenProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * AddProductInCart - GraphQL & REST API Resource for Adding Products to Cart
@@ -30,6 +31,7 @@ use Webkul\BagistoApi\State\CartTokenProcessor;
             output: CartData::class,
             provider: CartTokenMutationProvider::class,
             processor: CartTokenProcessor::class,
+            deserialize: false,
             denormalizationContext: [
                 'allow_extra_attributes' => true,
                 'groups'                 => ['mutation'],
@@ -38,7 +40,8 @@ use Webkul\BagistoApi\State\CartTokenProcessor;
                 'groups'                 => ['mutation'],
             ],
             description: 'Add product to cart. Can be used for both authenticated users and guests.',
-            openapi: new Model\Operation(
+            openapi: new Operation(
+                tags: ['Cart'],
                 summary: 'Add product to cart',
                 description: 'Add a product to the shopping cart with quantity and optional product options.',
                 requestBody: new Model\RequestBody(

+ 5 - 1
packages/Webkul/BagistoApi/src/Models/ApplyCoupon.php

@@ -12,6 +12,7 @@ use Webkul\BagistoApi\Dto\CartData;
 use Webkul\BagistoApi\Dto\CartInput;
 use Webkul\BagistoApi\State\CartTokenMutationProvider;
 use Webkul\BagistoApi\State\CartTokenProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * ApplyCoupon - GraphQL & REST API Resource for Applying Coupon Code
@@ -30,6 +31,7 @@ use Webkul\BagistoApi\State\CartTokenProcessor;
             output: CartData::class,
             provider: CartTokenMutationProvider::class,
             processor: CartTokenProcessor::class,
+            deserialize: false,
             denormalizationContext: [
                 'allow_extra_attributes' => true,
                 'groups'                 => ['mutation'],
@@ -38,7 +40,8 @@ use Webkul\BagistoApi\State\CartTokenProcessor;
                 'groups'                 => ['mutation'],
             ],
             description: 'Apply coupon code to cart.',
-            openapi: new Model\Operation(
+            openapi: new Operation(
+                tags: ['Cart'],
                 summary: 'Apply coupon to cart',
                 description: 'Apply a discount coupon code to the shopping cart.',
                 requestBody: new Model\RequestBody(
@@ -48,6 +51,7 @@ use Webkul\BagistoApi\State\CartTokenProcessor;
                         'application/json' => [
                             'schema' => [
                                 'type'       => 'object',
+                                'required'   => ['couponCode'],
                                 'properties' => [
                                     'couponCode' => [
                                         'type'        => 'string',

+ 41 - 12
packages/Webkul/BagistoApi/src/Models/AttributeOption.php

@@ -4,16 +4,53 @@ namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
-use Illuminate\Database\Eloquent\Model;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\OpenApi\Model;
+use Illuminate\Database\Eloquent\Model as EloquentModel;
 
 #[ApiResource(
     shortName: 'AttributeOption',
     description: 'Attribute option resource',
-    routePrefix: '/api/admin',
-    security: "is_granted('ROLE_ADMIN')",
+    operations: [
+        new GetCollection(
+            uriTemplate: '/attribute_options',
+            routePrefix: '/api/admin',
+            security: "is_granted('ROLE_ADMIN')",
+        ),
+        new Get(
+            uriTemplate: '/attribute_options/{id}',
+            routePrefix: '/api/admin',
+            security: "is_granted('ROLE_ADMIN')",
+        ),
+        new GetCollection(
+            uriTemplate: '/attribute-options',
+            routePrefix: '/api/shop',
+            openapi: new Model\Operation(
+                tags: ['Attribute'],
+                summary: 'List all attribute options',
+                description: 'Returns all attribute options.',
+            ),
+        ),
+        new Get(
+            uriTemplate: '/attribute-options/{id}',
+            routePrefix: '/api/shop',
+            openapi: new Model\Operation(
+                tags: ['Attribute'],
+                summary: 'Get a single attribute option by ID',
+            ),
+        ),
+    ],
 )]
 class AttributeOption extends \Webkul\Attribute\Models\AttributeOption
 {
+
+    #[ApiProperty(identifier: true, writable: false)]
+    public function getId(): ?int
+    {
+        return $this->id;
+    }
+
     #[ApiProperty(readableLink: true)]
     public function getTranslations()
     {
@@ -21,17 +58,9 @@ class AttributeOption extends \Webkul\Attribute\Models\AttributeOption
     }
 
     #[ApiProperty(readableLink: true, description: 'Current locale translation')]
-    public function getTranslation(?string $locale = null, ?bool $withFallback = null): ?Model
+    public function getTranslation(?string $locale = null, ?bool $withFallback = null): ?EloquentModel
     {
         return $this->translation;
     }
 
-    /**
-     * API Platform identifier
-     */
-    #[ApiProperty(identifier: true, writable: false)]
-    public function getId(): ?int
-    {
-        return $this->id;
-    }
 }

+ 10 - 1
packages/Webkul/BagistoApi/src/Models/AttributeTranslation.php

@@ -4,8 +4,17 @@ namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
 
-#[ApiResource()]
+#[ApiResource(
+    routePrefix: '/api/shop',
+    operations: [
+        new GetCollection,
+        new Get,
+    ],
+    graphQlOperations: []
+)]
 class AttributeTranslation extends \Webkul\Attribute\Models\AttributeTranslation
 {
     /**

+ 21 - 2
packages/Webkul/BagistoApi/src/Models/AttributeValue.php

@@ -4,13 +4,32 @@ namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
-use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Link;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use ApiPlatform\OpenApi\Model\Operation;
 
 #[ApiResource(
     routePrefix: '/api/shop',
-    operations: [],
+    shortName: 'AttributeValue',
+    uriTemplate: '/products/{productId}/attribute-values',
+    uriVariables: [
+        'productId' => new Link(
+            fromClass: Product::class,
+            fromProperty: 'attribute_values',
+            identifiers: ['id']
+        ),
+    ],
+    operations: [
+        new GetCollection(
+            openapi: new Operation(
+                tags: ['Product'],
+                summary: 'List attribute values for a product',
+                description: 'Returns the full set of attribute values associated with the given product ID. Each row carries the locale/channel scoping and the type-specific value column.',
+            ),
+        ),
+    ],
     graphQlOperations: []
 )]
 class AttributeValue extends Model

+ 118 - 51
packages/Webkul/BagistoApi/src/Models/BookingProduct.php

@@ -4,65 +4,144 @@ namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Link;
 use Illuminate\Database\Eloquent\Relations\HasMany;
 use Illuminate\Database\Eloquent\Relations\HasOne;
 use Webkul\BookingProduct\Models\BookingProduct as BaseBookingProduct;
+use ApiPlatform\OpenApi\Model\Operation;
 
 #[ApiResource(
     routePrefix: '/api/shop',
     uriTemplate: '/booking-products/{id}',
-    operations: [],
+    normalizationContext: ['skip_null_values' => false],
+    operations: [
+        new Get(
+            openapi: new Operation(
+                tags: ['Product Types'],
+                summary: 'Get booking-type product configuration',
+                description: 'Returns the booking-specific configuration with the type-specific slot data (`slots`) embedded inline — no dangling IRIs. The shape of `slots` varies by `type`: appointment / default / rental / table return a single config block; event returns `tickets[]`. To compute availability for a specific date use `/api/shop/booking-slots?id={booking_product_id}&date=YYYY-MM-DD`.',
+            ),
+        ),
+    ],
+    graphQlOperations: []
+)]
+#[ApiResource(
+    routePrefix: '/api/shop',
+    shortName: 'BookingProduct',
+    uriTemplate: '/products/{productId}/booking-products',
+    uriVariables: [
+        'productId' => new Link(
+            fromClass: Product::class,
+            fromProperty: 'booking_products',
+            identifiers: ['id']
+        ),
+    ],
+    operations: [
+        new GetCollection(
+            openapi: new Operation(
+                tags: ['Product Types'],
+                summary: 'List booking-product configurations for a product',
+                description: 'Booking-type only. Returns the booking-specific configuration row(s) for the given product. The `type` field on each row (default/appointment/rental/event/table) indicates which slot helper governs availability — use `/api/shop/booking-slots?id={id}&date=YYYY-MM-DD` to fetch slots.',
+            ),
+        ),
+    ],
     graphQlOperations: []
 )]
 class BookingProduct extends BaseBookingProduct
 {
-    /**
-     * Get default slot (for default booking type)
-     */
-    #[ApiProperty(writable: false, readable: true, required: false)]
-    public function getDefaultSlot()
-    {
-        return $this->default_slot;
-    }
+    protected $appends = ['slots'];
 
     /**
-     * Get appointment slot (for appointment booking type)
+     * Eloquent accessor — surfaced via $appends so API Platform sees `slots`
+     * as a regular model attribute. Returns the type-specific slot config
+     * inline as a nested object/array.
      */
-    #[ApiProperty(writable: false, readable: true, required: false)]
-    public function getAppointmentSlot()
+    public function getSlotsAttribute(): array
     {
-        return $this->appointment_slot;
-    }
+        $decode = function ($v) {
+            if (is_string($v)) {
+                $d = json_decode($v, true);
 
-    /**
-     * Get event tickets (for event booking type)
-     */
-    #[ApiProperty(writable: false, readable: true, required: false)]
-    public function getEventTickets()
-    {
-        return $this->event_tickets;
-    }
+                return is_array($d) ? $d : [];
+            }
 
-    /**
-     * Get rental slot (for rental booking type)
-     */
-    #[ApiProperty(writable: false, readable: true, required: false)]
-    public function getRentalSlot()
-    {
-        return $this->rental_slot;
-    }
+            return is_array($v) ? $v : [];
+        };
 
-    /**
-     * Get table slot (for table booking type)
-     */
-    #[ApiProperty(writable: false, readable: true, required: false)]
-    public function getTableSlot()
-    {
-        return $this->table_slot;
+        $slots = [];
+
+        switch ($this->type) {
+            case 'appointment':
+                if ($as = $this->appointment_slot) {
+                    $slots = [
+                        'duration'        => $as->duration,
+                        'breakTime'       => $as->break_time,
+                        'sameSlotAllDays' => (bool) $as->same_slot_all_days,
+                        'slots'           => $decode($as->slots ?? null),
+                    ];
+                }
+                break;
+            case 'default':
+                if ($ds = $this->default_slot) {
+                    $slots = [
+                        'bookingType'     => $ds->booking_type ?? null,
+                        'sameSlotAllDays' => isset($ds->same_slot_all_days) ? (bool) $ds->same_slot_all_days : null,
+                        'slots'           => $decode($ds->slots ?? null),
+                    ];
+                }
+                break;
+            case 'rental':
+                if ($rs = $this->rental_slot) {
+                    $slots = [
+                        'rentingType'     => $rs->renting_type ?? null,
+                        'dailyPrice'      => isset($rs->daily_price) ? (float) $rs->daily_price : null,
+                        'hourlyPrice'     => isset($rs->hourly_price) ? (float) $rs->hourly_price : null,
+                        'sameSlotAllDays' => isset($rs->same_slot_all_days) ? (bool) $rs->same_slot_all_days : null,
+                        'slots'           => $decode($rs->slots ?? null),
+                    ];
+                }
+                break;
+            case 'table':
+                if ($ts = $this->table_slot) {
+                    $slots = [
+                        'guestCapacity'          => $ts->guest_capacity ?? null,
+                        'prepTime'               => $ts->prep_time ?? null,
+                        'allowGuestsOverbooking' => isset($ts->allow_guests_overbooking) ? (bool) $ts->allow_guests_overbooking : null,
+                        'sameSlotAllDays'        => isset($ts->same_slot_all_days) ? (bool) $ts->same_slot_all_days : null,
+                        'slots'                  => $decode($ts->slots ?? null),
+                    ];
+                }
+                break;
+            case 'event':
+                $slots = [
+                    'tickets' => $this->event_tickets
+                        ? $this->event_tickets->map(fn ($t) => [
+                            'id'           => (int) $t->id,
+                            'price'        => $t->price !== null ? (float) $t->price : null,
+                            'qty'          => $t->qty ?? null,
+                            'specialPrice' => $t->special_price !== null ? (float) $t->special_price : null,
+                            'name'         => $t->name ?? null,
+                            'description'  => $t->description ?? null,
+                        ])->values()->all()
+                        : [],
+                ];
+                break;
+        }
+
+        return $slots;
     }
 
     /**
-     * Get the default slot relationship using BagistoApi model
+     * Backing slot relations. Kept readable so the GraphQL schema continues
+     * to expose `defaultSlot` / `appointmentSlot` / `eventTickets` /
+     * `rentalSlot` / `tableSlot` as fields on the BookingProduct type.
+     *
+     * For REST these will surface as IRIs to /api/shop/booking_product_*_slots/{id}
+     * — the slot models declare matching Get/GetCollection operations so the
+     * IRIs resolve. The new `slots` accessor above also inlines the same data
+     * for REST clients that prefer one-shot access.
      */
     #[ApiProperty(writable: false, readable: true, required: false)]
     public function default_slot(): HasOne
@@ -70,18 +149,12 @@ class BookingProduct extends BaseBookingProduct
         return $this->hasOne(BookingProductDefaultSlot::class, 'booking_product_id');
     }
 
-    /**
-     * Get the appointment slot relationship using BagistoApi model
-     */
     #[ApiProperty(writable: false, readable: true, required: false)]
     public function appointment_slot(): HasOne
     {
         return $this->hasOne(BookingProductAppointmentSlot::class, 'booking_product_id');
     }
 
-    /**
-     * Get the event tickets relationship using BagistoApi model
-     */
     #[ApiProperty(writable: false, readable: true, required: false)]
     public function event_tickets(): HasMany
     {
@@ -89,21 +162,15 @@ class BookingProduct extends BaseBookingProduct
             ->with(['translation', 'translations']);
     }
 
-    /**
-     * Get the rental slot relationship using BagistoApi model
-     */
     #[ApiProperty(writable: false, readable: true, required: false)]
     public function rental_slot(): HasOne
     {
         return $this->hasOne(BookingProductRentalSlot::class, 'booking_product_id');
     }
 
-    /**
-     * Get the table slot relationship using BagistoApi model
-     */
     #[ApiProperty(writable: false, readable: true, required: false)]
     public function table_slot(): HasOne
     {
         return $this->hasOne(BookingProductTableSlot::class, 'booking_product_id');
     }
-}
+}

+ 13 - 2
packages/Webkul/BagistoApi/src/Models/BookingProductAppointmentSlot.php

@@ -4,9 +4,20 @@ namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
 use Webkul\BookingProduct\Models\BookingProductAppointmentSlot as BaseModel;
-
-#[ApiResource(routePrefix: '/api/shop', operations: [], graphQlOperations: [])]
+use ApiPlatform\OpenApi\Model\Operation;
+
+#[ApiResource(
+    routePrefix: '/api/shop',
+    normalizationContext: ['skip_null_values' => false],
+    operations: [
+        new Get(openapi: new Operation(tags: ['Product Types'], summary: 'Get an appointment-type booking slot config by ID')),
+        new GetCollection(openapi: new Operation(tags: ['Product Types'], summary: 'List appointment-type booking slot configs')),
+    ],
+    graphQlOperations: []
+)]
 class BookingProductAppointmentSlot extends BaseModel
 {
     /**

+ 13 - 2
packages/Webkul/BagistoApi/src/Models/BookingProductDefaultSlot.php

@@ -4,9 +4,20 @@ namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
 use Webkul\BookingProduct\Models\BookingProductDefaultSlot as BaseModel;
-
-#[ApiResource(routePrefix: '/api/shop', operations: [], graphQlOperations: [])]
+use ApiPlatform\OpenApi\Model\Operation;
+
+#[ApiResource(
+    routePrefix: '/api/shop',
+    normalizationContext: ['skip_null_values' => false],
+    operations: [
+        new Get(openapi: new Operation(tags: ['Product Types'], summary: 'Get a default-type booking slot config by ID')),
+        new GetCollection(openapi: new Operation(tags: ['Product Types'], summary: 'List default-type booking slot configs')),
+    ],
+    graphQlOperations: []
+)]
 class BookingProductDefaultSlot extends BaseModel
 {
     /**

+ 12 - 2
packages/Webkul/BagistoApi/src/Models/BookingProductEventTicket.php

@@ -4,11 +4,21 @@ namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
 use Illuminate\Database\Eloquent\Relations\HasMany;
 use Illuminate\Database\Eloquent\Relations\HasOne;
 use Webkul\BookingProduct\Models\BookingProductEventTicket as BaseModel;
-
-#[ApiResource(routePrefix: '/api/shop', operations: [], graphQlOperations: [])]
+use ApiPlatform\OpenApi\Model\Operation;
+
+#[ApiResource(
+    routePrefix: '/api/shop',
+    operations: [
+        new Get(openapi: new Operation(tags: ['Product Types'], summary: 'Get an event-type booking ticket by ID')),
+        new GetCollection(openapi: new Operation(tags: ['Product Types'], summary: 'List event-type booking tickets')),
+    ],
+    graphQlOperations: []
+)]
 class BookingProductEventTicket extends BaseModel
 {
     protected $appends = ['formatted_price', 'formatted_special_price'];

+ 12 - 1
packages/Webkul/BagistoApi/src/Models/BookingProductRentalSlot.php

@@ -4,9 +4,20 @@ namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
 use Webkul\BookingProduct\Models\BookingProductRentalSlot as BaseModel;
+use ApiPlatform\OpenApi\Model\Operation;
 
-#[ApiResource(routePrefix: '/api/shop', operations: [], graphQlOperations: [])]
+#[ApiResource(
+    routePrefix: '/api/shop',
+    normalizationContext: ['skip_null_values' => false],
+    operations: [
+        new Get(openapi: new Operation(tags: ['Product Types'], summary: 'Get a rental-type booking slot config by ID')),
+        new GetCollection(openapi: new Operation(tags: ['Product Types'], summary: 'List rental-type booking slot configs')),
+    ],
+    graphQlOperations: []
+)]
 class BookingProductRentalSlot extends BaseModel
 {
     /**

+ 12 - 1
packages/Webkul/BagistoApi/src/Models/BookingProductTableSlot.php

@@ -4,9 +4,20 @@ namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
 use Webkul\BookingProduct\Models\BookingProductTableSlot as BaseModel;
+use ApiPlatform\OpenApi\Model\Operation;
 
-#[ApiResource(routePrefix: '/api/shop', operations: [], graphQlOperations: [])]
+#[ApiResource(
+    routePrefix: '/api/shop',
+    normalizationContext: ['skip_null_values' => false],
+    operations: [
+        new Get(openapi: new Operation(tags: ['Product Types'], summary: 'Get a table-type booking slot config by ID')),
+        new GetCollection(openapi: new Operation(tags: ['Product Types'], summary: 'List table-type booking slot configs')),
+    ],
+    graphQlOperations: []
+)]
 class BookingProductTableSlot extends BaseModel
 {
     /**

+ 31 - 3
packages/Webkul/BagistoApi/src/Models/BookingSlot.php

@@ -4,8 +4,10 @@ namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\GraphQl\QueryCollection;
 use Webkul\BagistoApi\State\BookingSlotProvider;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Booking Slot GraphQL API Resource
@@ -24,7 +26,33 @@ use Webkul\BagistoApi\State\BookingSlotProvider;
     routePrefix: '/api/shop',
     shortName: 'BookingSlot',
     uriTemplate: '/booking-slots',
-    operations: [],
+    operations: [ 
+        new GetCollection(
+            provider: BookingSlotProvider::class,
+            paginationEnabled: false,
+            openapi: new Operation(
+                tags: ['Product Types'],
+                summary: 'Get available booking slots for a product on a specific date',
+                description: 'Mirrors the GraphQL bookingSlots query. Routes the booking product to its type-specific helper (default | appointment | rental | event | table). Flat types return [{slotId, from, to, timestamp, qty}]; rental returns [{slotId, time, slots: [{from, to, timestamp, qty}]}].',
+                parameters: [
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'id',
+                        in: 'query',
+                        description: 'Booking product ID.',
+                        required: true,
+                        schema: ['type' => 'integer', 'example' => 1],
+                    ),
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'date',
+                        in: 'query',
+                        description: 'Date to fetch slots for (YYYY-MM-DD).',
+                        required: true,
+                        schema: ['type' => 'string', 'format' => 'date', 'example' => '2026-03-26'],
+                    ),
+                ],
+            ),
+        ),
+    ],
     graphQlOperations: [
         new QueryCollection(
             args: [
@@ -55,7 +83,7 @@ class BookingSlot
      * @var string|null Slot identifier (timestamp for flat slots, sequential index for groups)
      */
     #[ApiProperty(writable: false, readable: true)]
-    public ?string $slotId = null;
+    public ?string $slot_Id = null;
 
     /**
      * @var string|null Time range group label for rental hourly slots (e.g., "10:00 AM - 12:00 PM").
@@ -113,7 +141,7 @@ class BookingSlot
         ?array $slots = null
     ) {
         $this->_id = $slotId ?? uniqid('slot_');
-        $this->slotId = $slotId;
+        $this->slot_id = $slotId;
         $this->time = $time;
         $this->from = $from;
         $this->to = $to;

+ 25 - 0
packages/Webkul/BagistoApi/src/Models/CancelOrder.php

@@ -9,6 +9,7 @@ use ApiPlatform\Metadata\GraphQl\Mutation;
 use Symfony\Component\Serializer\Annotation\Groups;
 use Webkul\BagistoApi\Dto\CancelOrderInput;
 use Webkul\BagistoApi\State\CancelOrderProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Cancel Order Response Model
@@ -29,6 +30,30 @@ use Webkul\BagistoApi\State\CancelOrderProcessor;
             normalizationContext: [
                 'groups' => ['mutation'],
             ],
+            openapi: new Operation(
+                tags: ['Customer Order'],
+                summary: 'Cancel a customer order',
+                description: 'Cancels the given pending order owned by the authenticated customer. Returns success/failure status.',
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'required'   => ['orderId'],
+                                'properties' => [
+                                    'orderId' => [
+                                        'type'        => 'integer',
+                                        'description' => 'The ID of the order to cancel.',
+                                        'example'     => 411,
+                                    ],
+                                ],
+                            ],
+                            'example' => ['orderId' => 411],
+                        ],
+                    ]),
+                ),
+            ),
         ),
     ],
     graphQlOperations: [

+ 87 - 15
packages/Webkul/BagistoApi/src/Models/CartToken.php

@@ -8,10 +8,12 @@ use ApiPlatform\Metadata\Get;
 use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\Post;
 use ApiPlatform\Metadata\GraphQl\Mutation;
+use ApiPlatform\OpenApi\Model;
 use Symfony\Component\Serializer\Annotation\Groups;
 use Webkul\BagistoApi\Dto\CartInput;
 use Webkul\BagistoApi\State\CartTokenMutationProvider;
 use Webkul\BagistoApi\State\CartTokenProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * CartToken - GraphQL API Resource for Cart Operations
@@ -34,21 +36,91 @@ use Webkul\BagistoApi\State\CartTokenProcessor;
     paginationEnabled: false,
     uriTemplate: '/cart-tokens/{id}',
     operations: [
-        // new Post(
-        //     name: 'create',
-        //     uriTemplate: '/cart-tokens',
-        //     input: CartInput::class,
-        //     output: CartToken::class,
-        //     processor: CartTokenProcessor::class,
-        //     denormalizationContext: [
-        //         'allow_extra_attributes' => true,
-        //         'groups'                 => ['mutation'],
-        //     ],
-        //     normalizationContext: [
-        //         'groups'                 => ['mutation'],
-        //     ],
-        //     description: 'Create new guest cart with unique UUID token or get authenticated customer cart.',
-        // ),
+        new Post(
+            name: 'createCartToken',
+            uriTemplate: '/cart-tokens',
+            input: CartInput::class,
+            output: CartToken::class,
+            provider: CartTokenMutationProvider::class,
+            processor: CartTokenProcessor::class,
+            denormalizationContext: [
+                'allow_extra_attributes' => true,
+                'groups'                 => ['mutation'],
+            ],
+            normalizationContext: [
+                'groups' => ['mutation'],
+            ],
+            description: 'Create new guest cart with unique UUID token or get authenticated customer cart. Returns sessionToken for guests.',
+            openapi: new Operation(
+                tags: ['Cart'],
+                summary: 'Create cart token',
+                description: 'Create a new guest cart with unique UUID token or get authenticated customer cart. Returns sessionToken for guests.',
+                requestBody: new Model\RequestBody(
+                    description: 'Cart creation payload',
+                    required: false,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'properties' => [
+                                    'sessionId' => [
+                                        'type'        => 'string',
+                                        'example'     => 'my-session-id',
+                                        'description' => 'Session ID for cart creation (optional)',
+                                    ],
+                                    'createNew' => [
+                                        'type'        => 'boolean',
+                                        'example'     => true,
+                                        'description' => 'Generate new cart with unique token (optional)',
+                                    ],
+                                ],
+                            ],
+                            'examples' => [
+                                'guest_cart' => [
+                                    'summary'     => 'Create Guest Cart',
+                                    'description' => 'Create a new guest cart token',
+                                    'value'       => [
+                                        'createNew' => true,
+                                    ],
+                                ],
+                                'guest_cart_with_session' => [
+                                    'summary'     => 'Create Guest Cart with Session ID',
+                                    'description' => 'Create a guest cart linked to a session',
+                                    'value'       => [
+                                        'sessionId' => 'my-session-id',
+                                        'createNew' => true,
+                                    ],
+                                ],
+                            ],
+                        ],
+                    ]),
+                ),
+                responses: [
+                    '201' => new Model\Response(
+                        description: 'Guest cart created; returns the cart token used to identify the guest for subsequent cart/checkout calls.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    'id'                  => 6891,
+                                    'cartToken'           => '81975c94-4d61-4be8-b2f9-3562d58e646c',
+                                    'customerId'          => null,
+                                    'channelId'           => 1,
+                                    'itemsCount'          => 0,
+                                    'items'               => [],
+                                    'subtotal'            => 0,
+                                    'grandTotal'          => 0,
+                                    'taxAmount'           => 0,
+                                    'discountAmount'      => 0,
+                                    'couponCode'          => null,
+                                    'formattedSubtotal'   => '$0.00',
+                                    'formattedGrandTotal' => '$0.00',
+                                ],
+                            ],
+                        ]),
+                    ),
+                ],
+            ),
+        ),
         new GetCollection(uriTemplate: '/cart-tokens',
             provider: CartTokenMutationProvider::class,
             normalizationContext: [

+ 32 - 0
packages/Webkul/BagistoApi/src/Models/Category.php

@@ -12,15 +12,47 @@ use Webkul\BagistoApi\Resolver\BaseQueryItemResolver;
 use Webkul\BagistoApi\Resolver\CategoryCollectionResolver;
 use Webkul\BagistoApi\State\CursorAwareCollectionProvider;
 use Webkul\Category\Models\Category as BaseCategory;
+use Webkul\BagistoApi\State\CategoryRestProvider;
+use ApiPlatform\OpenApi\Model\Operation;
 
 #[ApiResource(
     routePrefix: '/api/shop',
     operations: [
         new Get,
         new GetCollection(
+            provider: CategoryRestProvider::class,
             paginationEnabled: true,
+            paginationClientItemsPerPage: true,
             paginationItemsPerPage: 15,
             paginationMaximumItemsPerPage: 100,
+            openapi: new Operation(
+                tags: ['Category'],
+                summary: 'List active categories with optional parent filtering',
+                description: 'Returns a flat list of active categories only (status=1). Admin-disabled categories are never returned. Use `?parent_id=N` for direct children of a category. Each item embeds its `translation`, `children`, and `filterableAttributes`. For a hierarchical tree response use /category-trees instead.',
+                parameters: [
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'parent_id',
+                        in: 'query',
+                        description: 'Return only direct children of this category ID. Accepts `parentId` as an alias.',
+                        required: false,
+                        schema: ['type' => 'integer', 'example' => 2],
+                    ),
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'page',
+                        in: 'query',
+                        description: 'Page number (1-based).',
+                        required: false,
+                        schema: ['type' => 'integer', 'default' => 1],
+                    ),
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'per_page',
+                        in: 'query',
+                        description: 'Items per page. Default 15, max 100.',
+                        required: false,
+                        schema: ['type' => 'integer', 'default' => 15],
+                    ),
+                ],
+            ),
         ),
     ],
     graphQlOperations: [

+ 21 - 1
packages/Webkul/BagistoApi/src/Models/CategoryTree.php

@@ -5,6 +5,7 @@ namespace Webkul\BagistoApi\Models;
 use ApiPlatform\Metadata\ApiResource;
 use ApiPlatform\Metadata\GetCollection;
 use Webkul\BagistoApi\State\CategoryTreeProvider;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Category Tree REST API Resource
@@ -23,7 +24,26 @@ use Webkul\BagistoApi\State\CategoryTreeProvider;
             uriTemplate: '/category-trees',
             provider: CategoryTreeProvider::class,
             paginationEnabled: false,
-            description: 'Get hierarchical category tree structure. Use parentId to filter by parent, depth to control depth.',
+            openapi: new Operation(
+                summary: 'Get hierarchical category tree structure',
+                description: 'Returns categories as a nested tree. Pass parentId to scope results to children of that category.',
+                parameters: [
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'parentId',
+                        in: 'query',
+                        description: 'Return children of this category ID. Omit to return root categories.',
+                        required: false,
+                        schema: ['type' => 'integer', 'example' => 1],
+                    ),
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'depth',
+                        in: 'query',
+                        description: 'Maximum nesting depth (default: 4).',
+                        required: false,
+                        schema: ['type' => 'integer', 'default' => 4, 'example' => 4],
+                    ),
+                ],
+            ),        
         ),
     ],
 )]

+ 2 - 1
packages/Webkul/BagistoApi/src/Models/Channel.php

@@ -15,9 +15,10 @@ use Webkul\BagistoApi\State\ChannelProvider;
 
 #[ApiResource(
     routePrefix: '/api/shop',
+    normalizationContext: ['skip_null_values' => false],
     operations: [
         new Get(provider: ChannelProvider::class),
-        new GetCollection(provider: ChannelProvider::class),
+        new GetCollection(provider: ChannelProvider::class, paginationClientItemsPerPage: true),
     ],
     graphQlOperations: [
         new Query(resolver: BaseQueryItemResolver::class),

+ 20 - 1
packages/Webkul/BagistoApi/src/Models/ChannelTranslation.php

@@ -3,10 +3,29 @@
 namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\OpenApi\Model\Operation;
 
 #[ApiResource(
     routePrefix: '/api/shop',
-    operations: [],
+    normalizationContext: ['skip_null_values' => false],
+    operations: [
+        new Get(
+            openapi: new Operation(
+                tags: ['Channel'],
+                summary: 'Get a channel translation by ID',
+                description: 'Returns the locale-specific translation row for a channel (homeSeo, name, etc.). Referenced from /api/shop/channels/{id} responses via the `translation` and `translations` IRI fields.',
+            ),
+        ),
+        new GetCollection(
+            openapi: new Operation(
+                tags: ['Channel'],
+                summary: 'List channel translations',
+                description: 'Lists all channel translation rows.',
+            ),
+        ),
+    ],
     graphQlOperations: []
 )]
 class ChannelTranslation extends \Webkul\Core\Models\ChannelTranslation {}

+ 72 - 2
packages/Webkul/BagistoApi/src/Models/CheckoutAddress.php

@@ -9,11 +9,13 @@ use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\GraphQl\Mutation;
 use ApiPlatform\Metadata\GraphQl\Query;
 use Symfony\Component\Serializer\Annotation\Groups;
+use ApiPlatform\Metadata\Post;
 use Webkul\BagistoApi\Dto\CheckoutAddressInput;
 use Webkul\BagistoApi\Dto\CheckoutAddressOutput;
 use Webkul\BagistoApi\Resolver\BaseQueryItemResolver;
 use Webkul\BagistoApi\State\CheckoutAddressProvider;
 use Webkul\BagistoApi\State\CheckoutProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * CheckoutAddress - GraphQL API Resource for Checkout Address
@@ -25,8 +27,76 @@ use Webkul\BagistoApi\State\CheckoutProcessor;
     shortName: 'CheckoutAddress',
     uriTemplate: '/checkout-addresses',
     operations: [
-        new Get(uriTemplate: '/checkout-addresses/{id}'),
-        new GetCollection(uriTemplate: '/checkout-addresses'),
+        new Get(
+            uriTemplate: '/checkout-addresses/{id}',
+            openapi: new Operation(tags: ['Customer Order']),
+        ),
+        new GetCollection(
+            uriTemplate: '/checkout-addresses',
+            openapi: new Operation(tags: ['Customer Order']),
+        ),
+        new Post(
+            uriTemplate: '/checkout-addresses',
+            output: CheckoutAddressOutput::class,
+            processor: CheckoutProcessor::class,
+            normalizationContext: [
+                'groups'            => ['mutation'],
+                'skip_null_values'  => false,
+            ],
+            denormalizationContext: [
+                'allow_extra_attributes' => true,
+                'groups'                 => ['mutation'],
+            ],
+            openapi: new Operation(
+                tags: ['Checkout'],
+                summary: 'Save billing and shipping addresses for checkout',
+                description: 'Saves billing and shipping addresses to the current cart. Use `useForShipping: true` to copy billing as shipping, or provide shipping fields for a different shipping address.',
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'     => 'object',
+                                'required' => [
+                                    'billingFirstName',
+                                    'billingLastName',
+                                    'billingEmail',
+                                    'billingAddress',
+                                    'billingCity',
+                                    'billingCountry',
+                                    'billingState',
+                                    'billingPostcode',
+                                    'billingPhoneNumber',
+                                ],
+                                'properties' => [
+                                    'billingFirstName'    => ['type' => 'string', 'example' => 'John'],
+                                    'billingLastName'     => ['type' => 'string', 'example' => 'Doe'],
+                                    'billingEmail'        => ['type' => 'string', 'example' => 'john@example.com'],
+                                    'billingCompanyName'  => ['type' => 'string', 'example' => ''],
+                                    'billingAddress'      => ['type' => 'string', 'example' => '123 Main St'],
+                                    'billingCity'         => ['type' => 'string', 'example' => 'Los Angeles'],
+                                    'billingCountry'      => ['type' => 'string', 'example' => 'US'],
+                                    'billingState'        => ['type' => 'string', 'example' => 'CA'],
+                                    'billingPostcode'     => ['type' => 'string', 'example' => '90001'],
+                                    'billingPhoneNumber'  => ['type' => 'string', 'example' => '2125551234'],
+                                    'useForShipping'      => ['type' => 'boolean', 'example' => true],
+                                    'shippingFirstName'   => ['type' => 'string', 'example' => 'Jane'],
+                                    'shippingLastName'    => ['type' => 'string', 'example' => 'Doe'],
+                                    'shippingEmail'       => ['type' => 'string', 'example' => 'jane@example.com'],
+                                    'shippingCompanyName' => ['type' => 'string', 'example' => ''],
+                                    'shippingAddress'     => ['type' => 'string', 'example' => '456 Oak Ave'],
+                                    'shippingCity'        => ['type' => 'string', 'example' => 'San Francisco'],
+                                    'shippingCountry'     => ['type' => 'string', 'example' => 'US'],
+                                    'shippingState'       => ['type' => 'string', 'example' => 'CA'],
+                                    'shippingPostcode'    => ['type' => 'string', 'example' => '94102'],
+                                    'shippingPhoneNumber' => ['type' => 'string', 'example' => '4155559876'],
+                                ],
+                            ],
+                        ],
+                    ]),
+                ),
+            ),
+        ),
     ],
     graphQlOperations: [
         new Query(

+ 39 - 2
packages/Webkul/BagistoApi/src/Models/CheckoutOrder.php

@@ -7,10 +7,12 @@ use ApiPlatform\Metadata\ApiResource;
 use ApiPlatform\Metadata\Get;
 use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\GraphQl\Mutation;
+use ApiPlatform\Metadata\Post;
 use Symfony\Component\Serializer\Annotation\Groups;
 use Webkul\BagistoApi\Dto\CartData;
 use Webkul\BagistoApi\Dto\CheckoutAddressInput;
 use Webkul\BagistoApi\State\CheckoutProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * CheckoutOrder - GraphQL API Resource for Creating Order from Cart
@@ -22,8 +24,43 @@ use Webkul\BagistoApi\State\CheckoutProcessor;
     shortName: 'CheckoutOrder',
     uriTemplate: '/checkout-orders',
     operations: [
-        new Get(uriTemplate: '/checkout-orders/{id}'),
-        new GetCollection(uriTemplate: '/checkout-orders'),
+        new Get(
+            uriTemplate: '/checkout-orders/{id}',
+            openapi: new Operation(tags: ['Customer Order']),
+        ),
+        new GetCollection(
+            uriTemplate: '/checkout-orders',
+            openapi: new Operation(tags: ['Customer Order']),
+        ),
+        new Post(
+            uriTemplate: '/checkout-orders',
+            processor: CheckoutProcessor::class,
+            normalizationContext: [
+                'groups'           => ['mutation'],
+                'skip_null_values' => false,
+            ],
+            denormalizationContext: [
+                'allow_extra_attributes' => true,
+                'groups'                 => ['mutation'],
+            ],
+            openapi: new Operation(
+                tags: ['Checkout'],
+                summary: 'Create order from cart',
+                description: 'Finalizes checkout and creates an order from the current cart. The cart is identified by the Bearer token in the Authorization header; all address, shipping, and payment data must already be saved on the cart.',
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    required: false,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'properties' => new \ArrayObject,
+                            ],
+                            'example' => new \ArrayObject,
+                        ],
+                    ]),
+                ),
+            ),
+        ),
     ],
     graphQlOperations: [
         new Mutation(

+ 39 - 2
packages/Webkul/BagistoApi/src/Models/CheckoutPaymentMethod.php

@@ -5,9 +5,11 @@ namespace Webkul\BagistoApi\Models;
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
 use ApiPlatform\Metadata\GraphQl\Mutation;
+use ApiPlatform\Metadata\Post;
 use Symfony\Component\Serializer\Annotation\Groups;
 use Webkul\BagistoApi\Dto\CheckoutAddressInput;
 use Webkul\BagistoApi\State\CheckoutProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * CheckoutPaymentMethod - GraphQL API Resource for Checkout Payment Method
@@ -15,9 +17,44 @@ use Webkul\BagistoApi\State\CheckoutProcessor;
  * Provides mutation for selecting and saving payment method during checkout
  */
 #[ApiResource(
-    routePrefix: '/api',
+    routePrefix: '/api/shop',
     shortName: 'CheckoutPaymentMethod',
-    operations: [],
+    operations: [
+        new Post(
+            uriTemplate: '/checkout-payment-methods',
+            output: self::class,
+            processor: CheckoutProcessor::class,
+            normalizationContext: [
+                'groups'            => ['mutation'],
+                'skip_null_values'  => false,
+            ],
+            denormalizationContext: [
+                'allow_extra_attributes' => true,
+                'groups'                 => ['mutation'],
+            ],
+            openapi: new Operation(
+                tags: ['Checkout'],
+                summary: 'Save selected payment method for checkout',
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'required'   => ['paymentMethod'],
+                                'properties' => [
+                                    'paymentMethod'     => ['type' => 'string', 'example' => 'moneytransfer'],
+                                    'paymentSuccessUrl' => ['type' => 'string', 'example' => 'https://myapp.com/payment/success'],
+                                    'paymentFailureUrl' => ['type' => 'string', 'example' => 'https://myapp.com/payment/failure'],
+                                    'paymentCancelUrl'  => ['type' => 'string', 'example' => 'https://myapp.com/payment/cancel'],
+                                ],
+                            ],
+                        ],
+                    ]),
+                ),
+            ),
+        ),
+    ],
     graphQlOperations: [
         new Mutation(
             name: 'create',

+ 47 - 3
packages/Webkul/BagistoApi/src/Models/CheckoutShippingMethod.php

@@ -4,12 +4,15 @@ namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
-use ApiPlatform\Metadata\Get;
 use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\GraphQl\Mutation;
+use ApiPlatform\Metadata\Post;
 use Symfony\Component\Serializer\Annotation\Groups;
 use Webkul\BagistoApi\Dto\CheckoutAddressInput;
+use Webkul\BagistoApi\Dto\ShippingRateOutput;
 use Webkul\BagistoApi\State\CheckoutProcessor;
+use Webkul\BagistoApi\State\ShippingRatesProvider;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * CheckoutShippingMethod - GraphQL API Resource for Checkout Shipping Method
@@ -21,8 +24,49 @@ use Webkul\BagistoApi\State\CheckoutProcessor;
     shortName: 'CheckoutShippingMethod',
     uriTemplate: '/checkout-shipping-methods',
     operations: [
-        new Get(uriTemplate: '/checkout-shipping-methods/{id}'),
-        new GetCollection(uriTemplate: '/checkout-shipping-methods'),
+        new GetCollection(
+            uriTemplate: '/checkout-shipping-methods',
+            output: ShippingRateOutput::class,
+            provider: ShippingRatesProvider::class,
+            paginationEnabled: false,
+            normalizationContext: ['skip_null_values' => false],
+            openapi: new Operation(
+                tags: ['CheckoutShippingMethod'],
+                summary: 'Get available shipping methods',
+                description: 'Returns the shipping rates available for the authenticated customer\'s active cart. The cart must have a shipping address set (via POST /api/shop/checkout-addresses) before rates can be computed.',
+            ),
+        ),
+        new Post(
+            uriTemplate: '/checkout-shipping-methods',
+            output: self::class,
+            processor: CheckoutProcessor::class,
+            normalizationContext: [
+                'groups'            => ['mutation'],
+                'skip_null_values'  => false,
+            ],
+            denormalizationContext: [
+                'allow_extra_attributes' => true,
+                'groups'                 => ['mutation'],
+            ],
+            openapi: new Operation(
+                tags: ['CheckoutShippingMethod'],
+                summary: 'Save selected shipping method for checkout',
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'required'   => ['shippingMethod'],
+                                'properties' => [
+                                    'shippingMethod' => ['type' => 'string', 'example' => 'flatrate_flatrate'],
+                                ],
+                            ],
+                        ],
+                    ]),
+                ),
+            ),
+        ),
     ],
     graphQlOperations: [
         new Mutation(

+ 22 - 0
packages/Webkul/BagistoApi/src/Models/ContactUs.php

@@ -6,10 +6,12 @@ use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
 use ApiPlatform\Metadata\GraphQl\Mutation;
 use ApiPlatform\Metadata\Post;
+use ApiPlatform\OpenApi\Model;
 use Symfony\Component\Serializer\Attribute\Groups;
 use Webkul\BagistoApi\Dto\ContactUsInput;
 use Webkul\BagistoApi\Dto\ContactUsOutput;
 use Webkul\BagistoApi\State\Processor\ContactUsProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 #[ApiResource(
     routePrefix: '/api/shop',
@@ -29,6 +31,26 @@ use Webkul\BagistoApi\State\Processor\ContactUsProcessor;
                 'groups' => ['mutation'],
             ],
             description: 'Submit a contact us inquiry',
+            openapi: new Operation(
+                summary: 'Submit a contact us inquiry',
+                requestBody: new Model\RequestBody(
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'required'   => ['name', 'email', 'message'],
+                                'properties' => [
+                                    'name'    => ['type' => 'string', 'example' => 'John Doe'],
+                                    'email'   => ['type' => 'string', 'format' => 'email', 'example' => 'john@example.com'],
+                                    'contact' => ['type' => 'string', 'example' => '+1234567890'],
+                                    'message' => ['type' => 'string', 'example' => 'I have a question about your products'],
+                                ],
+                            ],
+                        ],
+                    ]),
+                ),
+            ),
         ),
     ],
     graphQlOperations: [

+ 1 - 5
packages/Webkul/BagistoApi/src/Models/Country.php

@@ -42,11 +42,7 @@ class Country extends BaseCountry
         return $this->hasMany(\Webkul\BagistoApi\Models\CountryState::class, 'country_id');
     }
 
-    /**
-     * Get the default translation for the country
-     * Note: This field is excluded from GraphQL to avoid null conflicts.
-     * Use 'translations' (collection) instead to get all translations.
-     */
+    #[ApiProperty(readable: false)]
     public function getTranslation(?string $locale = null, ?bool $withFallback = null): ?\Illuminate\Database\Eloquent\Model
     {
         return parent::getTranslation($locale, $withFallback);

+ 55 - 2
packages/Webkul/BagistoApi/src/Models/Currency.php

@@ -10,12 +10,65 @@ use ApiPlatform\Metadata\GraphQl\Query;
 use ApiPlatform\Metadata\GraphQl\QueryCollection;
 use Webkul\BagistoApi\Resolver\BaseQueryItemResolver;
 use Webkul\BagistoApi\State\CursorAwareCollectionProvider;
+use ApiPlatform\OpenApi\Model\Operation;
 
 #[ApiResource(
     routePrefix: '/api/shop',
     operations: [
-        new Get,
-        new GetCollection,
+        new Get(
+            openapi: new Operation(
+                tags: ['Currency'],
+                summary: 'Get a currency by ID',
+                description: 'Returns one store currency. Public endpoint.',
+                responses: [
+                    '200' => new \ApiPlatform\OpenApi\Model\Response(
+                        description: 'The currency.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    'id'               => 1,
+                                    'code'             => 'USD',
+                                    'name'             => 'US Dollar',
+                                    'symbol'           => '$',
+                                    'decimal'          => 2,
+                                    'groupSeparator'   => ',',
+                                    'decimalSeparator' => '.',
+                                ],
+                            ],
+                        ]),
+                    ),
+                    '404' => new \ApiPlatform\OpenApi\Model\Response(description: 'Currency not found.'),
+                ],
+            ),
+        ),
+        new GetCollection(
+            paginationClientItemsPerPage: true,
+            openapi: new Operation(
+                tags: ['Currency'],
+                summary: 'List currencies',
+                description: 'Returns all store currencies. Public endpoint.',
+                responses: [
+                    '200' => new \ApiPlatform\OpenApi\Model\Response(
+                        description: 'List of currencies.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    [
+                                        'id'               => 1,
+                                        'code'             => 'USD',
+                                        'name'             => 'US Dollar',
+                                        'symbol'           => '$',
+                                        'decimal'          => 2,
+                                        'groupSeparator'   => ',',
+                                        'decimalSeparator' => '.',
+                                    ],
+                                ],
+                            ],
+                        ]),
+                    ),
+                ],
+            ),
+        ),
     ],
     graphQlOperations: [
         new Query(resolver: BaseQueryItemResolver::class),

+ 7 - 21
packages/Webkul/BagistoApi/src/Models/Customer.php

@@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Storage;
 use Symfony\Component\Serializer\Annotation\Groups;
 use Webkul\BagistoApi\Resolver\CustomerQueryResolver;
 use Webkul\BagistoApi\State\CustomerProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 #[ApiResource(
     routePrefix: '/api/shop',
@@ -37,9 +38,9 @@ use Webkul\BagistoApi\State\CustomerProcessor;
             normalizationContext: [
                 'skip_null_values' => false,
             ],
-            openapi: new Model\Operation(
-                summary: 'Create a new customer',
-                description: 'Register a new customer account',
+            openapi: new Operation(
+                tags: ['Customer'],
+                summary: 'Register a new customer',
                 requestBody: new Model\RequestBody(
                     description: 'Customer registration data',
                     content: new \ArrayObject([
@@ -56,6 +57,9 @@ use Webkul\BagistoApi\State\CustomerProcessor;
                                     'phone'                  => ['type' => 'string', 'example' => '1234567890'],
                                     'gender'                 => ['type' => 'string', 'enum' => ['Male', 'Female', 'Other']],
                                     'dateOfBirth'            => ['type' => 'string', 'format' => 'date', 'example' => '1990-01-15'],
+                                    'status'                 => ['type' => 'integer', 'example' => 1, 'description' => 'Customer status (1 = active, 0 = inactive)'],
+                                    'isVerified'             => ['type' => 'integer', 'example' => 1, 'description' => 'Whether the customer is verified (1 = verified, 0 = not verified)'],
+                                    'isSuspended'            => ['type' => 'integer', 'example' => 0, 'description' => 'Whether the customer is suspended (1 = suspended, 0 = active)'],
                                     'subscribedToNewsLetter' => ['type' => 'boolean', 'example' => true],
                                     'deviceToken'            => ['type' => 'string', 'example' => 'your-fcm-device-token'],
                                 ],
@@ -65,24 +69,6 @@ use Webkul\BagistoApi\State\CustomerProcessor;
                 ),
             ),
         ),
-        new \ApiPlatform\Metadata\Put(
-            input: self::class,
-            output: self::class,
-            processor: CustomerProcessor::class,
-            denormalizationContext: [
-                'allow_extra_attributes' => true,
-                'groups'                 => ['mutation'],
-            ],
-        ),
-        new \ApiPlatform\Metadata\Delete(
-            input: self::class,
-            output: self::class,
-            processor: CustomerProcessor::class,
-            denormalizationContext: [
-                'allow_extra_attributes' => true,
-                'groups'                 => ['mutation'],
-            ],
-        ),
     ],
     graphQlOperations: [
         new Query(resolver: CustomerQueryResolver::class),

+ 265 - 3
packages/Webkul/BagistoApi/src/Models/CustomerAddress.php

@@ -3,18 +3,280 @@
 namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Delete;
 use ApiPlatform\Metadata\Get;
 use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\GraphQl\Query;
-use Webkul\Customer\Models\CustomerAddress as CustomerAddressModel;
+use ApiPlatform\Metadata\Post;
+use ApiPlatform\Metadata\Put;
+use ApiPlatform\OpenApi\Model;
+use Webkul\BagistoApi\Dto\CustomerAddressInput;
 use Webkul\BagistoApi\Resolver\BaseQueryItemResolver;
+use Webkul\BagistoApi\State\CustomerAddressProvider;
+use Webkul\BagistoApi\State\CustomerAddressTokenProcessor;
+use Webkul\Customer\Models\CustomerAddress as CustomerAddressModel;
+use ApiPlatform\OpenApi\Model\Operation;
 
 #[ApiResource(
     routePrefix: '/api/shop',
     uriTemplate: '/customer-addresses/{id}',
     operations: [
-        new Get(uriTemplate: '/customer-addresses/{id}'),
-        new GetCollection(uriTemplate: '/customer-addresses'),
+        new GetCollection(
+            uriTemplate: '/customer-addresses',
+            provider: CustomerAddressProvider::class,
+            openapi: new Operation(
+                tags: ['Customer Address'],
+                summary: 'Get all customer addresses',
+                description: 'Returns all addresses of the authenticated customer. Requires Bearer token.',
+                responses: [
+                    '200' => new Model\Response(
+                        description: 'List of customer addresses.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    [
+                                        'id'             => 5563,
+                                        'addressType'    => 'customer',
+                                        'firstName'      => 'Api',
+                                        'lastName'       => 'Addr',
+                                        'companyName'    => 'Acme Inc.',
+                                        'address'        => '123 Main St',
+                                        'city'           => 'New York',
+                                        'state'          => 'NY',
+                                        'country'        => 'US',
+                                        'postcode'       => '10001',
+                                        'email'          => 'api@example.com',
+                                        'phone'          => '1234567890',
+                                        'vatId'          => 'GB123456789',
+                                        'defaultAddress' => true,
+                                        'useForShipping' => false,
+                                        'createdAt'      => '2026-07-02T11:16:28+05:30',
+                                        'updatedAt'      => '2026-07-02T11:16:28+05:30',
+                                        'name'           => 'Api Addr',
+                                    ],
+                                ],
+                            ],
+                        ]),
+                    ),
+                ],
+                parameters: [
+                    new Model\Parameter(
+                        name: 'sort',
+                        in: 'query',
+                        description: 'Column to sort by: `id` (default) or `created_at`. Compound form also accepted, e.g. `created_at-desc`.',
+                        required: false,
+                        schema: ['type' => 'string', 'enum' => ['id', 'created_at', 'id-asc', 'id-desc', 'created_at-asc', 'created_at-desc']],
+                    ),
+                    new Model\Parameter(
+                        name: 'order',
+                        in: 'query',
+                        description: 'Sort direction: `asc` (default) or `desc`. Use `desc` to show the most recently added addresses first.',
+                        required: false,
+                        schema: ['type' => 'string', 'enum' => ['asc', 'desc']],
+                    ),
+                ],
+            ),
+        ),
+        new Get(
+            uriTemplate: '/customer-addresses/{id}',
+            openapi: new Operation(
+                tags: ['Customer Address'],
+                summary: 'Get a customer address',
+                description: 'Returns a specific address by ID. Requires Bearer token.',
+                responses: [
+                    '200' => new Model\Response(
+                        description: 'A customer address.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    'id'             => 5563,
+                                    'addressType'    => 'customer',
+                                    'firstName'      => 'Api',
+                                    'lastName'       => 'Addr',
+                                    'companyName'    => 'Acme Inc.',
+                                    'address'        => '123 Main St',
+                                    'city'           => 'New York',
+                                    'state'          => 'NY',
+                                    'country'        => 'US',
+                                    'postcode'       => '10001',
+                                    'email'          => 'api@example.com',
+                                    'phone'          => '1234567890',
+                                    'vatId'          => 'GB123456789',
+                                    'defaultAddress' => true,
+                                    'useForShipping' => false,
+                                    'createdAt'      => '2026-07-02T11:16:28+05:30',
+                                    'updatedAt'      => '2026-07-02T11:16:28+05:30',
+                                    'name'           => 'Api Addr',
+                                ],
+                            ],
+                        ]),
+                    ),
+                ],
+            ),
+        ),
+        new Post(
+            uriTemplate: '/customer-addresses',
+            input: CustomerAddressInput::class,
+            processor: CustomerAddressTokenProcessor::class,
+            denormalizationContext: [
+                'allow_extra_attributes' => true,
+                'groups'                 => ['mutation'],
+            ],
+            openapi: new Operation(
+                tags: ['Customer Address'],
+                summary: 'Create a customer address',
+                description: 'Create a new address for the authenticated customer. Requires Bearer token.',
+                requestBody: new Model\RequestBody(
+                    description: 'Address data',
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'required'   => ['firstName', 'lastName', 'email', 'phone', 'address1', 'city', 'state', 'postcode', 'country'],
+                                'properties' => [
+                                    'firstName'      => ['type' => 'string', 'example' => 'John'],
+                                    'lastName'       => ['type' => 'string', 'example' => 'Doe'],
+                                    'companyName'    => ['type' => 'string', 'example' => 'Acme Inc.'],
+                                    'vatId'          => ['type' => 'string', 'example' => 'GB123456789'],
+                                    'email'          => ['type' => 'string', 'format' => 'email', 'example' => 'john@example.com'],
+                                    'phone'          => ['type' => 'string', 'example' => '1234567890'],
+                                    'address1'       => ['type' => 'string', 'example' => '123 Main St'],
+                                    'address2'       => ['type' => 'string', 'example' => 'Apt 4B'],
+                                    'city'           => ['type' => 'string', 'example' => 'New York'],
+                                    'state'          => ['type' => 'string', 'example' => 'NY'],
+                                    'postcode'       => ['type' => 'string', 'example' => '10001'],
+                                    'country'        => ['type' => 'string', 'example' => 'US'],
+                                    'defaultAddress' => ['type' => 'boolean', 'example' => true, 'description' => 'Set as default address'],
+                                ],
+                            ],
+                            'example' => [
+                                'company_name'    => 'Acme Inc.',
+                                'first_name'      => 'Api',
+                                'last_name'       => 'Addr',
+                                'vat_id'          => 'GB123456789',
+                                'address'         => '123 Main St',
+                                'city'            => 'New York',
+                                'state'           => 'NY',
+                                'country'         => 'US',
+                                'postcode'        => '10001',
+                                'phone'           => '1234567890',
+                                'email'           => 'api@example.com',
+                                'default_address' => true,
+                            ],
+                        ],
+                    ]),
+                ),
+                responses: [
+                    '201' => new Model\Response(
+                        description: 'Address created.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    'id'             => 5563,
+                                    'addressId'      => 5563,
+                                    'firstName'      => 'Api',
+                                    'lastName'       => 'Addr',
+                                    'companyName'    => 'Acme Inc.',
+                                    'vatId'          => 'GB123456789',
+                                    'email'          => 'api@example.com',
+                                    'phone'          => '1234567890',
+                                    'address1'       => null,
+                                    'address2'       => null,
+                                    'country'        => 'US',
+                                    'state'          => 'NY',
+                                    'city'           => 'New York',
+                                    'postcode'       => '10001',
+                                    'defaultAddress' => true,
+                                ],
+                            ],
+                        ]),
+                    ),
+                ],
+            ),
+        ),
+        new Put(
+            uriTemplate: '/customer-addresses/{id}',
+            input: CustomerAddressInput::class,
+            processor: CustomerAddressTokenProcessor::class,
+            denormalizationContext: [
+                'allow_extra_attributes' => true,
+                'groups'                 => ['mutation'],
+            ],
+            openapi: new Operation(
+                tags: ['Customer Address'],
+                summary: 'Update a customer address',
+                description: 'Update an existing address. Requires Bearer token. Include addressId in the body.',
+                requestBody: new Model\RequestBody(
+                    description: 'Address fields to update',
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'properties' => [
+                                    'firstName'      => ['type' => 'string', 'example' => 'John'],
+                                    'lastName'       => ['type' => 'string', 'example' => 'Doe'],
+                                    'companyName'    => ['type' => 'string', 'example' => 'Acme Inc.'],
+                                    'vatId'          => ['type' => 'string', 'example' => 'GB123456789'],
+                                    'email'          => ['type' => 'string', 'format' => 'email', 'example' => 'john@example.com'],
+                                    'phone'          => ['type' => 'string', 'example' => '1234567890'],
+                                    'address1'       => ['type' => 'string', 'example' => '456 Oak Ave'],
+                                    'address2'       => ['type' => 'string', 'example' => 'Suite 100'],
+                                    'city'           => ['type' => 'string', 'example' => 'Los Angeles'],
+                                    'state'          => ['type' => 'string', 'example' => 'CA'],
+                                    'postcode'       => ['type' => 'string', 'example' => '90001'],
+                                    'country'        => ['type' => 'string', 'example' => 'US'],
+                                    'defaultAddress' => ['type' => 'boolean', 'example' => false, 'description' => 'Set as default address'],
+                                ],
+                            ],
+                            'example' => [
+                                'city'  => 'Boston',
+                                'phone' => '5551112222',
+                            ],
+                        ],
+                    ]),
+                ),
+                responses: [
+                    '200' => new Model\Response(
+                        description: 'Address updated.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    'id'             => 5563,
+                                    'addressId'      => 5563,
+                                    'firstName'      => 'Api',
+                                    'lastName'       => 'Addr',
+                                    'companyName'    => 'Acme Inc.',
+                                    'vatId'          => 'GB123456789',
+                                    'email'          => 'api@example.com',
+                                    'phone'          => '5551112222',
+                                    'address1'       => null,
+                                    'address2'       => null,
+                                    'country'        => 'US',
+                                    'state'          => 'NY',
+                                    'city'           => 'Boston',
+                                    'postcode'       => '10001',
+                                    'defaultAddress' => true,
+                                ],
+                            ],
+                        ]),
+                    ),
+                ],
+            ),
+        ),
+        new Delete(
+            uriTemplate: '/customer-addresses/{id}',
+            processor: CustomerAddressTokenProcessor::class,
+            openapi: new Operation(
+                tags: ['Customer Address'],
+                summary: 'Delete a customer address',
+                description: 'Delete an address by ID. Requires Bearer token.',
+                responses: [
+                    '204' => new Model\Response(description: 'Address deleted.'),
+                ],
+            ),
+        ),
     ],
     graphQlOperations: [
         new Query(resolver: BaseQueryItemResolver::class),

+ 1 - 6
packages/Webkul/BagistoApi/src/Models/CustomerAddressAddUpdate.php

@@ -3,8 +3,6 @@
 namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiResource;
-use ApiPlatform\Metadata\Get;
-use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\GraphQl\Mutation;
 use Webkul\BagistoApi\Dto\CustomerAddressInput;
 use Webkul\BagistoApi\State\CustomerAddressTokenProcessor;
@@ -14,10 +12,7 @@ use Webkul\BagistoApi\State\CustomerAddressTokenProcessor;
     shortName: 'AddUpdateCustomerAddress',
     class: CustomerAddressInput::class,
     uriTemplate: '/customer-address-add-updates',
-    operations: [
-        new Get(uriTemplate: '/customer-address-add-updates/{id}'),
-        new GetCollection(uriTemplate: '/customer-address-add-updates'),
-    ],
+    operations: [],
     graphQlOperations: [
         new Mutation(
             name: 'create',

+ 1 - 4
packages/Webkul/BagistoApi/src/Models/CustomerAddressDelete.php

@@ -3,7 +3,6 @@
 namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiResource;
-use ApiPlatform\Metadata\Delete;
 use ApiPlatform\Metadata\GraphQl\Mutation;
 use Webkul\BagistoApi\Dto\CustomerAddressInput;
 use Webkul\BagistoApi\State\CustomerAddressTokenProcessor;
@@ -13,9 +12,7 @@ use Webkul\BagistoApi\State\CustomerAddressTokenProcessor;
     shortName: 'DeleteCustomerAddress',
     class: CustomerAddressInput::class,
     uriTemplate: '/customer-address-deletes',
-    operations: [
-        new Delete(uriTemplate: '/customer-address-deletes/{id}'),
-    ],
+    operations: [],
     graphQlOperations: [
         new Mutation(
             name: 'create',

+ 1 - 6
packages/Webkul/BagistoApi/src/Models/CustomerAddressGet.php

@@ -3,8 +3,6 @@
 namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiResource;
-use ApiPlatform\Metadata\Get;
-use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\GraphQl\Query;
 use Webkul\BagistoApi\Dto\CustomerAddressInput;
 use Webkul\BagistoApi\State\CustomerAddressTokenProcessor;
@@ -14,10 +12,7 @@ use Webkul\BagistoApi\State\CustomerAddressTokenProcessor;
     shortName: 'GetCustomerAddress',
     class: CustomerAddressInput::class,
     uriTemplate: '/customer-address-gets',
-    operations: [
-        new Get(uriTemplate: '/customer-address-gets/{id}'),
-        new GetCollection(uriTemplate: '/customer-address-gets'),
-    ],
+    operations: [],
     graphQlOperations: [
         new Query(
             name: 'read',

+ 5 - 4
packages/Webkul/BagistoApi/src/Models/CustomerAddressGetAll.php

@@ -3,7 +3,6 @@
 namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiResource;
-use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\GraphQl\QueryCollection;
 use Webkul\BagistoApi\State\CustomerAddressProvider;
 use Webkul\Customer\Models\CustomerAddress as CustomerAddressModel;
@@ -15,14 +14,16 @@ use Webkul\Customer\Models\CustomerAddress as CustomerAddressModel;
     routePrefix: '/api/shop',
     shortName: 'GetCustomerAddresses',
     uriTemplate: '/customer-addresses',
-    operations: [
-        new GetCollection(provider: CustomerAddressProvider::class),
-    ],
+    operations: [],
     graphQlOperations: [
         new QueryCollection(
             provider: CustomerAddressProvider::class,
             paginationPartial: false,
             paginationType: 'cursor',
+            extraArgs: [
+                'sort'  => ['type' => 'String'],
+                'order' => ['type' => 'String'],
+            ],
         ),
     ]
 )]

+ 22 - 0
packages/Webkul/BagistoApi/src/Models/CustomerInvoice.php

@@ -14,6 +14,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
 use Webkul\BagistoApi\Resolver\BaseQueryItemResolver;
 use Webkul\BagistoApi\State\CustomerInvoiceProvider;
 use Webkul\Sales\Models\Order;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Customer Invoice API Resource
@@ -30,6 +31,27 @@ use Webkul\Sales\Models\Order;
         new GetCollection(
             uriTemplate: '/customer-invoices',
             provider: CustomerInvoiceProvider::class,
+            openapi: new Operation(
+                tags: ['Customer Order'],
+                summary: 'List invoices for the authenticated customer',
+                description: 'Mirrors the GraphQL `customerInvoices(orderId:)` query. Use `?order_id=N` to scope to a specific order, `?state=paid` (or pending, pending_payment, overdue, refunded) to filter by invoice state.',
+                parameters: [
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'order_id',
+                        in: 'query',
+                        description: 'Return only invoices for this order ID. Accepts `orderId` as an alias.',
+                        required: false,
+                        schema: ['type' => 'integer', 'example' => 590],
+                    ),
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'state',
+                        in: 'query',
+                        description: 'Filter by invoice state (pending, pending_payment, paid, overdue, refunded).',
+                        required: false,
+                        schema: ['type' => 'string'],
+                    ),
+                ],
+            ),
         ),
         new Get(
             uriTemplate: '/customer-invoices/{id}',

+ 44 - 0
packages/Webkul/BagistoApi/src/Models/CustomerLogin.php

@@ -7,6 +7,7 @@ use ApiPlatform\Metadata\ApiResource;
 use ApiPlatform\Metadata\GraphQl\Mutation;
 use Webkul\BagistoApi\Dto\LoginInput;
 use Webkul\BagistoApi\State\LoginProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 #[ApiResource(
     routePrefix: '/api/shop',
@@ -22,6 +23,49 @@ use Webkul\BagistoApi\State\LoginProcessor;
                 'allow_extra_attributes' => true,
                 'groups'                 => ['mutation'],
             ],
+            openapi: new Operation(
+                tags: ['Customer'],
+                summary: 'Customer login',
+                description: 'Authenticate a customer with email and password. Returns a Bearer token for subsequent API calls.',
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    description: 'Customer login credentials',
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'required'   => ['email', 'password'],
+                                'properties' => [
+                                    'email'    => ['type' => 'string', 'format' => 'email', 'example' => 'john@example.com'],
+                                    'password' => ['type' => 'string', 'format' => 'password', 'example' => 'Password123!'],
+                                ],
+                            ],
+                            'example' => [
+                                'email'    => 'john@example.com',
+                                'password' => 'Password123!',
+                            ],
+                        ],
+                    ]),
+                ),
+                responses: [
+                    '201' => new \ApiPlatform\OpenApi\Model\Response(
+                        description: 'Authenticated. Use `token` as the Bearer token in the Authorization header for subsequent calls.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    'id'       => 1529,
+                                    '_id'      => 1529,
+                                    'apiToken' => 'PPKI0VNiX5qehio2q1cMeMd6TCaIKofwQ4mb7y2zcy0japYwsyc',
+                                    'token'    => '3762|wT7yGIo82N7T3r2YFc3iEcugycFjPQgG338DKnPXeaa96bad',
+                                    'success'  => true,
+                                    'message'  => 'You have logged in successfully',
+                                ],
+                            ],
+                        ]),
+                    ),
+                    '401' => new \ApiPlatform\OpenApi\Model\Response(description: 'Invalid email or password.'),
+                ],
+            ),
         ),
     ],
     graphQlOperations: [

+ 49 - 2
packages/Webkul/BagistoApi/src/Models/CustomerLogout.php

@@ -5,14 +5,61 @@ namespace Webkul\BagistoApi\Models;
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
 use ApiPlatform\Metadata\GraphQl\Mutation;
+use ApiPlatform\Metadata\Post;
+use ApiPlatform\OpenApi\Model;
 use Symfony\Component\Serializer\Annotation\Groups;
 use Webkul\BagistoApi\Dto\LogoutInput;
 use Webkul\BagistoApi\State\LogoutProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 #[ApiResource(
     routePrefix: '/api/shop',
     shortName: 'Logout',
-    operations: [],
+    operations: [
+        new Post(
+            uriTemplate: '/customer/logout',
+            input: LogoutInput::class,
+            output: CustomerLogout::class,
+            processor: LogoutProcessor::class,
+            denormalizationContext: [
+                'allow_extra_attributes' => true,
+                'groups'                 => ['mutation'],
+            ],
+            normalizationContext: [
+                'groups' => ['mutation'],
+            ],
+            openapi: new Operation(
+                tags: ['Customer'],
+                summary: 'Customer logout',
+                description: 'Logout the authenticated customer and revoke the Bearer token.',
+                requestBody: new Model\RequestBody(
+                    description: 'Empty body',
+                    required: false,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema'  => [
+                                'type' => 'object',
+                            ],
+                            'example' => new \ArrayObject,
+                        ],
+                    ]),
+                ),
+                responses: [
+                    '201' => new Model\Response(
+                        description: 'Logged out successfully',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    'success' => true,
+                                    'message' => 'Logged out successfully',
+                                ],
+                            ],
+                        ]),
+                    ),
+                ],
+            ),
+        ),
+    ],
     graphQlOperations: [
         new Mutation(
             name: 'create',
@@ -24,7 +71,7 @@ use Webkul\BagistoApi\State\LogoutProcessor;
             ],
             denormalizationContext: [
                 'allow_extra_attributes' => true,
-                'groups' => ['mutation'],
+                'groups'                 => ['mutation'],
             ],
         ),
     ]

+ 7 - 0
packages/Webkul/BagistoApi/src/Models/CustomerOrder.php

@@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Relations\HasMany;
 use Illuminate\Database\Eloquent\Relations\HasOne;
 use Webkul\BagistoApi\State\CustomerOrderProvider;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Customer Order API Resource
@@ -31,10 +32,16 @@ use Webkul\BagistoApi\State\CustomerOrderProvider;
         new GetCollection(
             uriTemplate: '/customer-orders',
             provider: CustomerOrderProvider::class,
+            output: \Webkul\BagistoApi\Dto\CustomerOrder\CustomerOrderListDto::class,
+            normalizationContext: ['skip_null_values' => false],
+            openapi: new Operation(tags: ['Customer Order']),
         ),
         new Get(
             uriTemplate: '/customer-orders/{id}',
             provider: CustomerOrderProvider::class,
+            output: \Webkul\BagistoApi\Dto\CustomerOrder\CustomerOrderDetailDto::class,
+            normalizationContext: ['skip_null_values' => false],
+            openapi: new Operation(tags: ['Customer Order']),
         ),
     ],
     graphQlOperations: [

+ 22 - 0
packages/Webkul/BagistoApi/src/Models/CustomerOrderShipment.php

@@ -16,6 +16,7 @@ use Webkul\BagistoApi\State\CustomerOrderShipmentProvider;
 use Webkul\Sales\Models\Order;
 use Webkul\Sales\Models\OrderAddress;
 use Webkul\BagistoApi\Models\CustomerOrderAddress;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Customer Order Shipment API Resource
@@ -38,6 +39,27 @@ use Webkul\BagistoApi\Models\CustomerOrderAddress;
         new GetCollection(
             uriTemplate: '/customer-order-shipments',
             provider: CustomerOrderShipmentProvider::class,
+            openapi: new Operation(
+                tags: ['Customer Order'],
+                summary: 'List shipments for the authenticated customer',
+                description: 'Mirrors the GraphQL `customerOrderShipments(orderId:)` query. Use `?order_id=N` to scope to a specific order, `?status=X` to filter by shipment status.',
+                parameters: [
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'order_id',
+                        in: 'query',
+                        description: 'Return only shipments for this order ID. Accepts `orderId` as an alias.',
+                        required: false,
+                        schema: ['type' => 'integer', 'example' => 3],
+                    ),
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'status',
+                        in: 'query',
+                        description: 'Filter by shipment status (pending, shipped, canceled).',
+                        required: false,
+                        schema: ['type' => 'string'],
+                    ),
+                ],
+            ),
         ),
         new Get(
             uriTemplate: '/customer-order-shipments/{id}',

+ 13 - 2
packages/Webkul/BagistoApi/src/Models/CustomerOrderShipmentItem.php

@@ -10,6 +10,8 @@ use ApiPlatform\Metadata\GraphQl\Query;
 use ApiPlatform\Metadata\GraphQl\QueryCollection;
 use Illuminate\Database\Eloquent\Model;
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Webkul\BagistoApi\State\CustomerOrderShipmentItemProvider;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Customer Order Shipment Item API Resource
@@ -18,10 +20,19 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
  * Contains product details and quantity shipped.
  */
 #[ApiResource(
+    routePrefix: '/api/shop',
     shortName: 'CustomerOrderShipmentItem',
     operations: [
-        new Get,
-        new GetCollection,
+        new Get(
+            uriTemplate: '/customer-order-shipment-items/{id}',
+            provider: CustomerOrderShipmentItemProvider::class,
+            openapi: new Operation(tags: ['Customer Order']),
+        ),
+        new GetCollection(
+            uriTemplate: '/customer-order-shipment-items',
+            provider: CustomerOrderShipmentItemProvider::class,
+            openapi: new Operation(tags: ['Customer Order']),
+        ),
     ],
     graphQlOperations: [
         new Query,

+ 44 - 3
packages/Webkul/BagistoApi/src/Models/CustomerProfile.php

@@ -4,11 +4,11 @@ namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiProperty;
 use ApiPlatform\Metadata\ApiResource;
-use ApiPlatform\Metadata\Get;
 use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\GraphQl\Query;
 use Symfony\Component\Serializer\Annotation\Groups;
 use Webkul\BagistoApi\Resolver\CustomerQueryResolver;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Authenticated customer profile read operation
@@ -17,9 +17,50 @@ use Webkul\BagistoApi\Resolver\CustomerQueryResolver;
     routePrefix: '/api/shop',
     uriTemplate: '/customer-profiles',
     shortName: 'CustomerProfile',
+    paginationEnabled: false,
     operations: [
-        new Get(uriTemplate: '/customer-profiles/{id}'),
-        new GetCollection(uriTemplate: '/customer-profiles'),
+        new GetCollection(
+            uriTemplate: '/customer-profile',
+            provider: \Webkul\BagistoApi\State\CustomerProfileCollectionProvider::class,
+            paginationEnabled: false,
+            normalizationContext: [
+                'skip_null_values' => false,
+            ],
+            openapi: new Operation(
+                tags: ['Customer'],
+                summary: 'Get authenticated customer profile',
+                description: 'Returns the profile of the currently authenticated customer. Requires Bearer token via the Authorize button.',
+                responses: [
+                    '200' => new \ApiPlatform\OpenApi\Model\Response(
+                        description: 'Authenticated customer profile',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    [
+                                        'id'                      => '1529',
+                                        'firstName'               => 'Api',
+                                        'lastName'                => 'Doc',
+                                        'email'                   => 'john@example.com',
+                                        'phone'                   => null,
+                                        'gender'                  => null,
+                                        'dateOfBirth'             => null,
+                                        'status'                  => '1',
+                                        'subscribedToNewsLetter'  => false,
+                                        'isVerified'              => '0',
+                                        'isSuspended'             => '0',
+                                        'image'                   => null,
+                                        'password'                => null,
+                                        'confirmPassword'         => null,
+                                        'success'                 => null,
+                                        'message'                 => null,
+                                    ],
+                                ],
+                            ],
+                        ]),
+                    ),
+                ],
+            ),
+        ),
     ],
     graphQlOperations: [
         new Query(

+ 37 - 1
packages/Webkul/BagistoApi/src/Models/CustomerProfileDelete.php

@@ -8,6 +8,7 @@ use ApiPlatform\Metadata\GraphQl\Mutation;
 use ApiPlatform\Metadata\Post;
 use Webkul\BagistoApi\Dto\CustomerProfileInput;
 use Webkul\BagistoApi\State\CustomerProfileProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Customer profile delete resource
@@ -18,7 +19,42 @@ use Webkul\BagistoApi\State\CustomerProfileProcessor;
     shortName: 'CustomerProfileDelete',
     uriTemplate: '/customer-profile-deletes',
     operations: [
-        new Post(uriTemplate: '/customer-profile-deletes/{id}'),
+        new Post(
+            uriTemplate: '/customer-profile-deletes/{id}',
+            openapi: new Operation(
+                tags: ['Customer'],
+                summary: 'Delete customer profile',
+                description: 'Delete the authenticated customer\'s account. Requires Bearer token.',
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    description: 'Confirm deletion with the account password.',
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema'  => [
+                                'type'       => 'object',
+                                'required'   => ['password'],
+                                'properties' => [
+                                    'password' => ['type' => 'string', 'format' => 'password', 'example' => 'Password123!'],
+                                ],
+                            ],
+                            'example' => [
+                                'password' => 'Password123!',
+                            ],
+                        ],
+                    ]),
+                ),
+                responses: [
+                    '201' => new \ApiPlatform\OpenApi\Model\Response(
+                        description: 'Customer account deleted',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [],
+                            ],
+                        ]),
+                    ),
+                ],
+            ),
+        ),
     ],
     graphQlOperations: [
         new Mutation(

+ 66 - 1
packages/Webkul/BagistoApi/src/Models/CustomerProfileUpdate.php

@@ -10,6 +10,7 @@ use Symfony\Component\Serializer\Annotation\Groups;
 use Webkul\BagistoApi\Dto\CustomerProfileInput;
 use Webkul\BagistoApi\Dto\CustomerProfileOutput;
 use Webkul\BagistoApi\State\CustomerProfileProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Customer profile update resource
@@ -20,7 +21,66 @@ use Webkul\BagistoApi\State\CustomerProfileProcessor;
     shortName: 'CustomerProfileUpdate',
     uriTemplate: '/customer-profile-updates',
     operations: [
-        new Put(uriTemplate: '/customer-profile-updates/{id}'),
+        new Put(
+            uriTemplate: '/customer-profile-updates/{id}',
+            input: CustomerProfileInput::class,
+            output: CustomerProfileOutput::class,
+            provider: \Webkul\BagistoApi\State\AuthenticatedCustomerProvider::class,
+            processor: CustomerProfileProcessor::class,
+            denormalizationContext: [
+                'allow_extra_attributes' => true,
+                'groups'                 => ['mutation'],
+            ],
+            normalizationContext: [
+                'groups' => ['mutation'],
+            ],
+            openapi: new Operation(
+                tags: ['Customer'],
+                summary: 'Update customer profile',
+                description: 'Update the authenticated customer\'s profile. Requires Bearer token. Fields isVerified, isSuspended, and status cannot be changed by the customer.',
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    description: 'Customer profile fields to update. Only include the fields you want to change.',
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'properties' => [
+                                    'firstName'              => ['type' => 'string', 'example' => 'John'],
+                                    'lastName'               => ['type' => 'string', 'example' => 'Doe'],
+                                    'email'                  => ['type' => 'string', 'format' => 'email', 'example' => 'john@example.com'],
+                                    'phone'                  => ['type' => 'string', 'example' => '1234567890'],
+                                    'gender'                 => ['type' => 'string', 'enum' => ['Male', 'Female', 'Other'], 'example' => 'Male'],
+                                    'dateOfBirth'            => ['type' => 'string', 'format' => 'date', 'example' => '1990-01-15'],
+                                    'currentPassword'        => ['type' => 'string', 'format' => 'password', 'example' => 'OldPassword123!', 'description' => 'Current password — required only when changing password'],
+                                    'password'               => ['type' => 'string', 'format' => 'password', 'example' => 'NewPassword123!', 'description' => 'New password (requires currentPassword and confirmPassword)'],
+                                    'confirmPassword'        => ['type' => 'string', 'format' => 'password', 'example' => 'NewPassword123!', 'description' => 'Must match password'],
+                                    'subscribedToNewsLetter' => ['type' => 'boolean', 'example' => true],
+                                    'image'                  => ['type' => 'string', 'example' => 'data:image/jpeg;base64,...', 'description' => 'Profile image as base64 encoded string'],
+                                    'deleteImage'            => ['type' => 'boolean', 'example' => false, 'description' => 'Set true to remove existing profile image'],
+                                ],
+                            ],
+                            'examples' => [
+                                'update_full_profile' => [
+                                    'summary' => 'Update full profile',
+                                    'value'   => [
+                                        'firstName'              => 'John',
+                                        'lastName'               => 'Doe',
+                                        'email'                  => 'john@example.com',
+                                        'phone'                  => '1234567890',
+                                        'gender'                 => 'Male',
+                                        'dateOfBirth'            => '1990-01-15',
+                                        'subscribedToNewsLetter' => true,
+                                        'password'               => 'NewPassword123!',
+                                        'confirmPassword'        => 'NewPassword123!',
+                                    ],
+                                ],
+                            ],
+                        ],
+                    ]),
+                ),
+            ),
+        ),
     ],
     graphQlOperations: [
         new Mutation(
@@ -69,6 +129,11 @@ class CustomerProfileUpdate
     #[Groups(['mutation'])]
     public ?string $dateOfBirth = null;
 
+
+    #[ApiProperty(readable: true, writable: true)]
+    #[Groups(['mutation'])]
+    public ?string $currentPassword = null;
+
     #[ApiProperty(readable: true, writable: true)]
     #[Groups(['mutation'])]
     public ?string $password = null;

+ 9 - 0
packages/Webkul/BagistoApi/src/Models/CustomerReview.php

@@ -11,6 +11,7 @@ use ApiPlatform\Metadata\GraphQl\QueryCollection;
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Webkul\BagistoApi\Resolver\BaseQueryItemResolver;
 use Webkul\BagistoApi\State\CustomerReviewProvider;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Customer Review API Resource
@@ -26,10 +27,18 @@ use Webkul\BagistoApi\State\CustomerReviewProvider;
         new GetCollection(
             uriTemplate: '/customer-reviews',
             provider: CustomerReviewProvider::class,
+            openapi: new Operation(
+                tags: ['Customer Review'],
+                summary: 'List reviews submitted by the authenticated customer',
+            ),
         ),
         new Get(
             uriTemplate: '/customer-reviews/{id}',
             provider: CustomerReviewProvider::class,
+            openapi: new Operation(
+                tags: ['Customer Review'],
+                summary: 'Get a single review by ID (customer-scoped)',
+            ),
         ),
     ],
     graphQlOperations: [

+ 8 - 0
packages/Webkul/BagistoApi/src/Models/DeleteAllCompareItems.php

@@ -9,6 +9,7 @@ use ApiPlatform\Metadata\GraphQl\Mutation;
 use Symfony\Component\Serializer\Annotation\Groups;
 use Webkul\BagistoApi\Dto\DeleteAllCompareItemsInput;
 use Webkul\BagistoApi\State\DeleteAllCompareItemsProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Delete All Compare Items Response Model
@@ -27,6 +28,13 @@ use Webkul\BagistoApi\State\DeleteAllCompareItemsProcessor;
             normalizationContext: [
                 'groups' => ['mutation'],
             ],
+            openapi: new Operation(
+                tags: ['CompareItem'],
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    required: false,
+                    content: new \ArrayObject([]),
+                ),
+            ),
         ),
     ],
     graphQlOperations: [

+ 2 - 0
packages/Webkul/BagistoApi/src/Models/DeleteAllWishlists.php

@@ -9,6 +9,7 @@ use ApiPlatform\Metadata\GraphQl\Mutation;
 use Symfony\Component\Serializer\Annotation\Groups;
 use Webkul\BagistoApi\Dto\DeleteAllWishlistsInput;
 use Webkul\BagistoApi\State\DeleteAllWishlistsProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Delete All Wishlists Response Model
@@ -27,6 +28,7 @@ use Webkul\BagistoApi\State\DeleteAllWishlistsProcessor;
             normalizationContext: [
                 'groups' => ['mutation'],
             ],
+            openapi: new Operation(tags: ['Wishlist']),
         ),
     ],
     graphQlOperations: [

+ 0 - 3
packages/Webkul/BagistoApi/src/Models/Filter/Attribute.php

@@ -11,9 +11,6 @@ use Webkul\BagistoApi\State\FilterableAttributesProvider;
     description: 'Attribute Filters for the category or product grid page',
     routePrefix: '/api/shop',
     operations: [
-        new \ApiPlatform\Metadata\GetCollection(
-            uriTemplate: '/filters/attributes'
-        ),
     ],
     graphQlOperations: [
         new QueryCollection(

+ 32 - 1
packages/Webkul/BagistoApi/src/Models/ForgotPassword.php

@@ -7,11 +7,42 @@ use ApiPlatform\Metadata\ApiResource;
 use ApiPlatform\Metadata\GraphQl\Mutation;
 use Webkul\BagistoApi\Dto\ForgotPasswordInput;
 use Webkul\BagistoApi\State\ForgotPasswordProcessor;
+use ApiPlatform\Metadata\Post;
+use ApiPlatform\OpenApi\Model\Operation;
 
 #[ApiResource(
     routePrefix: '/api/shop',
     shortName: 'ForgotPassword',
-    operations: [],
+    operations: [
+        new Post(
+            uriTemplate: '/forgot-passwords',
+            processor: ForgotPasswordProcessor::class,
+            normalizationContext: ['skip_null_values' => false],
+            denormalizationContext: [
+                'allow_extra_attributes' => true,
+                'groups'                 => ['mutation'],
+            ],
+            openapi: new Operation(
+                tags: ['Customer'],
+                summary: 'Request password reset link',
+                description: 'Sends a password reset email to the given customer email if the account exists.',
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'     => 'object',
+                                'required' => ['email'],
+                                'properties' => [
+                                    'email' => ['type' => 'string', 'example' => 'customer@example.com'],
+                                ],
+                            ],
+                        ],
+                    ]),
+                ),
+            ),
+        ),
+    ],
     graphQlOperations: [
         new Mutation(
             name: 'create',

+ 264 - 0
packages/Webkul/BagistoApi/src/Models/GdprRequest.php

@@ -0,0 +1,264 @@
+<?php
+
+namespace Webkul\BagistoApi\Models;
+
+use ApiPlatform\Metadata\ApiProperty;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Delete;
+use ApiPlatform\Metadata\Get;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\GraphQl\Mutation;
+use ApiPlatform\Metadata\GraphQl\Query;
+use ApiPlatform\Metadata\GraphQl\QueryCollection;
+use ApiPlatform\Metadata\Post;
+use Webkul\BagistoApi\Dto\CreateGdprRequestInput;
+use Webkul\BagistoApi\Dto\DeleteGdprRequestInput;
+use Webkul\BagistoApi\Dto\RevokeGdprRequestInput;
+use Webkul\BagistoApi\Resolver\GdprRequestQueryResolver;
+use Webkul\BagistoApi\State\GdprRequestItemProvider;
+use Webkul\BagistoApi\State\GdprRequestProcessor;
+use Webkul\BagistoApi\State\GdprRequestProvider;
+use ApiPlatform\OpenApi\Model\Operation;
+
+#[ApiResource(
+    shortName: 'GdprRequest',
+    routePrefix: '/api/shop',
+    normalizationContext: ['skip_null_values' => false],
+    operations: [
+        new Get(
+            uriTemplate: '/gdpr-requests/{id}',
+            provider: GdprRequestItemProvider::class,
+            openapi: new Operation(
+                tags: ['GDPR Requests'],
+                summary: 'Get one of the customer\'s own GDPR data requests',
+                description: 'Returns a GDPR data request owned by the authenticated customer. Returns 404 if it is not theirs. Requires GDPR to be enabled in admin config.',
+                responses: [
+                    '200' => new \ApiPlatform\OpenApi\Model\Response(
+                        description: 'The GDPR data request.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    'id'             => 35,
+                                    'email'          => 'jane@example.com',
+                                    'status'         => 'pending',
+                                    'type'           => 'delete',
+                                    'message'        => 'Please delete my personal data.',
+                                    'revokedAt'      => null,
+                                    'createdAt'      => '2026-07-02T12:01:36+05:30',
+                                    'updatedAt'      => '2026-07-02T12:01:36+05:30',
+                                    'successMessage' => null,
+                                    'customer'       => '/api/shop/customers/1534',
+                                ],
+                            ],
+                        ]),
+                    ),
+                    '404' => new \ApiPlatform\OpenApi\Model\Response(description: 'Request not found or not owned by the caller.'),
+                ],
+            ),
+        ),
+        new GetCollection(
+            uriTemplate: '/gdpr-requests',
+            provider: GdprRequestProvider::class,
+            openapi: new Operation(
+                tags: ['GDPR Requests'],
+                summary: 'List the customer\'s own GDPR data requests',
+                description: 'Returns every GDPR data request raised by the authenticated customer. Requires GDPR to be enabled in admin config.',
+                responses: [
+                    '200' => new \ApiPlatform\OpenApi\Model\Response(
+                        description: 'List of the customer\'s GDPR data requests.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    [
+                                        'id'             => 35,
+                                        'email'          => 'jane@example.com',
+                                        'status'         => 'pending',
+                                        'type'           => 'delete',
+                                        'message'        => 'Please delete my personal data.',
+                                        'revokedAt'      => null,
+                                        'createdAt'      => '2026-07-02T12:01:36+05:30',
+                                        'updatedAt'      => '2026-07-02T12:01:36+05:30',
+                                        'successMessage' => null,
+                                        'customer'       => '/api/shop/customers/1534',
+                                    ],
+                                ],
+                            ],
+                        ]),
+                    ),
+                ],
+                parameters: [
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'sort',
+                        in: 'query',
+                        description: 'Column to sort by: `id` (default) or `created_at`.',
+                        required: false,
+                        schema: ['type' => 'string', 'enum' => ['id', 'created_at', 'id-asc', 'id-desc', 'created_at-asc', 'created_at-desc']],
+                    ),
+                    new \ApiPlatform\OpenApi\Model\Parameter(
+                        name: 'order',
+                        in: 'query',
+                        description: 'Sort direction: `asc` (default) or `desc`.',
+                        required: false,
+                        schema: ['type' => 'string', 'enum' => ['asc', 'desc']],
+                    ),
+                ],
+            ),
+        ),
+        new Post(
+            uriTemplate: '/gdpr-requests',
+            processor: GdprRequestProcessor::class,
+            openapi: new Operation(
+                tags: ['GDPR Requests'],
+                summary: 'Raise a GDPR data request',
+                description: 'Raise a GDPR data request for the authenticated customer. Type must be `delete` or `update`.',
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    description: 'GDPR data request details',
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'required'   => ['type', 'message'],
+                                'properties' => [
+                                    'type'    => ['type' => 'string', 'enum' => ['delete', 'update'], 'example' => 'delete'],
+                                    'message' => ['type' => 'string', 'example' => 'Please delete all my personal data.'],
+                                ],
+                            ],
+                            'example' => [
+                                'type'    => 'delete',
+                                'message' => 'Please delete my personal data.',
+                            ],
+                        ],
+                    ]),
+                ),
+                responses: [
+                    '201' => new \ApiPlatform\OpenApi\Model\Response(
+                        description: 'Request raised. Starts in `pending` status.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    'id'             => 35,
+                                    'email'          => 'jane@example.com',
+                                    'status'         => 'pending',
+                                    'type'           => 'delete',
+                                    'message'        => 'Please delete my personal data.',
+                                    'revokedAt'      => null,
+                                    'createdAt'      => '2026-07-02T12:01:36+05:30',
+                                    'updatedAt'      => '2026-07-02T12:01:36+05:30',
+                                    'successMessage' => 'Your GDPR data request has been raised successfully.',
+                                    'customer'       => '/api/shop/customers/1534',
+                                ],
+                            ],
+                        ]),
+                    ),
+                    '400' => new \ApiPlatform\OpenApi\Model\Response(description: 'GDPR disabled in admin config, or invalid type/message.'),
+                ],
+            ),
+        ),
+        new Post(
+            name: 'revoke_post',
+            uriTemplate: '/gdpr-requests/{id}/revoke',
+            status: 200,
+            processor: GdprRequestProcessor::class,
+            openapi: new Operation(
+                tags: ['GDPR Requests'],
+                summary: 'Revoke a GDPR data request',
+                description: 'Revoke one of the customer\'s own GDPR data requests. Allowed only while the request is pending or processing. Send an empty JSON body `{}`.',
+                responses: [
+                    '200' => new \ApiPlatform\OpenApi\Model\Response(
+                        description: 'Request revoked. `status` becomes `revoked` and `revokedAt` is stamped.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    'id'             => 35,
+                                    'email'          => 'jane@example.com',
+                                    'status'         => 'revoked',
+                                    'type'           => 'delete',
+                                    'message'        => 'Please delete my personal data.',
+                                    'revokedAt'      => '2026-07-02 12:01:37',
+                                    'createdAt'      => '2026-07-02T12:01:36+05:30',
+                                    'updatedAt'      => '2026-07-02T12:01:37+05:30',
+                                    'successMessage' => null,
+                                    'customer'       => '/api/shop/customers/1534',
+                                ],
+                            ],
+                        ]),
+                    ),
+                    '422' => new \ApiPlatform\OpenApi\Model\Response(description: 'Request is not pending/processing, so it cannot be revoked.'),
+                ],
+            ),
+        ),
+        new Delete(
+            uriTemplate: '/gdpr-requests/{id}',
+            processor: GdprRequestProcessor::class,
+            openapi: new Operation(
+                tags: ['GDPR Requests'],
+                summary: 'Delete a GDPR data request',
+                description: 'Deletes one of the customer\'s own GDPR data requests. Returns 204 No Content on success.',
+                responses: [
+                    '204' => new \ApiPlatform\OpenApi\Model\Response(description: 'Request deleted. No content.'),
+                    '404' => new \ApiPlatform\OpenApi\Model\Response(description: 'Request not found or not owned by the caller.'),
+                ],
+            ),
+        ),
+    ],
+    graphQlOperations: [
+        new Query(resolver: GdprRequestQueryResolver::class),
+        new QueryCollection(
+            provider: GdprRequestProvider::class,
+            paginationType: 'cursor',
+            extraArgs: [
+                'sort'  => ['type' => 'String'],
+                'order' => ['type' => 'String'],
+            ],
+        ),
+        new Mutation(
+            name: 'create',
+            input: CreateGdprRequestInput::class,
+            output: GdprRequest::class,
+            processor: GdprRequestProcessor::class,
+        ),
+        new Mutation(
+            name: 'revoke',
+            input: RevokeGdprRequestInput::class,
+            output: GdprRequest::class,
+            processor: GdprRequestProcessor::class,
+        ),
+        new Mutation(
+            name: 'delete',
+            input: DeleteGdprRequestInput::class,
+            output: GdprRequest::class,
+            processor: GdprRequestProcessor::class,
+        ),
+    ],
+)]
+class GdprRequest extends \Webkul\GDPR\Models\GDPRDataRequest
+{
+    protected $appends = ['success_message'];
+
+    public ?string $responseMessage = null;
+
+    #[ApiProperty(identifier: true, writable: false)]
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    #[ApiProperty(writable: false, description: 'The customer who owns the request')]
+    public function customer(): \Illuminate\Database\Eloquent\Relations\BelongsTo
+    {
+        return $this->belongsTo(Customer::class, 'customer_id');
+    }
+
+    public function getSuccessMessageAttribute(): ?string
+    {
+        return $this->responseMessage;
+    }
+
+    public function setResponseMessage(string $message): self
+    {
+        $this->responseMessage = $message;
+
+        return $this;
+    }
+}

+ 15 - 1
packages/Webkul/BagistoApi/src/Models/GetCheckoutAddress.php

@@ -3,9 +3,11 @@
 namespace Webkul\BagistoApi\Models;
 
 use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\GetCollection;
 use ApiPlatform\Metadata\GraphQl\QueryCollection;
 use Webkul\BagistoApi\State\GetCheckoutAddressCollectionProvider;
 use Webkul\Checkout\Models\CartAddress;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * GetCheckoutAddress - GraphQL Query Collection for Cart Addresses
@@ -16,7 +18,19 @@ use Webkul\Checkout\Models\CartAddress;
 #[ApiResource(
     routePrefix: '/api/shop',
     shortName: 'GetCheckoutAddress',
-    operations: [],
+    operations: [
+        new GetCollection(
+            uriTemplate: '/checkout-addresses',
+            provider: GetCheckoutAddressCollectionProvider::class,
+            paginationEnabled: false,
+            normalizationContext: ['skip_null_values' => false],
+            openapi: new Operation(
+                tags: ['Checkout'],
+                summary: 'Get billing and shipping addresses for the authenticated cart',
+                description: 'Returns the CartAddress rows (billing + shipping) attached to the cart identified by the Bearer token. Mirrors the GraphQL `collectionGetCheckoutAddresses` query.',
+            ),
+        ),
+    ],
     graphQlOperations: [
         new QueryCollection(
             name: 'collection',

+ 30 - 1
packages/Webkul/BagistoApi/src/Models/MergeCart.php

@@ -11,6 +11,7 @@ use Webkul\BagistoApi\Dto\CartData;
 use Webkul\BagistoApi\Dto\CartInput;
 use Webkul\BagistoApi\State\CartTokenMutationProvider;
 use Webkul\BagistoApi\State\CartTokenProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * MergeCart - GraphQL API Resource for Merging Guest Cart to Customer Cart
@@ -37,7 +38,35 @@ use Webkul\BagistoApi\State\CartTokenProcessor;
     shortName: 'MergeCart',
     uriTemplate: '/merge-carts',
     operations: [
-        new Post(uriTemplate: '/merge-carts/{id}'),
+        new Post(
+            uriTemplate: '/merge-carts',
+            input: CartInput::class,
+            output: CartData::class,
+            processor: CartTokenProcessor::class,
+            openapi: new Operation(
+                summary: 'Merge guest cart into customer cart',
+                description: 'Merges a guest cart into the authenticated customer\'s cart. Requires a valid bearer token and the guest cart ID.',
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    description: 'Guest cart details to merge',
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'required'   => ['cart_id'],
+                                'properties' => [
+                                    'cart_id' => [
+                                        'type'        => 'integer',
+                                        'description' => 'The ID of the guest cart to merge into the customer cart',
+                                        'example'     => 1341,
+                                    ],
+                                ],
+                            ],
+                        ],
+                    ]),
+                ),
+            ),
+        ),
     ],
     graphQlOperations: [
         new Mutation(

+ 1 - 3
packages/Webkul/BagistoApi/src/Models/MoveToWishlist.php

@@ -21,9 +21,7 @@ use Webkul\BagistoApi\State\CartTokenProcessor;
     routePrefix: '/api/shop',
     shortName: 'MoveToWishlist',
     uriTemplate: '/move-to-wishlists',
-    operations: [
-        new Post(uriTemplate: '/move-to-wishlists/{id}'),
-    ],
+    operations: [],
     graphQlOperations: [
         new Mutation(
             name: 'create',

+ 63 - 0
packages/Webkul/BagistoApi/src/Models/MoveWishlistToCart.php

@@ -10,6 +10,7 @@ use Symfony\Component\Serializer\Annotation\Groups;
 use Webkul\BagistoApi\Dto\CartData;
 use Webkul\BagistoApi\Dto\MoveWishlistToCartInput;
 use Webkul\BagistoApi\State\MoveWishlistToCartProcessor;
+use ApiPlatform\OpenApi\Model\Operation;
 
 /**
  * Move Wishlist to Cart Response Model
@@ -32,6 +33,68 @@ use Webkul\BagistoApi\State\MoveWishlistToCartProcessor;
             normalizationContext: [
                 'groups' => ['mutation'],
             ],
+            openapi: new Operation(
+                tags: ['Wishlist'],
+                summary: 'Move a wishlist item to the cart',
+                description: 'Moves the given wishlist item into the authenticated customer\'s active cart and removes it from the wishlist. Returns the updated cart.',
+                requestBody: new \ApiPlatform\OpenApi\Model\RequestBody(
+                    required: true,
+                    content: new \ArrayObject([
+                        'application/json' => [
+                            'schema' => [
+                                'type'       => 'object',
+                                'required'   => ['wishlistItemId'],
+                                'properties' => [
+                                    'wishlistItemId' => ['type' => 'integer', 'example' => 202, 'description' => 'ID of the wishlist item to move'],
+                                    'quantity'       => ['type' => 'integer', 'example' => 1, 'description' => 'Quantity to add to cart (defaults to 1)'],
+                                ],
+                            ],
+                            'example' => [
+                                'wishlistItemId' => 202,
+                                'quantity'       => 1,
+                            ],
+                        ],
+                    ]),
+                ),
+                responses: [
+                    '201' => new \ApiPlatform\OpenApi\Model\Response(
+                        description: 'Item moved to cart. Returns the updated cart.',
+                        content: new \ArrayObject([
+                            'application/json' => [
+                                'example' => [
+                                    'id'                  => 6885,
+                                    'cartToken'           => '6885',
+                                    'customerId'          => 1533,
+                                    'channelId'           => 1,
+                                    'itemsCount'          => 1,
+                                    'items'               => [
+                                        [
+                                            'id'             => 7763,
+                                            'cartId'         => 6885,
+                                            'productId'      => 1,
+                                            'name'           => 'Coastal Breeze Men\'s Blue Zipper Hoodie',
+                                            'sku'            => 'COASTALBREEZEMENSHOODIE',
+                                            'quantity'       => 1,
+                                            'price'          => 100,
+                                            'total'          => 100,
+                                            'type'           => 'simple',
+                                            'formattedPrice' => '$100.00',
+                                            'formattedTotal' => '$100.00',
+                                        ],
+                                    ],
+                                    'subtotal'            => 100,
+                                    'grandTotal'          => 100,
+                                    'taxAmount'           => 0,
+                                    'discountAmount'      => 0,
+                                    'formattedSubtotal'   => '$100.00',
+                                    'formattedGrandTotal' => '$100.00',
+                                ],
+                            ],
+                        ]),
+                    ),
+                    '404' => new \ApiPlatform\OpenApi\Model\Response(description: 'Wishlist item not found or not owned by the caller.'),
+                ],
+            ),
         ),
     ],
     graphQlOperations: [

+ 0 - 0
packages/Webkul/BagistoApi/src/Models/Newsletter.php


部分文件因文件數量過多而無法顯示