Guide 08 · Developer docs

Commerce

A Core dna store is a set of records and an API that moves a purchase from cart to order: products described in every language and priced in every currency the store sells in, a cart, a checkout that becomes an order, invoices, payments, tax and shipping. The site builds the storefront on top: the cart and checkout pages, their scripts, and the customer emails.

L2–L3
Unit 02 · Carts, cookies and the cache

Changing the cart

Adding, updating and removing items, and discount codes, through the cart API: the requests with real responses, the business refusals that answer 200, the messages that report them, the limits on item attributes, and the cart page that shows it all.

Who this is for. The JavaScript developer wiring add-to-cart buttons and the cart page. Afterwards you can make every cart change, read what happened from the response alone, and show the shopper the platform's own explanation when something was refused.

The requests

There are no page routes for changing items: every change goes through the API, from your script. Each request has two forms. Without a cart id it works on the visitor's own cart, found through the cart cookie, which it also renews. With an id, /cdna-api/cart/<uuid>/…, it works on that cart and leaves the cookie alone, which is the form a headless front end or a server uses.

ToRequestBody
AddPOST /cdna-api/cart/item/<sku>quantity (default 1), optional attributes, optional currency (a three-letter code; only an empty cart takes it)
Change a linePUT /cdna-api/cart/item/<item id>quantity (1 or more) and the line's attributes, always
Remove a lineDELETE /cdna-api/cart/item/<item id>None
Add or remove a discount codePOST or DELETE /cdna-api/cart/discount/<code>None
Empty the cartPUT /cdna-api/cart/clearNone; the cart record stays, and its currency is cleared
Read itGET /cdna-api/cartNone; 404 when the visitor has no saved cart
Re-price itPUT /cdna-api/cart/evaluateNone; checks every line and re-applies discounts
Dismiss a messageDELETE /cdna-api/cart/message/<message id>None

Every answer carries the whole cart as data.cart, totals, items and messages included, so the page never needs a second request to redraw. Use a properties selector to keep the answer to what the page draws, as in the examples below. A line is addressed by its item id from that answer, not by its SKU.

Adding the same SKU again with the same attributes adds to the existing line, so a retried add counts twice: disable the button while a request is in flight. Changing a line's quantity is safe to retry.

Add an item with an attribute
POST /cdna-api/cart/f6b4ec7c-8192-43ca-9327-1ba9add89fe3/item/API-SANDBOX-MUG-BLUE?properties={cart{total,messages{status,text},items{id,productCode,quantity,amount,attributes}}} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Content-Type: application/json

{
  "quantity": 1,
  "attributes": {
    "engraving": "For Sam & Jo"
  }
}
Response 200 OKAdded
{
  "result": "success",
  "data": {
    "cart": {
      "total": "58.00",
      "messages": [{"status": "info", "text": "Item has been added"}],
      "items": [
        {"id": 1, "productCode": "API-SANDBOX-MUG", "quantity": 3, "amount": "30.0000", "attributes": []},
        {"id": 2, "productCode": "API-SANDBOX-MUG-BLUE", "quantity": 1, "amount": "14.0000", "attributes": []},
        {"id": 3, "productCode": "API-SANDBOX-MUG-BLUE", "quantity": 1, "amount": "14.00", "attributes": {"engraving": "For Sam &amp; Jo"}}
      ]
    }
  }
}
Real response from this documentation site, on its API sandbox cart

The engraved mug is a line of its own, because two lines merge only when both the SKU and every attribute match. Now change that line's quantity without sending its attributes, as a quantity stepper written in a hurry does:

Change a line's quantity: without its attributes, the engraving is lost and the line merges into its twin
PUT /cdna-api/cart/f6b4ec7c-8192-43ca-9327-1ba9add89fe3/item/3?properties={cart{total,messages{status,text},items{id,productCode,quantity,amount,attributes}}} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Content-Type: application/json

{
  "quantity": 2
}
Response
{
  "result": "success",
  "data": {
    "cart": {
      "total": "72.00",
      "messages": [{"status": "info", "text": "Item has been updated"}],
      "items": [
        {"id": 1, "productCode": "API-SANDBOX-MUG", "quantity": 3, "amount": "30.0000", "attributes": []},
        {"id": 2, "productCode": "API-SANDBOX-MUG-BLUE", "quantity": 3, "amount": "42.00", "attributes": []}
      ]
    }
  }
}
{
  "result": "error",
  "message": "Quantity is too little"
}
Real responses from this documentation site, on its API sandbox cart

So a quantity change must always send the line's current attributes back, and a quantity of 0 is refused: remove the line with DELETE instead.

Refusals answer 200

Only a malformed request is an HTTP error: a quantity that is not a number ("Quantity has to be a number") or below 1 ("Quantity is too little"), attributes that are not an object ("Attributes should be an array") or hold a list ("Attribute value should be scalar"), and a currency that is not a known three-letter code. Each is a 400.

Everything the shop refuses for a business reason answers 200 with "result": "success" and the reason in the cart's messages: an unknown or unpublished SKU, a product of another site, a product with no price in the cart's currency, a discount code that does not exist or is used up. Code that checks only the HTTP status will tell the shopper their item was added when it was not.

An unknown SKU: a successful answer with an error message
POST /cdna-api/cart/f6b4ec7c-8192-43ca-9327-1ba9add89fe3/item/NO-SUCH-SKU?properties={cart{total,messages{status,text}}} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Content-Type: application/json

{
  "quantity": 1
}
Response 200 OKUnknown SKU
{
  "result": "success",
  "data": {
    "cart": {
      "total": "72.0000",
      "messages": [{"status": "error", "text": "Item is not available"}]
    }
  }
}
Real response from this documentation site, on its API sandbox cart

Totals come back as decimal strings whose number of places varies from one answer to the next ("58.00", "72.0000"); parse them as numbers and format them yourself.

Every change to the cart deletes its checkout. A shopper who changes a quantity, adds a donation or applies a code on the cart while a checkout is open must start the checkout again; The checkout and when it can be paid covers what that means for the checkout page.

Cart messages

Messages are how the platform tells the shopper what happened to their cart. Each has a status (info or error; the model allows warning, but nothing in the cart issues one), a translated text, a data object naming the item it concerns (data.item.id and data.item.name), and an expiry: 5 seconds after it was made for info, 15 for errors. Expired messages are removed the next time the cart is loaded, so show a message in the answer to the action that caused it; a page loaded a minute later will not have it. There is no message code, so match on status, never on the text.

StatusTexts
info"Item has been added", "Item has been updated", "Item has been deleted", "Cart has been cleared", "Code has been added", "Code is not added as it is already added"
error"Item is not available", "Item is not found", "Item could not be added to cart as it is not available", "Item could not be added to cart as it has no price in the selected currency", "Item could not be added to cart as its price is not specified" (and the same three as "could not be updated"), "Item is not available in the selected currency", "Item price is not specified", "Code is not found", "Code could not be added as its maximum usage is reached", "This code cannot be applied because another code for the same discount is already in use"

Two things to handle on the cart page:

  • Stock and price changes. The cart charges each line at its current price when it is evaluated, and leaves stock checks to the site: show stock or price messages in your own template where the shop needs them.
  • Unavailable lines. A line marked unavailable (product unpublished, price removed) is left out of the total and out of the checkout until the shopper changes it. Show such lines clearly, with a remove button.

The cart page

/cart renders carts/view.html, which you supply, with cart, and _recaptcha when the checkout captcha is on. It is never cached, so it is the one page where the template can read the cart directly:

Template code templates/carts/view.html
{foreach $cart->getMessages() as $message}
  <p class="cart-message cart-message--{$message->getStatus()}">{$message->getText()|escape}</p>
{/foreach}

{foreach $cart->getItems() as $item}
  <div class="cart-line{if !$item->isAvailable()} is-unavailable{/if}" data-item="{$item->getId()}">
    {$item->getName()|escape} &times; {$item->getQuantity()}: {$item->getTotalAsMoney()}
    {if $item->getAttribute('engraving')}<small>Engraving: {$item->getAttribute('engraving')}</small>{/if}
    {if !$item->isAvailable()}<small>No longer available</small>{/if}
  </div>
{foreachelse}
  <p>Your cart is empty.</p>
{/foreach}

<p>Discount: {$cart->getDiscountTotalAsMoney()}</p>
<p>Total: {$cart->getTotalAsMoney()}</p>
{% for message in cart.getMessages() %}
  <p class="cart-message cart-message--{{ message.getStatus() }}">{{ message.getText()|e }}</p>
{% endfor %}

{% for item in cart.getItems() %}
  <div class="cart-line{{ item.isAvailable() ? '' : ' is-unavailable' }}" data-item="{{ item.getId() }}">
    {{ item.getName()|e }} &times; {{ item.getQuantity() }}: {{ item.getTotalAsMoney() }}
    {% if item.getAttribute('engraving') %}<small>Engraving: {{ item.getAttribute('engraving') }}</small>{% endif %}
    {% if not item.isAvailable() %}<small>No longer available</small>{% endif %}
  </div>
{% else %}
  <p>Your cart is empty.</p>
{% endfor %}

<p>Discount: {{ cart.getDiscountTotalAsMoney() }}</p>
<p>Total: {{ cart.getTotalAsMoney() }}</p>

The money getters print the amount with its currency, such as $14.00. Attribute values are printed as they are, without escaping: they are stored already HTML-encoded (next section), and escaping them again would show &amp;.

Item attributes

Attributes are name and value pairs on a line, for whatever the shopper chooses that is not a separate SKU: an engraving, a gift note, a monogram colour. The limits are fixed: at most 20 attributes on a line, names of at most 50 characters, values of at most 500. Names and values are trimmed and HTML-encoded before they are stored, and the limits apply to the encoded text, so & counts as five characters.

Keep every line within those limits: check lengths and counts in the browser before sending, and compare the attributes in the answer with what you sent. Attributes are stored sorted by name, and an empty value is not stored.

Discount codes on the cart

A code is added with POST /cdna-api/cart/discount/<code>, in any letter case. As with items, a refused code is a 200 with an error message ("Code is not found", the maximum-usage message, or the one for a second code of the same discount). A code that exists but whose discount does not apply yet, or no longer, is accepted and takes nothing off: compare discountTotal before and after to tell the shopper. A link to /cart/discount/<code> does the same for a visitor arriving from a campaign, and then shows the cart. Discounts at checkout covers how discounts are worked out.

Go deeper

Was this page helpful?

Your answer goes to the docs team.