Enable Dark Mode!
how-developers-can-use-cart-service-in-odoo-19-with-interaction-classes.jpg
By: Aswin AK

How Developers Can Use Cart Service in Odoo 19 with Interaction Classes

Technical Odoo 19 Website&E-commerce

Odoo 19 introduces a powerful cart service that simplifies cart management in e-commerce applications through Interaction Classes. The cart service provides a comprehensive API to add products with various configurations, including quantities, variants, custom attributes, and optional products. In this tutorial, we'll explore all possible ways to use the cart service with practical examples.

Understanding the Cart Service

The cart service is accessible through this.services.cart in Interaction Classes and provides the add() method with extensive configuration options.

Basic Cart Service Method

await this.services.cart.add(productData, options);

Project Structure

custom_cart_demo/
+-- __init__.py
+-- __manifest__.py
+-- controllers/
¦   +-- __init__.py
¦   +-- main.py
+-- static/
¦   +-- src/
¦       +-- js/
¦           +-- cart_examples.js
+-- views/
    +-- templates.xml

Step 1: Create the Manifest File

Create __manifest__.py:

{
    'name': 'Cart Service Examples',
    'version': '19.0.1.0.0',
    'category': 'Website/Website',
    'summary': 'Complete Cart Service Examples for Odoo 19',
    'depends': ['website_sale'],
    'data': ['views/templates.xml'],
    'assets': {
        'web.assets_frontend': [
            'custom_cart_demo/static/src/js/cart_examples.js',
        ],
    },
    'installable': True,
    'application': False,
    'license': 'LGPL-3',
}

Step 2: Create Init Files

Create __init__.py:

from . import controllers
Create controllers/__init__.py:
from . import main

Step 3: Create the Controller

Create controllers/main.py:

from odoo import http
from odoo.http import request
class CartExamplesController(http.Controller):
    
    @http.route('/shop/cart/examples', type='http', auth='public', website=True)
    def cart_examples_page(self, **kwargs):
       """ function redirect to the demo cart page with required values """
        products = request.env['product.template'].sudo().search([
            ('sale_ok', '=', True),
            ('website_published', '=', True)
        ], limit=10)
        
        return request.render('your_module_name.cart_examples_template', {
            'products': products,
        })

Step 4: Create the Template

Create views/templates.xml:

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="cart_examples_template" name="Cart Examples Page">
        <t t-call="website.layout">
            <div id="wrap" class="oe_structure oe_website_sale"/>
                <div class="container my-5 cart-examples-demo">
                    <h1 class="text-center mb-5">Complete Cart Service Examples</h1>
                    
                    <!-- Basic Examples -->
                    <div class="card mb-4">
                        <div class="card-header bg-primary text-white">
                            <h3 class="mb-0">1. Basic Examples</h3>
                        </div>
                        <div class="card-body">
                            <t t-foreach="products[:3]" t-as="product">
                                <div class="d-flex justify-content-between align-items-center mb-3 p-3 border rounded">
                                    <div>
                                        <h5 t-field="product.name"/>
                                        <small class="text-muted">Template ID: <t t-esc="product.id"/> | Product ID: <t t-esc="product.product_variant_id.id"/></small>
                                    </div>
                                    <button class="btn btn-primary btn-basic-add"
                                            t-att-data-template-id="product.id"
                                            t-att-data-product-id="product.product_variant_id.id">
                                        Basic Add
                                    </button>
                                </div>
                            </t>
                        </div>
                    </div>
                    <!-- Quantity Examples -->
                    <div class="card mb-4">
                        <div class="card-header bg-success text-white">
                            <h3 class="mb-0">2. Quantity Examples</h3>
                        </div>
                        <div class="card-body">
                            <t t-if="products">
                                <div class="d-flex justify-content-between align-items-center mb-3 p-3 border rounded">
                                    <div class="flex-grow-1">
                                        <h5 t-field="products[0].name"/>
                                        <div class="input-group mt-2" style="max-width: 200px;">
                                            <button class="btn btn-outline-secondary qty-minus" type="button">-</button>
                                            <input type="number" class="form-control text-center qty-input" value="1" min="1"/>
                                            <button class="btn btn-outline-secondary qty-plus" type="button">+</button>
                                        </div>
                                    </div>
                                    <button class="btn btn-success btn-qty-add"
                                            t-att-data-template-id="products[0].id"
                                            t-att-data-product-id="products[0].product_variant_id.id">
                                        Add with Quantity
                                    </button>
                                </div>
                            </t>
                        </div>
                    </div>
                    <!-- Buy Now Example -->
                    <div class="card mb-4">
                        <div class="card-header bg-warning text-dark">
                            <h3 class="mb-0">3. Buy Now (Immediate Purchase)</h3>
                        </div>
                        <div class="card-body">
                            <t t-if="products">
                                <div class="d-flex justify-content-between align-items-center p-3 border rounded">
                                    <div>
                                        <h5 t-field="products[0].name"/>
                                        <small class="text-muted">Bypasses configurators and redirects to cart</small>
                                    </div>
                                    <div>
                                        <button class="btn btn-warning me-2 btn-buy-now"
                                                t-att-data-template-id="products[0].id"
                                                t-att-data-product-id="products[0].product_variant_id.id">
                                            Buy Now (Redirect)
                                        </button>
                                        <button class="btn btn-outline-warning btn-buy-no-redirect"
                                                t-att-data-template-id="products[0].id"
                                                t-att-data-product-id="products[0].product_variant_id.id">
                                            Buy Now (No Redirect)
                                        </button>
                                    </div>
                                </div>
                            </t>
                        </div>
                    </div>
                  
                    <!-- Advanced Options -->
                    <div class="card mb-4">
                        <div class="card-header bg-danger text-white">
                            <h3 class="mb-0">4. Advanced Options</h3>
                        </div>
                        <div class="card-body">
                            <t t-if="products">
                                <div class="mb-3 p-3 border rounded">
                                    <h5>Force Configuration Dialog</h5>
                                    <button class="btn btn-danger btn-force-configurator"
                                            t-att-data-template-id="products[0].id"
                                            t-att-data-product-id="products[0].product_variant_id.id">
                                        Add with Configurator
                                    </button>
                                </div>
                                <div class="p-3 border rounded">
                                    <h5>Pre-configured Product</h5>
                                    <button class="btn btn-outline-danger btn-preconfigured"
                                            t-att-data-template-id="products[0].id"
                                            t-att-data-product-id="products[0].product_variant_id.id">
                                        Add as Configured
                                    </button>
                                </div>
                            </t>
                        </div>
                    </div>
              
             </div>
        </t>
    </template>
    
    <record id="menu_cart_examples" model="website.menu">
        <field name="name">Cart Examples</field>
        <field name="url">/shop/cart/examples</field>
        <field name="parent_id" ref="website.main_menu"/>
        <field name="sequence">50</field>
    </record>
</odoo>

Step 5: Complete Cart Service Examples

Create static/src/js/cart_examples.js:

/** @odoo-module **/
import { Interaction } from "@web/public/interaction";
import { registry } from "@web/core/registry";
/**
 * Complete Cart Service Examples - All Possible Usage Patterns
 * 
 * This interaction demonstrates every way to use the cart service in Odoo 19
 */
export class CartExamplesInteraction extends Interaction {
    static selector = '.cart-examples-demo';
    dynamicContent = {
        // Basic Examples
        '.btn-basic-add': { 't-on-click': this.onBasicAdd },
        
        // Quantity Examples
        '.qty-plus': { 't-on-click': this.onQuantityPlus },
        '.qty-minus': { 't-on-click': this.onQuantityMinus },
        '.btn-qty-add': { 't-on-click': this.onAddWithQuantity },
        
        // Buy Now Examples
        '.btn-buy-now': { 't-on-click': this.onBuyNow },
        '.btn-buy-no-redirect': { 't-on-click': this.onBuyNowNoRedirect },
        
        // Advanced Options
        '.btn-force-configurator': { 't-on-click': this.onForceConfigurator },
        '.btn-preconfigured': { 't-on-click': this.onPreconfigured },
    };
    //==========================================================================
    // EXAMPLE 1: Basic Product Add (Minimum Required Parameters)
    //==========================================================================
    
    /**
     * Most basic usage - only required parameters
     * 
     * Required parameters:
     * - productTemplateId: product.template ID
     * - productId: product.product ID (variant)
     * - quantity: number of items (defaults to 1 if not specified)
     */
    async onBasicAdd(event) {
        const btn = event.currentTarget;
        const templateId = parseInt(btn.dataset.templateId);
        const productId = parseInt(btn.dataset.productId);
        try {
            this.setLoading(btn, true);
            
            // Simplest possible call
            const quantity = await this.services.cart.add({
                productTemplateId: templateId,
                productId: productId,
                quantity: 1,
            }, {});
            
            this.showSuccess(btn, `Added ${quantity} item!`);
        } catch (error) {
            this.showError(btn, 'Failed');
            console.error('Error:', error);
        }
    }
    //==========================================================================
    // EXAMPLE 2: Add with Custom Quantity
    //==========================================================================
    
    onQuantityPlus(event) {
        const input = event.currentTarget.parentElement.querySelector('.qty-input');
        input.value = parseInt(input.value) + 1;
    }
    onQuantityMinus(event) {
        const input = event.currentTarget.parentElement.querySelector('.qty-input');
        const newValue = parseInt(input.value) - 1;
        input.value = newValue > 1 ? newValue : 1;
    }
    async onAddWithQuantity(event) {
        const btn = event.currentTarget;
        const templateId = parseInt(btn.dataset.templateId);
        const productId = parseInt(btn.dataset.productId);
        const qtyInput = this.el.querySelector('.qty-input');
        const quantity = parseInt(qtyInput.value) || 1;
        try {
            this.setLoading(btn, true);
            
            // Add with custom quantity
            const addedQty = await this.services.cart.add({
                productTemplateId: templateId,
                productId: productId,
                quantity: quantity,
            }, {});
            
            this.showSuccess(btn, `Added ${addedQty} items!`);
        } catch (error) {
            this.showError(btn, 'Failed');
        }
    }
    //==========================================================================
    // EXAMPLE 3: Buy Now - Immediate Purchase
    //==========================================================================
    
    /**
     * Buy Now with redirect to cart
     * 
     * Options:
     * - isBuyNow: true - bypasses optional configurations
     * - redirectToCart: true (default) - redirects to /shop/cart
     */
    async onBuyNow(event) {
        const btn = event.currentTarget;
        const templateId = parseInt(btn.dataset.templateId);
        const productId = parseInt(btn.dataset.productId);
        try {
            this.setLoading(btn, true);
            
            // Buy now with redirect
            await this.services.cart.add({
                productTemplateId: templateId,
                productId: productId,
                quantity: 1,
            }, {
                isBuyNow: true,
                redirectToCart: true,  // Will redirect to cart page
            });
            
            // Code below won't execute due to redirect
        } catch (error) {
            this.showError(btn, 'Failed');
        }
    }
    /**
     * Buy Now without redirect
     * Useful when you want to add to cart immediately but stay on page
     */
    async onBuyNowNoRedirect(event) {
        const btn = event.currentTarget;
        const templateId = parseInt(btn.dataset.templateId);
        const productId = parseInt(btn.dataset.productId);
        try {
            this.setLoading(btn, true);
            
            // Buy now without redirect
            const quantity = await this.services.cart.add({
                productTemplateId: templateId,
                productId: productId,
                quantity: 1,
            }, {
                isBuyNow: true,
                redirectToCart: false,  // Stay on current page
            });
            
            this.showSuccess(btn, 'Added without redirect!');
        } catch (error) {
            this.showError(btn, 'Failed');
        }
    }
    //==========================================================================
    // EXAMPLE 4: Advanced Options
    //==========================================================================
    
    /**
     * Force showing configurator even if product doesn't require it
     * 
     * Options:
     * - showQuantity: false - hide quantity selector in configurator
     */
    async onForceConfigurator(event) {
        const btn = event.currentTarget;
        const templateId = parseInt(btn.dataset.templateId);
        const productId = parseInt(btn.dataset.productId);
        try {
            this.setLoading(btn, true);
            
            // This will show configurator if available
            const quantity = await this.services.cart.add({
                productTemplateId: templateId,
                productId: productId,
                quantity: 1,
            }, {
                isBuyNow: false,  // Allow configurator to show
                showQuantity: true,  // Show quantity selector
            });
            
            if (quantity > 0) {
                this.showSuccess(btn, 'Added!');
            } else {
                this.resetButton(btn, 'Add with Configurator');
            }
        } catch (error) {
            this.showError(btn, 'Failed');
        }
    }
    /**
     * Add pre-configured product (skip configurator)
     * 
     * Options:
     * - isConfigured: true - treats product as already configured
     */
    async onPreconfigured(event) {
        const btn = event.currentTarget;
        const templateId = parseInt(btn.dataset.templateId);
        const productId = parseInt(btn.dataset.productId);
        try {
            this.setLoading(btn, true);
            
            // Add as pre-configured product
            const quantity = await this.services.cart.add({
                productTemplateId: templateId,
                productId: productId,
                quantity: 1,
            }, {
                isConfigured: true,  // Skip configurator even if available
            });
            
            this.showSuccess(btn, 'Added as configured!');
        } catch (error) {
            this.showError(btn, 'Failed');
        }
    }

    //==========================================================================
    // Helper Methods
    //==========================================================================
    
    setLoading(btn, loading, text = 'Adding...') {
        btn.disabled = loading;
        if (loading) {
            btn.dataset.originalText = btn.innerHTML;
            btn.innerHTML = `<span class="spinner-border spinner-border-sm me-2"></span>${text}`;
        }
    }
    showSuccess(btn, text) {
        btn.innerHTML = `<i class="fa fa-check me-2"></i>${text}`;
        btn.classList.add('btn-success');
        setTimeout(() => this.resetButton(btn), 2000);
    }
    showError(btn, text) {
        btn.innerHTML = `<i class="fa fa-times me-2"></i>${text}`;
        btn.classList.add('btn-danger');
        setTimeout(() => this.resetButton(btn), 2000);
    }
    resetButton(btn, text = null) {
        btn.disabled = false;
        btn.innerHTML = text || btn.dataset.originalText || 'Add to Cart';
        btn.className = btn.className.replace(/btn-(success|danger)/g, '').trim();
        if (!btn.className.includes('btn-')) {
            btn.classList.add('btn-primary');
        }
    }
}
registry.category("public.interactions").add("CartExamplesInteraction", CartExamplesInteraction);

Parameter Reference

When calling the function, you’ll pass two main parameters:

  1. A Product Data Object
  2. An Options Object

Below is a clean, structured explanation of every property.

1.Product Data Object

This object represents the product you want to add/configure. It contains both required and optional fields.

  • productTemplateId (Number — Required)
  • The ID of the product template.

  • productId (Number — Conditional)
  • The product variant ID.

    If you don’t provide this, the system will attempt to create/identify a variant using the attribute values from ptavs.

  • quantity (Number — Optional, default: 1)
  • How many units to add.

  • uomId (Number — Optional)
  • ID of the Unit of Measure to use for this product.

  • ptavs (Number[] — Optional)
  • List of stored attribute value IDs (for variant creation).

  • productCustomAttributeValues (Array — Optional)
  • Used when the product has custom (non-stored) attribute values.

  • noVariantAttributeValues (Number[] — Optional)
  • Array of attribute IDs that do not create a variant (no-variant attributes).

  • isCombo (Boolean — Optional, default: false)
  • Indicates whether this product is part of a combo.

2.Options Object

These settings control how the product behaves when being added to the cart or configured.

  • isBuyNow (Boolean — default: false)
  • Skips configurators and processes the product immediately.

  • redirectToCart (Boolean — default: true)
  • If isBuyNow is true, this decides whether the user should be redirected to the cart.

  • isConfigured (Boolean — default: false)
  • Marks the product as already configured, bypassing further steps.

  • showQuantity (Boolean — default: true)
  • Controls whether the quantity selector is displayed.

Conclusion

The Odoo 19 cart service provides a comprehensive API for adding products to the cart with full control over variants, quantities, attributes, and user experience. The Interaction Class pattern makes it easy to implement these features with clean, maintainable code.

To read more about How to Enable E-store Picking and Payment for Odoo 18 Ecommerce Store, refer to our blog How to Enable E-store Picking and Payment for Odoo 18 Ecommerce Store.


If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message