SimplyClub

Product Attribute Mapping

integration attributes line-items cart

This guide explains how to enrich SimplyClub transactions with per-line product attributes — mapping each cart line’s extended_attributes (tags, collections, metafields) onto the Simply line-item fields (Department, CategoryCode, Vendor, …) that drive product-level promotions and reporting.

It is the companion to the Frontend Embedding Guide (which covers setCart) and the Webhook Integration Guide (which covers the order line-item shape). This page focuses only on the attribute → Simply-field layer.

Opt-in, off by default. Mapping does nothing until you explicitly enable it for a POI (active: true). With it off, your extended_attributes are accepted and ignored — zero behavior change.


1. What it does

Each cart line item can carry an optional extended_attributes bag. When mapping is active for a POI, the SimplyClub backend reads that bag, resolves a value for each configured field, and copies it onto the Simply transaction line item (TranItem) under the Simply field name.

The result: a single product can tell SimplyClub its department, category, vendor, and classification codes — so club rules and promotions can target “all items in Department 12” or “anything from Vendor X” without SimplyClub having a product catalog.

cart line.extended_attributes  ──►  (per-POI mapping rules)  ──►  TranItem Simply fields
   { tags, collections,                  active === true              Department, CategoryCode,
     metafields }                                                     Vendor, SubDept1…3, PLU…1…3

The same mapping is applied in three contexts, so a line is enriched identically wherever it enters:

  • Drop-in session create / update (live cart / benefit calculation)
  • Shopify orders/paid webhook (transaction close)
  • Refunds

2. The extended_attributes contract

Your storefront / cart extension is responsible for putting extended_attributes on each line item it sends (via setCart, and forwarded into the order webhook). The shape:

extended_attributes?: {
  tags?:        { id: string; value: string }[];
  collections?: { id: string; value: string }[];
  metafields?:  { id: string; value: string | number | boolean }[];
}
  • id is the machine key SimplyClub maps and stores (e.g. a tag ID, a collection ID, or a metafield identifier like custom.material_composition).
  • value is the human-readable label — useful for your own debugging; mapping itself keys off id (tags/collections) or matches by id (metafields).

This is the contract this app expects on line objects — it is not a verbatim Shopify Admin API payload. Any storefront that emits this shape works (see §6).

For the surrounding line-item fields (id, sku, price, quantity, …) see the line-item shape in the Webhook Integration Guide and the setCart command in the Technical Documentation — this page does not repeat them.


3. The mappable Simply fields

Nine Simply line-item fields can be filled. Each maps from one source:

Mapping keySimply fieldTypical use
departmentDepartmentTop-level merchandise department
categoryCategoryCodeProduct category
subDept1SubDept1Sub-department level 1
subDept2SubDept2Sub-department level 2
subDept3SubDept3Sub-department level 3
vendorVendorSupplier / brand
pluClassification1PLUClassificationCode1Classification code 1
pluClassification2PLUClassificationCode2Classification code 2
pluClassification3PLUClassificationCode3Classification code 3

A field is only written when its rule resolves to a value — unresolved fields are omitted, never set to an empty string.


4. How a value is resolved

Each of the nine fields has a rule:

{ source: 'tags' | 'collections' | 'metafield' | 'none', key?: string }
sourceHow the value is resolved
tagsJoins the id of every tag on the line with , (e.g. t1, t2). Resolves to nothing if the line has no tags.
collectionsJoins the id of every collection on the line with , . Entries missing an id are skipped (and logged).
metafieldRequires a key. Finds the metafield whose id === key and uses its value (coerced to a string).
noneField is not mapped. (Default for every field.)

Tags & collections are bulk by nature. Because they join all entries on the line, point a single Simply field (e.g. Department) at tags only when a line carries exactly the tag(s) you want there. For one-value-per-field precision, prefer metafield.

Example

A line item like:

{
  "id": 44550337626248,
  "sku": "1004",
  "extended_attributes": {
    "tags": [{ "id": "12", "value": "Womenswear" }],
    "metafields": [
      { "id": "custom.vendor_code", "value": "ACME" },
      { "id": "custom.category", "value": "337" }
    ]
  }
}

with this mapping:

{
  "department": { "source": "tags" },
  "category":   { "source": "metafield", "key": "custom.category" },
  "vendor":     { "source": "metafield", "key": "custom.vendor_code" }
}

produces these fields on the Simply line item:

{ "Department": "12", "CategoryCode": "337", "Vendor": "ACME" }

5. Configuring a POI

Mapping is stored per POI under settings.shopifySimplyAttributeMapping:

{
  active: boolean,                  // master switch — defaults to false
  mapping: { /* the 9 rules */ },
  discoveredMetafieldKeys: string[] // see §5.2
}

5.1 Management API

These endpoints are vendor-authenticated (Dashboard) and POI-scoped.

GET  /api/v1/poi/:poiId/attribute-mapping
PUT  /api/v1/poi/:poiId/attribute-mapping
GET  /api/v1/poi/:poiId/attribute-mapping/metafield-keys

GET returns the current configuration (with defaults filled in for any unset rule):

{
  "storeId": "65f...",
  "active": true,
  "mapping": {
    "department": { "source": "tags" },
    "category":   { "source": "metafield", "key": "custom.category" },
    "subDept1":   { "source": "none" },
    "subDept2":   { "source": "none" },
    "subDept3":   { "source": "none" },
    "vendor":     { "source": "metafield", "key": "custom.vendor_code" },
    "pluClassification1": { "source": "none" },
    "pluClassification2": { "source": "none" },
    "pluClassification3": { "source": "none" }
  },
  "discoveredMetafieldKeys": ["custom.category", "custom.vendor_code"]
}

PUT persists { active, mapping }. The body is validated: active must be a boolean, and any rule with source: "metafield" must include a non-empty key. Unknown / omitted rules default to { source: "none" }. The response uses the standard envelope:

{ "success": true, "data": { "...same shape as GET..." }, "error": null }
curl -X PUT https://dropins.simplyclub.co.il/api/v1/poi/65f.../attribute-mapping \
  -H "Authorization: Bearer <vendor-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "active": true,
    "mapping": {
      "department": { "source": "tags" },
      "category":   { "source": "metafield", "key": "custom.category" },
      "vendor":     { "source": "metafield", "key": "custom.vendor_code" }
    }
  }'

5.2 Metafield-key discovery

You don’t have to know your metafield IDs up front. As carts and orders flow through, the backend records every metafield id it sees into discoveredMetafieldKeys (best-effort, non-blocking). The Dashboard surfaces these as a picklist when you choose source: metafield, so the keys your storefront actually emits become selectable. GET …/attribute-mapping/metafield-keys returns just that list.


6. Channel support

Despite the shopify… naming on the setting and internal types, the mapping engine has no Shopify-specific logic. It works for any storefront that emits the extended_attributes contract through the drop-in session / cart endpoints — custom HTML, WooCommerce, or anything else.

Shopify is simply the one webhook intake wired today. The drop-in session path (used by every channel) already applies the mapping.

Webhook caveat. Shopify’s orders/paid webhook frequently drops extended_attributes from its line items. To avoid losing your mapping at transaction close, the backend re-attaches the attributes from the saved cart session (matching lines by variant_id / sku) before applying the mapping. So attributes set on the cart survive into the paid-order transaction — as long as the session carried them.


7. Checklist before going live

  • Storefront emits extended_attributes (tags / collections / metafields, each { id, value }) on every relevant line in setCart.
  • You’ve chosen a source for each Simply field you need; everything else stays none.
  • Every metafield rule has a non-empty key that matches the metafield id your storefront emits.
  • active is set to true for the POI (PUT …/attribute-mapping).
  • You ran a test cart and confirmed the expected Simply fields appear (GET …/attribute-mapping/metafield-keys shows your keys were seen).
  • For Shopify: confirmed the session carries the attributes so the webhook merge can restore them at orders/paid.

8. Troubleshooting

SymptomLikely cause
Fields never appear on the transactionMapping is not active for the POI, or no rule points at a populated source.
A metafield field is always emptyThe rule’s key doesn’t match any metafield id on the line, or the storefront isn’t sending that metafield. Check discoveredMetafieldKeys.
Department has several comma-joined valuessource: tags/collections joins all entries on the line. Use metafield for a single value.
Attributes present on the cart but missing at orders/paidThe Shopify webhook dropped them and the session didn’t carry them (no variant_id/sku match for the merge). Verify the session cart included extended_attributes.
PUT rejected with a validation erroractive must be boolean; every metafield rule needs a non-empty key.