Skip to content

Feat: add Cart Endpoint#201

Open
axel-paillaud wants to merge 9 commits into
PrestaShop:devfrom
axel-paillaud:feat/add_cart
Open

Feat: add Cart Endpoint#201
axel-paillaud wants to merge 9 commits into
PrestaShop:devfrom
axel-paillaud:feat/add_cart

Conversation

@axel-paillaud
Copy link
Copy Markdown

@axel-paillaud axel-paillaud commented Apr 30, 2026

Questions Answers
Description? Add Cart Domain
Type? new feature
BC breaks? no
Deprecations? no
Fixed ticket? N A
Sponsor company axel-paillaud
How to test? Get a token, add cart_write and cart_read scope, test all endpoints with tools like cURL or Bruno

All endpoints require Authorization: Bearer {token} and ?shopId=1.

Method Endpoint Body
POST /carts {"customerId": 1}
GET /carts/{cartId}
DELETE /carts/{cartId}
GET /carts/{cartId}/view
GET /carts/last-empty/{customerId}
POST /carts/{cartId}/products {"productId": 1, "quantity": 2, "combinationId": null, "customizationsByFieldIds": {}}
POST /carts/{cartId}/products (with customization) {"productId": 19, "quantity": 1, "combinationId": null, "customizationsByFieldIds": {"3": "My custom text"}}
DELETE /carts/{cartId}/products/{productId}
PATCH /carts/{cartId}/products/quantity {"productId": 1, "quantity": 3, "combinationId": null, "customizationId": null}
PATCH /carts/{cartId}/products/price {"productId": 1, "combinationId": 0, "price": 19.99}
POST /carts/{cartId}/cart-rules {"cartRuleId": 1}
DELETE /carts/{cartId}/cart-rules {"cartRuleId": 1}
POST /carts/{cartId}/customizations {"productId": 19, "customizationValuesByFieldIds": {"3": "My custom text"}}
PATCH /carts/{cartId}/addresses {"deliveryAddressId": 2, "invoiceAddressId": 2}
PATCH /carts/{cartId}/carrier {"carrierId": 2}
PATCH /carts/{cartId}/currency {"currencyId": 1}
PATCH /carts/{cartId}/language {"languageId": 1}
PATCH /carts/{cartId}/delivery-settings {"allowFreeShipping": false, "gift": false, "recycledPackaging": false, "giftMessage": null}
POST /carts/{cartId}/send-email (deprecated)
DELETE /carts/bulk-delete {"cartIds": [2, 3]}

This PR need #41364 to work properly, otherwise the customerId field is null.

Screenshot_2026-04-30_06-13-36

This doesn't prevent the endpoint from functioning correctly, but it shouldn't be null.

@axel-paillaud axel-paillaud changed the title Feat/add cart Feat: add Cart Endpoint May 11, 2026
@axel-paillaud axel-paillaud marked this pull request as ready for review May 11, 2026 04:14
@ps-jarvis
Copy link
Copy Markdown

Hello @axel-paillaud!

This is your first pull request on ps_apiresources repository of the PrestaShop project.

Thank you, and welcome to this Open Source community!

@github-actions
Copy link
Copy Markdown

🤖 Claude AI Pre-Review — Automated analysis. Does not replace human review.

📋 Summary of changes

This PR introduces a full Cart domain (11 resource classes + 1 integration test): create/get/delete a cart (Cart.php), cart-view (CartView.php), last-empty-cart lookup (LastEmptyCustomerCart.php), product management (CartProducts.php, CartProductQuantity.php, CartProductPrice.php), cart-rule management (CartCartRules.php), customization (CartCustomization.php), address/carrier/currency/language/delivery-settings updates, a deprecated send-email action (CartSendEmail.php), and a bulk-delete resource (BulkDeleteCarts.php). CQRS entities include GetCartForOrderCreation, GetCartForViewing, GetLastEmptyCustomerCart, CreateEmptyCustomerCartCommand, DeleteCartCommand, BulkDeleteCartCommand, and a dozen sub-resource commands.

⏱️ Estimated review time

30–40 minutes — 11 resource classes, 19 endpoints, several cross-cutting convention issues, Core query result alignment that needs verification.

🎯 Scope

  • Exposed operations: GET (cart, view, last-empty), POST (create, products, cart-rules, customizations, send-email), PATCH (addresses, carrier, currency, language, delivery-settings, product-quantity, product-price), DELETE (single, bulk, products, cart-rules)
  • CQRS entity: Cart domain — multiple commands/queries
  • Integration test: partial (main CRUD chain + bulk-delete + view; most sub-resource endpoints untested)
🧱 API Platform / CQRS architecture compliance

🔴 Hard blocker — CI-enforced violation

CartProductPrice.phppublic float $price violates the CI-enforced rule: "Decimals — use DecimalNumber, never float". Must be replaced with public \PrestaShop\Decimal\DecimalNumber $price (and CQRSCommandMapping updated accordingly).


🟠 Missing validationContext on ALL CQRSPartialUpdate operations

CONTEXT.md mandates validationContext: ['groups' => ['Default', 'Update']] on every CQRSPartialUpdate. All 7 PATCH resources are missing it: CartAddresses, CartCarrier, CartCurrency, CartDeliverySettings, CartLanguage, CartProductQuantity, CartProductPrice.


🟠 CartProducts.php — wrong validation group name

CQRSCreate uses validationContext: ['groups' => ['Default', 'Add']]. Convention requires ['Default', 'Create']. Constraints must also use groups: ['Create'].


🟠 Missing validationContext on CQRSCreate in 3 files

CartCartRules.php, CartCustomization.php, and CartSendEmail.php are all missing validationContext: ['groups' => ['Default', 'Create']].


🟠 Cart.php — no CQRSQueryMapping for GetCartForOrderCreation

Both CQRSGet and CQRSCreate (via CQRSQuery) use GetCartForOrderCreation but define no QUERY_MAPPING. The Core query result likely uses different field names — CartView.php already demonstrates this by mapping [cartCurrencyId] => [currencyId] for GetCartForViewing. The equivalent must be verified against the Core handler and added. Without it, all DTO fields may silently be null at runtime.


🟡 CQRSQuery missing on write operations

CONTEXT.md: "Use a CQRSQuery on CQRSCreate and CQRSPartialUpdate when the endpoint should return the full updated state." Currently CartCartRules, CartCustomization, CartProducts, and all 7 PATCH classes return only an identifier or 204. Confirm whether returning the full updated cart state is desired.


🟡 BulkDeleteCarts.php — no #[ApiProperty(identifier: true)]

Every ApiResource class needs one property marked #[ApiProperty(identifier: true)]. The canonical BulkAttributeGroups.php marks the IDs array as the identifier. $cartIds should carry the annotation.


🟡 CartDeliverySettings.php — partial command mapping

COMMAND_MAPPING maps gift => isAGift and recycledPackaging => useRecycledPackaging, but allowFreeShipping and giftMessage are absent. Verify UpdateCartDeliverySettingsCommand constructor uses exactly those names; add explicit mappings if they differ.


🟡 CartProductPrice.php / CartProductQuantity.php — no CQRSCommandMapping

Neither file defines command mappings. Verify UpdateProductPriceInCartCommand and UpdateProductQuantityInCartCommand accept parameters with the exact API field names (productId, combinationId, price, quantity, customizationId).


🟡 Multi-shop / shop context not wired

The PR description says all endpoints require ?shopId=1. However, none of the resource classes forward the shop context to CQRS commands/queries via [_context][shopId] or [_context][shopConstraint] mappings. If any Core commands/queries expect a ShopConstraint, the forwarding is missing. Compare against Product.php / CustomerGroup.php for the correct pattern.

💡 Improvement suggestions
  1. CartProducts.php:$quantity defaultpublic int $quantity = 0 with #[Assert\Positive(groups: ['Add'])] means omitting quantity silently passes 0 and fires a non-obvious error. Remove the default and add #[Assert\NotBlank(groups: ['Create'])].

  2. CartCartRules.php DELETE with body — DELETE on /carts/{cartId}/cart-rules with cartRuleId in the request body is unconventional. Consider /carts/{cartId}/cart-rules/{cartRuleId} with the ID in the URI.

  3. Monetary subfields in nested arrays$products, $summary, $shipping contain fields like unitPrice, totalProductsPrice, shippingPrice. Confirm the Core returns these as strings, not floats (they bypass the DecimalNumber CI rule since they're nested inside array).

  4. Deprecated endpoint CartSendEmail.php — The @deprecated PHPDoc is on the class but API Platform won't surface it in the OpenAPI spec. Consider adding deprecationReason: 'Deprecated since PrestaShop 9.0' to the CQRSCreate attribute.

  5. normalizationContext: ['skip_null_values' => false] on Cart.php, CartView.php, LastEmptyCustomerCart.php — Confirm this is intentional; it forces every null field to appear in responses (e.g. $shipping on a freshly-created cart).

✅ Pre-review checklist

URI & routing

  • URI is plural, lowercase, kebab-case
  • Identifier uses domain name + Id suffix — cartId, customerId
  • Sub-resources follow parent path — /carts/{cartId}/products, etc.
  • Bulk operation URI uses bulk- prefix and plural Ids parameter — /carts/bulk-delete, $cartIds

Operations & scopes

  • Correct operation attribute per HTTP method
  • Scope format: cart_read / cart_write

API Resource properties

  • All properties strictly typed, scalars/arrays only (no Value Objects) — except below
  • CartProductPrice.php: public float $price — CI-enforced hard blocker
  • #[ApiProperty(identifier: true)] on ID property — present on most classes
  • BulkDeleteCarts.php$cartIds missing #[ApiProperty(identifier: true)]
  • #[LocalizedValue] / #[DefaultLanguage] — N/A, no localised fields

CQRS mapping

  • QUERY_MAPPING direction: QueryResult field → API field — correct where present
  • CQRSCommandMapping direction: API field → Command parameter — correct where present
  • Cart.php — no CQRSQueryMapping for GetCartForOrderCreation; field alignment unverified
  • CQRSQuery absent on CQRSCreate/CQRSPartialUpdate in most sub-resource classes
  • No SerializedName — not used

Forbidden practices (CI-enforced)

  • No custom normalizers or processors
  • CartProductPrice.php: public float $priceCI will fail

Exception handling & validation

  • CartConstraintException → 422, CartNotFoundException → 404
  • Missing validationContext on ALL 7 CQRSPartialUpdate operations
  • Missing validationContext on CQRSCreate in CartCartRules, CartCustomization, CartSendEmail
  • CartProducts.php: uses 'Add' group instead of 'Create'

Multi-shop

  • Shop context ([_context][shopId]/[_context][shopConstraint]) not forwarded to any CQRS command/query despite ?shopId=1 being required per PR description

Listing field alignment — no list endpoint, N/A

Integration test

  • Extends ApiTestCase
  • @depends chain present for main CRUD flow
  • testInvalidData only covers testCreateCartInvalidData; no validation tests for PATCH/sub-resource endpoints
  • PATCH test methods do not assert response body/structure
  • testAddProductToCart asserts 'customization' => null in product structure, but Cart.php $products OpenAPI context defines no customization key — mismatch to clarify
  • getProtectedEndpoints() lists all 19 endpoint variants
  • DatabaseDump::restoreTables() covers cart, cart_product, cart_cart_rule — verify customization table is not also affected
  • declare(strict_types=1) present

@github-actions github-actions Bot added AI reviewed Status: Claude AI has already pre-reviewed this PR and removed Need AI review Trigger: Request an AI pre-review from Claude labels May 13, 2026
@kpodemski
Copy link
Copy Markdown
Contributor

Hello @axel-paillaud

Thank you for contributing! Please find the AI review above, which should help you improve the pull request.

@ps-jarvis ps-jarvis moved this from Ready for review to Waiting for author in PR Dashboard May 13, 2026
use Symfony\Component\HttpFoundation\Response;

/**
* @deprecated Since PrestaShop 9.0. Will be removed in the next major version.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you implementing a deprecated endpoint? :D

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol okay, removed

@Hlavtox
Copy link
Copy Markdown

Hlavtox commented May 13, 2026

@kpodemski Regarding the SendCartToCustomerCommand. It was replaced by SendProcessOrderEmail, but I think SendCartToCustomer is actually a better naming.

@axel-paillaud
Copy link
Copy Markdown
Author

Hello @axel-paillaud

Thank you for contributing! Please find the AI review above, which should help you improve the pull request.

Hello @kpodemski,

The first point : CartProductPrice.php: public float $price — CI-enforced hard blocker is fixed : use DecimalPrice instead of float for price.

For the second point : BulkDeleteCarts.php — $cartIds missing #[ApiProperty(identifier: true)] :

I suspect this is an error. Other bulk actions don't implement it (e.g., BulkDeleteZones), and identifier: true marks a unique instance of the resource (e.g., $cartId in GET /carts/{cartId}). For a bulk delete, there's no unique identifier; the URL is fixed (carts/bulk-delete). Do you confirm ?

@axel-paillaud
Copy link
Copy Markdown
Author

#[LocalizedValue] / #[DefaultLanguage] — N/A, no localised fields :

Confirmed: no localised fields in the Cart resources. Product names and carrier names come from existing domain objects and are not stored directly in the Cart resource properties.

@axel-paillaud
Copy link
Copy Markdown
Author

Cart.php — no CQRSQueryMapping for GetCartForOrderCreation; field alignment unverified
Field alignment verified, all CartForOrderCreation getters map directly to Cart resource properties without renaming. No CQRSQueryMapping needed.

CQRSQuery absent on CQRSCreate/CQRSPartialUpdate in most sub-resource classes
Apparently, the other "action-oriented" endpoints (often patch or bulk) also don't use CQRSQuery. Do you want to use CQRSQuery for all Cart sub-resources, and therefore return the entire cart with each response, or do we leave it as is (returning only what was submitted)?

@axel-paillaud
Copy link
Copy Markdown
Author

Missing validationContext on ALL 7 CQRSPartialUpdate operations
Missing validationContext on CQRSCreate in CartCartRules, CartCustomization, CartSendEmail

Our Cart sub-resources have no constraints with groups: ['Create'] or groups: ['Update'], so the absence of validationContext has no functional impact.

Other endpoints, such as Profile, CustomerGroup, and Title, also omit ValidationContext when validation groups are not differentiated.

@axel-paillaud
Copy link
Copy Markdown
Author

CartProducts.php: uses 'Add' group instead of 'Create'

fixed

@axel-paillaud
Copy link
Copy Markdown
Author

axel-paillaud commented May 20, 2026

Multi-shop

Shop context ([_context][shopId]/[_context][shopConstraint]) not forwarded to any CQRS command/query despite ?shopId=1 being required per PR description

?shopId=1 is required for API client authentication in multi-store mode, but the Cart domain has no ShopConstraint support, CreateEmptyCustomerCartCommand resolves the shop implicitly via $customer->id_shop. Forwarding the shop context to CQRS would require a Core domain change outside the scope of this PR.

@axel-paillaud
Copy link
Copy Markdown
Author

Integration test

testInvalidData only covers testCreateCartInvalidData; no validation tests for PATCH/sub-resource endpoints
fixed

PATCH test methods do not assert response body/structure
fixed

testAddProductToCart asserts 'customization' => null in product structure, but Cart.php $products OpenAPI context defines no customization key — mismatch to clarify
fixed

@axel-paillaud
Copy link
Copy Markdown
Author

@kpodemski I've just checked all the points raised by the AI, let me know if it's okay

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Admin API Contributions AI reviewed Status: Claude AI has already pre-reviewed this PR Waiting for author

Projects

Status: Waiting for author

Development

Successfully merging this pull request may close these issues.

4 participants