Nonflavored Prescription Medicine
    Wondering what’s next for npm?Check out our public roadmap! »

    @restorecommerce/cart
    TypeScript icon, indicating that this package has built-in type declarations

    0.2.3 • Public • Published

    Cart

    Build StatusDependenciesCoverage Status

    An backend agnostic purely data-driven shopping cart that can be used on client- and server side.

    Features

    • Item handling (add, remove etc.)
    • VAT calculation with extensible tax model
      • EU tax calculation built-in (for sub-threshold based taxation)
    • Shipping cost calculation based on selected courier and plan
    • Extensibility for adding couriers
    • Totals calculation based on items, taxes and shipping
    • Can run completely offline
    • Local storage on browsers via pluggable serializers
    • Back-end agnostic
    • Fully typed

    Usage

    Basic Example

    Cart Instantiation:

    const cart = new Cart({
      serializer: new MockSerializer(),
      shippingMethod: new Courier({
        source: JSON.stringify(data.publicDHL),
        shipping: {originCountry: 'DE'}
      }),
      taxOriginCountry: 'DE',
      taxes: {
        vat_standard: {
          rate: new Decimal(1.19),
          desc: '+ VAT 19%'
        },
        vat_reduced: {
          rate: new Decimal(1.07),
          desc: '+ VAT 7%'
        }
      }
    });

    Setting serializer and shippingMethod is optional.

    setDestinationCountry(country: string)

    cart.setDestinationCountry('LV');

    getShippingMethod()

    cart.getShippingMethod();

    Output:

    Courier {
      _source: {
        assumptions: {
          currency: 'eur',
          dimensions: [Object],
          length: 'mm',
          ranges: [Object],
          weight: 'gram'
        },
        zones: {
          '1': [Object],
          '2': [Object],
          '3': [Object],
          '4': [Object],
          '5': [Object],
          '6': [Object],
          '7': [Object],
          '8': [Object],
          national: [Object]
        }
      },
      _shipping: {
        destinationCountry: 'LV',
        originCountry: 'DE' }
    }

    getShipping()

    There is no setShipping setter, since info about shipping is taken from ShippingMethod's source property.

    cart.getShipping(); // calculates shipping cost

    Output:

    {
      price: '15.99',
      taxType: 'vat_standard',
      maxWeight: 5000,
      type: 'package',
      zone: '1',
      human: {
        zone: '1 (all EU countries)',
        offer: 'Package up to 5kg',
      }
    }

    getSerializer()

    cart.getSerializer();

    getTaxRates()

    cart.getTaxRates();

    Output:

    {
      taxes: {
        vat_standard: {
          rate: '1.19',
          desc: '+ VAT 19%'
        },
        vat_reduced: {
          rate: '1.07',
          desc: '+ VAT 7%'
        }
      }
    }

    addItems(items: IItem[])

    cart.addItems([{
      sku: 'cr2-blue',
      price: new Decimal('12.95'), // Price
      taxType: 'vat_reduced',
      weight: 210, // grams
      height: 2.20, // cm
      width: 13.5, // cm
      depth: 8.22, // cm
      quantity: 7,
    }, {
      sku: 'cr5-red',
      price: new Decimal('1.10'),
      taxType: 'vat_standard',
      weight: 210, // grams
      height: 2.20, // cm
      width: 13.5, // cm
      depth: 8.22, // cm
      quantity: 15,
    }, {
      sku: 'cr3-yellow',
      price: new Decimal('2.48'),
      taxType: 'vat_standard',
      weight: 210, // grams
      height: 2.20, // cm
      width: 13.5, // cm
      depth: 8.22, // cm
      quantity: 3,
    }]);

    remItem(sku: string)

    cart.remItem('cr3-yellow');

    getItems()

    cart.getItems();

    Output:

    [
      {
        sku: 'cr2-blue',
        price: 12.95,
        taxType: 'vat_reduced',
        weight: 210,
        height: 2.2,
        width: 13.5,
        depth: 8.22,
        quantity: 7
      },
      {
        sku: 'cr5-red',
        price: 1.10,
        taxType: 'vat_standard',
        weight: 210,
        height: 2.2,
        width: 13.5,
        depth: 8.22,
        quantity: 15
       }
    ]

    setCustomer(customer: ICustomer)

    cart.setCustomer({
      type: CustomerType.COMMERCIAL,
    });

    setCustomerType(type: CustomerType)

    This modifies the customer..

    cart.setCustomerType(CustomerType.PRIVATE);

    getCustomer()

    cart.getCustomer();

    Output:

    {
      type: 1 // 0 - COMMERCIAL / 1 - PRIVATE
    }

    modifyItem(item: any)

    cart.modifyItem({
      sku: 'cr5-red',
      // no update for other Cart properties as no change
      quantity: 10, // change only quantity
    });

    modifyItemQuantity(sku: string, quantity: number)

    cart.modifyItemQuantity('cr5-red', 5); // adds 5 to initial quantity

    getItemQuantity(sku: string)

    `Amount of cr2-blue = ${cart.getItemQuantity('cr5-red')}`;

    Output:

    Amount of cr5-red = 15 // 10 + 5
    

    getItemCount()

    `Amount of unique products = ${cart.getItemCount()}`;

    Output:

    Amount of unique products = 2
    

    getGrandQuantity()

    `Amount of all products = ${cart.getGrandQuantity()}`;

    Output:

    Amount of all products = 22 // 15 + 7
    

    getTaxes(keepOriginalTaxType?: boolean )

    cart.getTaxes();

    Output:

    netPrice and rate are instances of Decimal.

    {
      vat_standard: {
        netPrice: '32.49',
        rate: '1.19',
        desc: '+ VAT 19%'
      },
      vat_reduced: {
        netPrice: '90.65',
        rate: '1.07',
        desc: '+ VAT 7%'
      }
    }

    static round(money: Money)

    Parameter could be number|string|Decimal. Returns string.

    Cart.round(1.120); // '1.12'
    Cart.round('1.123'); // '1.13'
    Cart.round(new Decimal(1.125)); // '1.13'
    Cart.round(new Decimal('1.127')); // '1.13'

    getTotalNet()

    Total net (without taxes).

    Cart.round(cart.getTotalNet()); // '123.14'

    Calculation:

    1) customer.billing.countryCode === 'LV' => VAT + 19% / 7%
    2) cr2_blue: Price * quantity => 12.95 * 7  = 90.65
    3) cr5_red:  Price * quantity => 1.10 * 15  = 16.5
    4) Max Weight of all items = 22 items * 210 grams  = 4620 grams =>
    shipping = 15.99 euro for package less than 5000 grams to EU
    5) sum = 90.65 + 16.5 + 15.99 = 123.14
    

    getTotalGross()

    Total gross (with taxes).

    Cart.round(cart.getTotalGross()); // '135.66'

    Calculation:

    1) cr2_blue => cr2_blue + VAT 7% = 90.65 * 1.07 = 96.9955
    2) cr5_red => cr5_red + VAT 19% = 16.5 * 1.19 = 19.635
    3) shipping => shipping + VAT 19% = 15.99 * 1.19 = 19.0281
    4) sum  =>  96.9955 + 19.635 + 19.0281 = 135.6586
    5) round(135.6586) = 135.66
    

    Tests

    More examples of using the Cart can be found in test/index.ts.

    Reference

    Method Description
    getItems(): IItems / undefined Get Items
    private setItems(items: IItem[]): void Set items
    getCustomer(): ICustomer / undefined Get customer
    setCustomer(customer: ICustomer): void Set customer
    getShippingMethod(): IShippingMethod/ undefined Get shipping method
    setShippingMethod(shippingMethod: IShippingMethod): void Set shipping method
    getSerializer(): ISerializer / undefined Get serializer
    setSerializer(serializer: ISerializer): void Set serializer
    getTaxRates(): TaxRates Get tax rates
    private setTaxRates(taxRates: TaxRates) Set tax rates
    setCustomerType(type: CustomerType): void Set customer's type (PRIVATE/COMMERCIAL)
    setDestinationCountry(country: string): void Set destination country
    addItems(items: IItem[]): void Add item/items to the cart
    remItem(sku: string): void Remove item from the cart by SKU (Stock Keeping Unit)
    modifyItem(item: any) Update item
    modifyItemQuantity(sku: string, quantity: number): void Modify item's quantity
    getItemCount(): number Get item count
    getItemQuantity(sku: string): number Get quantity of particular item
    getGrandQuantity(): number Item count x quantity of each item
    getTaxes(keepOriginalTaxType?: boolean ): { [taxType: string]: { netPrice: Decimal, rate: Decimal, desc: string, price: Decimal } } Get tax list, with ratios and additive costs
    getTotalNet(): number Get sum of item and shipping costs (taxes excluded)
    getTotalGross(): number Get sum of item and shipping costs (taxes included)
    getShipping(): { price: Money, [prop: string]: any } Get item's shipping info
    static round(money: Money): string Convert money type (number/string/Decimal) to string with rounding

    Development

    To lint, transpile and test, run:

    npm test

    Couriers

    IShippingMethod interface implementations:

    Courier Plans:

    Calculation Logic

    Taxes

    For VAT calculation, the cart applies the EU ruleset for taxation which is exemplified in the following with a shop that is located in Germany and customer location location/ type being:

    • Germany private: VAT applies
    • Germany commercial: VAT applies
    • Other EU countries private: VAT applies
    • Other EU countries commercial: VAT free
    • Non-EU countries private: VAT free
    • Non-EU countries commercial: VAT free

    This also works for other countries as it's just a simplification of this for most countries. Sales taxes and other country specific taxes can be added.

    Legal background:

    Install

    npm i @restorecommerce/cart

    DownloadsWeekly Downloads

    4

    Version

    0.2.3

    License

    MIT

    Unpacked Size

    165 kB

    Total Files

    48

    Last publish

    Collaborators

    • avatar
    • avatar
    • avatar
    • avatar
    • avatar