Models

Please have a look at our Vocabulary for an explanation of the overall concepts used in the Ka-ching system.

Each of the models described below have a lot of related concepts. For instance the sale describes recorded discounts in context of a sale. As such the description of this discount representation can be found in the documentation for the sale.

Pervasive to the entire system is the concept of the localizable string, so this is explained here first:

L10nString

Many text properties in the Ka-ching system are localizable by default. As this is not always desirable or practical, we model this using the L10nString construct. This construct allows us to have either a plain string, which carries no additional information, or a localized string, which carries localizations for numerous languages.

Examples:

Unlocalized

{
    "name": "Chair"
}

Localized

{
    "name": {
        "da": "Stol",
        "de": "Stuhl",
        "en": "Chair"
    }
}

Product

First of all, an example of a product with localized name and description properties:

{
  "barcode" : "5714081000426",
  "cost_price" : 10,
  "description" : {
    "en" : "This is a really weird product. Transparent Cola must be a Danish speciality.",
    "da" : "Lækker og sund sportsdrik. Gør dig sandsynligvis til et bedre menneske."
  },
  "id" : "-Kaw7xNDADAjHKSpWRqJ",
  "image_url" : "https://cdn.shopify.com/s/files/1/0017/4184/8641/files/ka-ching-logo_360x.png?v=1522756522",
  "name" : {
    "da" : "Klar cola",
    "en" : "Transparent Cola",
  },
  "product_group" : "beverages",
  "retail_price" : 30,
  "taxes" : [
    {
      "name" : "Speciel moms",
      "rate" : 0.13,
      "type" : "vat"
    }
  ]
}

The only required properties of a product is id and name. If a product has no retail_price, it is interpreted to be a variable price product, where the price is entered at the time of sale.

Taxes generally do not need to be specified, since the market in which a product is sold defines a default tax.

Bar codes can be used for EAN, SKU, ISBN or other means of identifying a product by it's bar code.

Cost prices are optional, and are not directly displayed in the POS, but since they are needed in order to calculate margins, we recommend to include this information whenever possible.

Please refer to the Import Integrations for further details about the product model.

Sale

A sale is a record of a completed sale in the system. The record contains all the data that is relevant to the sale, and the data in here also serves as a building block for generating receipts.

In the root of the sale we define basic properties of the sale:

{
    "base_currency": "<string, required, ISO 4217 currency code>",
    "identifier": "<string, required>",
    "payments": [...],
    "receipt_metadata": [...],
    "sequence_number": "<number, requred>",
    "source": [...],
    "summary": [...],
    "timing": [...],
    "voided": "<boolean, required>"
}

sale.payments

There will be one entry per payment method. For instance a sale that totals 100 DKK may be paid with 50 DKK on credit card and 50 DKK in cash. Two special payment entries will often occur when dealing with cash payments: rounding and cashback. Rounding is added in countries where the lowest currency denomination is greater than the lowest payable amount. For instance in Denmark the smallest denomination is 0.50 DKK (See the wikipedia entry: Swedish Rounding)

[
    {
       "amount": "<number, required, the paid amount with any decimals>",
       "foreign_currency": "<string, optional, ISO 4217 currency code>",
       "foreign_currency_amount": "<number, optional>",
       "identifier": "<string, required>",
       "payment_type" : "<string, required, card, cash, gift card, etc>",
       "success" : "<boolean, required, true if the payment succeeded>"
    }
]

sale.receipt_metadata

Optional element containing receipt metadata.

{
        "footer": "<string, optional>",
        "header": "<string, optional>",
        "shop_address" : {
            "city": "<string, optional>",
            "name": "<string, optional>",
            "street": "<string, optional>",
            "zip": "<string, optional>"
        }
    }

sale.source

{
    "market_id": "<identifier of the market in which the sale was made>",
    "market_name": "<market name>",
    "shop_id": "<identifier of the shop in which the sale was made>",
    "shop_name": "<name of the shop in which the sale was made>",
    "register_id": "<identifier of the register the performed the sale>",
    "register_name": "<register name>",
    "cashier_id": "<cashier identifier>",
    "cashier_name": "<cashier name>"
}

sale.summary

This summary both contains sub totals, totals, taxes, discounts, etc. for the entire sale - and also for each of the line items in the sale.

{
    "base_price": "<number, required>",
    "comment": "<string, optional>",
    "customer": [...],
    "is_return": "<boolean, required>",
    "line_items": [...],
    "margin": "<number, optional>",
    "margin_total": "<number, optional>",
    "purchase_type": "<..., optional>",
    "return_reference": "<string, optional>",
    "sales_tax_amount": "<number, required>",
    "sub_total": "<number, required>",
    "taxes": [...],
    "total": "<number, required>",
    "total_discounts" : "<number, required>",
    "total_tax_amount" : "<number, required>",
    "vat_amount" : "<number, required>"
}

Note that if the is_return flag is true, then the summary represents a return of goods.

sale.timing

This is a representation of the point in time in which the sale was made - as well as the time zone. These two pieces of information are used to generate a few representations of the time stamp for convenience. See the tz database wikipedia entry for more information.

{
    "timestamp": "<unix time stamp with decimals, number>",
    "timestamp_string": "<ISO 8601 textual representation of time with time zone>",
    "timezone": "<standard area/location string representation of a timezone as defined in the tz database>",
    "timestamp_date_string": "<convenience representation of the timestamp down to the minute: year-month-day-hour-minute>",
    "timestamp_week_string": "<convenience representation of the timestamp week: weekyear-week. Note that weekyear is NOT the same as year>",
}

sale.summary.customer

The identifier of the customer that might be associated with a given sale. Due to GDPR compliance, we do specifically not add any customer details other than the identifier which should be sufficient to identify the customer when a sale is exported to an integrating system.

{
    "identifier": "<string, required>"
}

sale.summary.line_items

A line item contains information about the product that it represents:

{
    "barcode": "<string, optional>",
    "base_price": "<number, required>",
    "cost_price": "<number, optional>",
    "dimensions": [...],
    "discounts": [...],
    "ecom_id": "<string, optional>",
    "id": "<string, optional, product identifier>",
    "image_url": "<string, optional>",
    "line_item_id": "<string, required>",
    "margin": "<number, optional>",
    "margin_total": "<number, optional>",
    "name": "<l10nstring, optional>",
    "product_group": "<string, optional>",
    "quantity": "<number, required>",
    "retail_price": "<number, required>",
    "sales_tax_amount": "<number, required>",
    "sub_total": "<number, required>",
    "tags": [...],
    "taxes": [...],
    "total": "<number, required>",
    "total_tax_amount": "<number, required>",
    "variant_id": "<string, optional>",
    "vat_amount": "<number, required>"
}

sale.summary.line_items[n].dimensions

If dimensions are used to describe the variant of this product, this will contain one or more dimensions.

{
    "id": "<string, required>",
    "name": "<l10nstring, optional>",
    "value": {
        "id": "<string, required>",
        "name": "<l10nstring, required>",
        "color": "<string, optional, formatted as a hex color #AABBCC>"
    }
}

sale.summary.line_items[n].discounts

See Discount Summary

sale.summary.purchase_type

A purchase type can be a special price across all sold products.

{
    "name": "<string, required>",
    "identifier": "<string, required>"
}

sale.summary.line_items[n].tags

List of associated tags.

Products can be attributed with any number of informal tags. These are for instance currently being used for category filtering in the POS. These are included in the line item details as an array of strings for reference.

sale.summary.taxes and sale.summary.line_items[n].taxes

List of taxes in this sale / involving this line item.

See Tax Summary for details.

An example of a complete sale:

{
  "base_currency_code" : "DKK",
  "identifier" : "DBC5D956-6945-414C-B872-90137F113CD8",
  "payments" : [ {
    "amount" : 500,
    "identifier" : "D2286271-4F30-4395-914F-5A98AA26C0B0",
    "payment_type" : "card.external",
    "success" : true
  }, {
    "amount" : 3310,
    "identifier" : "4547E098-57D5-4F40-9EA7-DE8557F2841C",
    "payment_type" : "cash",
    "success" : true
  }, {
    "amount" : 0.05,
    "identifier" : "E44E3F45-D9CC-4ED3-A3D3-CBA911068C69",
    "payment_type" : "cash.rounding",
    "success" : true
  } ],
  "receipt_metadata" : {
    "footer" : "Hav en god dag\nVarer ombyttes ikke.",
    "header" : "Tak for handlen!",
    "shop_address" : {
      "city" : "Aarhus",
      "name" : "Ka-ching, Aarhus",
      "street" : "Katrinebjergvej 115",
      "zip" : "8200"
    },
    "vat_number" : "38683829"
  },
  "sequence_number" : 1,
  "source" : {
    "cashier_id" : "01955FE1-A5DE-431F-ACBA-97B09F3FA6A3",
    "cashier_name" : "USC",
    "market_id" : "dk",
    "market_name" : "Danmark",
    "register_id" : "C2FD043D-4719-4A69-BBFD-5DDFF0EFB8DC",
    "register_name" : "iPad Pro (9.7-inch)",
    "shop_id" : "aarhus",
    "shop_name" : "Ka-ching, Aarhus"
  },
  "summary" : {
    "base_price" : 3048.04,
    "is_return" : false,
    "line_items" : [ {
      "barcode" : "4210024",
      "base_price" : 2872,
      "dimensions" : [ {
        "id" : "color",
        "name" : "Color",
        "value" : {
          "id" : "warm_white_brass",
          "name" : "Warm white/Brass"
        }
      } ],
      "id" : "1048",
      "image_url" : "https://cdn.shopify.com/s/files/1/0017/4184/8641/files/ka-ching-logo_360x.png?v=1522756522",
      "line_item_id" : "E2B20A95-69E0-421A-9D2A-354CDFCC1A5A",
      "name" : {
        "da" : "Ambience Bordlampe",
        "en" : "Ambience Table lamp, Sparkling rose"
      },
      "quantity" : 2,
      "retail_price" : 3590,
      "sales_tax_amount" : 0,
      "sub_total" : 3590,
      "tags" : [ "bordlamper", "belysning" ],
      "taxes" : [ {
        "amount" : 718,
        "name" : "Moms",
        "rate" : 0.25,
        "type" : "vat"
      } ],
      "total" : 3590,
      "total_tax_amount" : 718,
      "variant_id" : "1053",
      "vat_amount" : 718
    }, {
      "base_price" : 136.04,
      "dimensions" : [ {
        "id" : "BB0440B7-993B-4D07-A83E-AD59BD5E1862",
        "name" : "Color",
        "value" : {
          "color" : "#AA8800",
          "id" : "7D72EC85-28BE-417F-9D3C-C5D7F66A4CF7",
          "name" : "Orange"
        }
      }, {
        "id" : "40982BAE-E1C9-4CB4-B639-3F678B19EF83",
        "name" : "Size",
        "value" : {
          "id" : "8D878328-10BE-4F02-ADC0-5C1A55A49A51",
          "name" : "M"
        }
      } ],
      "discounts" : [ {
        "amount" : 8.95,
        "discount" : {
          "application" : {
            "line_item" : "849D58D8-F0D4-45ED-B25B-C77BA2C6C388"
          },
          "id" : "39973A80-17F5-4171-8101-FF3C964581C1",
          "mode" : {
            "manual" : true
          },
          "type" : {
            "percentage" : 0.05
          }
        }
      } ],
      "id" : "prodId-36601",
      "image_url" : "https://cdn.shopify.com/s/files/1/0017/4184/8641/files/ka-ching-logo_360x.png?v=1522756522",
      "line_item_id" : "849D58D8-F0D4-45ED-B25B-C77BA2C6C388",
      "name" : "Asics Dame Allover Graphic Kortærmet Top",
      "quantity" : 1,
      "retail_price" : 179,
      "sales_tax_amount" : 0,
      "sub_total" : 170.05,
      "taxes" : [ {
        "amount" : 34.01,
        "name" : "Moms",
        "rate" : 0.25,
        "type" : "vat"
      } ],
      "total" : 170.05,
      "total_tax_amount" : 34.01,
      "variant_id" : "6408E763-DF40-4A4F-8D24-36C65F6C1405",
      "vat_amount" : 34.01
    }, {
      "base_price" : 40,
      "cost_price" : 70,
      "id" : "asteroids",
      "image_url" : "https://cdn.shopify.com/s/files/1/0017/4184/8641/files/ka-ching-logo_360x.png?v=1522756522",
      "line_item_id" : "30838E39-525D-4F66-9037-5321472EB997",
      "margin" : -30,
      "margin_total" : 40,
      "name" : "Asteroids",
      "quantity" : 1,
      "retail_price" : 50,
      "sales_tax_amount" : 0,
      "sub_total" : 50,
      "tags" : [ "retro" ],
      "taxes" : [ {
        "amount" : 10,
        "name" : "Moms",
        "rate" : 0.25,
        "type" : "vat"
      } ],
      "total" : 50,
      "total_tax_amount" : 10,
      "vat_amount" : 10
    } ],
    "margin" : -30,
    "margin_total" : 40,
    "sales_tax_amount" : 0,
    "sub_total" : 3810.05,
    "taxes" : [ {
      "amount" : 762.01,
      "name" : "Moms",
      "rate" : 0.25,
      "type" : "vat"
    } ],
    "total" : 3810.05,
    "total_discounts" : 8.95,
    "total_tax_amount" : 762.01,
    "vat_amount" : 762.01
  },
  "timing" : {
    "timestamp" : 1.5471910861096148E9,
    "timestamp_date_string" : "2019-01-11-08-01",
    "timestamp_string" : "2019-01-11T07:18:06Z",
    "timestamp_week_string" : "2019-02",
    "timezone" : "Europe/Copenhagen"
  },
  "voided" : false
}

Discount Summary

As a part of a sale, we record detailed information about all the discounts applied to a sale. This information includes the discount amount, but also information about the type of discount and how it was applied. All this can be used to trace the origin of the applied discounts.

If you are only interested in the discount amount, please disregard all other fields than the amount.

{
  "amount" : "<number, required>",
  "discount" : {
    "application" : [...],
    "id": "<string, required>",
    "mode": [...],
    "name": "<L10nString, optional>",
    "type": [...]
  }
}

{
    "application": "<Application, required>",
    "id": "<identifier, required>",
    "mode": "<Mode, required>",
    "name": "<L10nString, optional>",
    "trigger": "<Trigger, optional>"
    "type": "<DiscountType, required>"
}

application

This can take one of three values, and is what this discount is applied to.

basket applies to the entire basket.

{
    "basket": "<boolean, required, always true>"
}

line_item applies to each item in a line item

{
    "line_item": "<string, required, line item identifier>"
}

items applies to individual item ranges that may be part of several line items. For instance a reference to a line item with a quantity of three could reference just one of the three. This is done using a map from the line item identifier to either an index or a range index.

{
    "line_items": [
        "<line_item_identifier>": {
            "index": "<number, required>"
        }
    }]
}
{
    "line_items": [
        "<line_item_identifier>": {
            "start_index": "<number, required>",
            "end_index": "<number, required>"
        }
    }]
}

mode

Manual discount, added by cashier

{
    "manual": true
}

Automatic discount, added by the system - either from a sale discount on the product or from a triggered discount rule.

{
    "automatic" : {
        "domain" : "<string, required>",
        "identifier" : "<string, required>"
    }
}

domain can be one of the following:

  • KX_SALE_PRICE_DOMAIN if triggered by a product sale price

  • KX_PROMOTION_DOMAIN if triggered by a promotion campaign

discount type

The type of discount given.

  • amount fixed discount.

  • amount_per_item fixed discount per item.

  • percentage percentage discount.

  • percentage_with_max percentage discount with a maximum value given.

  • new_price replaces the retail price of the line with a new price.

  • new_price_per_item replaces the retail price of every item in a line with a new price.

Trigger

This optional element is present if the discount was triggered by a promotion campaign.

{
    items: "<required>",
    count: "<number, required>"
}

items can be on of the following:

  • basket triggered by the entire basket

  • line_item triggered by an entire line item

  • items triggered by individual line items, that can be part of several line items

Tax Summary

As described in the Vocabulary, a tax has a type (VAT or Sales Tax). Furthermore it has a tax rate and a name.

{
    "amount": "<number, required>",
    "rate": "<number, required>",
    "tax_name": "<string, required>",
    "type": "<string, required, can be vat or sales_tax>"
}

Register Close Statement

A register close statement is a record of a register close action in the POS, with any reconciliation details added. The record contains all the data that is relevant to the register close, and the data in here also serves as a building block for generating register close receipts (the Z-report).

In the root of the register close statement, we define basic properties of the statement:

{
    "base_currency_code": "<string, required, ISO 4217 currency code>",
    "reconciliation_time": "<unix time stamp with decimals, number>",
    "sequence_number": "<number, requred>",
    "shop_info": [...],
    "source": [...],
    "register_summary": [...],
    "reconciliations": [...]
}

shop_info

Optional element containing shop information.

{
        "vat_number": "<string, optional>",
        "shop_address" : {
            "city": "<string, optional>",
            "name": "<string, optional>",
            "street": "<string, optional>",
            "zip": "<string, optional>"
        }
    }

reconciliations

An array of reconciliations - one per payment type configured for the account. For cash payments there will be an additional entry per foreign currency that has been used for cash payment in the current register period.

{
        "counted": "<number, required>",
        "currency_code":<string, required, ISO 4217 currency code>",
        "deposited_amount": "<number, optional>",
        "depositing": "<string, required, 'manual', 'automatic' or 'none'>",
        "payment_type_identifier": "<string, required>",
        "should_be_reconciled": "<bool, required>",
        "total": "<number, required>"
    }

Only certain payment types require reconciliation, this fact is represented by the 'should_be_reconciled' flag. For all payment types that do not require reconciliation, the 'counted' property will always equal the 'total'.

Only cash payment needs depositing. Cash in base currency has manual depositing (the cashier describes how much is deposited) while currently all foreign currencies have automatic depositing (meaning that all foreign cash in the register is deposited at register close). Other payment types will have 'none' for the 'depositing' property.

The payment type identifier is a reference to the payment type in the Ka-ching system. This can be one of (but not limited to): cash, card.external, card.payex, card.izettle, giftcard.external, giftcard.integration, mobilepay.external, mobilepay.integration.

register_close_statement.register_summary

{
        "all": "<sales summary>",
        "all_by_product_group": "<map from product group to sales summary>",
        "sales": "<sales summary>",
        "sales_by_product_group": "<map from product group to sales summary>",
        "returns": "<sales summary>",
        "returns_by_product_group": "<map from product group to sales summary>",
        "cash_drawer_open_count": "<number, required>",
        "cash_total_at_open": "<number, required>",
        "id": "<string, required>",
        "number_of_aborted_sales": "<number, required>",
        "number_of_initiated_sales": "<number, required>",
        "opened_at": "<unix time stamp with decimals, number>",
        "voided_count": "<number, required>"
    }

The all key contains a total summary of both sales and returns. Since the values in the returns summary are negative, the amounts in the all summary can be less than those in the sales summary.

The sales key contains a similar summary, but only accounting for the sales, not the returns.

The returns key contains a similar summary, but only accounting for the returns, not the sales.

The *_by_product_group contain a similar summary, but grouped into the ids of the sold items. Any sold items that do not belong to a product group are grouped into the special __none__ group.

sales summary

{
        "base_price": "<number, required>",
        "margin": "<number, required>",
        "margin_total": "<number, required>",
        "sub_total": "<number, required>",
        "total": "<number, required>",
        "total_discounts": "<number, required>",
        "total_tax_amount": "<number, required>",
        "vat_amount": "<number, optional>",
        "sales_tax_amount": "<number, optional>",
        "count": "<number, required>",
        "item_count": "<number, required>",
        "payments": [...],
        "tax_summaries": [...]
    }

sales summary.payments[n]

{
        "totals": {
          "base_currency_total": "<number, required>",
          "foreign_currencies": "<optional map of totals grouped by currency code>",
          "total": "<number, required>"
        },
        "type": "<string, required>"
}

The payments array has an entry per payment type used in the register period. The type specifies the payment type in question. The base_currency_total specifies the total amount paid in the base currency, where the foreign_currencies map contain all totals for all foreign payments in the foreign currency. The total specifies the total value in the base currency when converting all totals to base currency and adding them together.

The sales summary will also contain an entry for the cash.rounding pseudo payment type that is used to aggregate all rounding errors due to Swedish Rounding

sales summary.tax_summaries[n]

{
        "amount": "<number, required>",
        "rate": "<number, required>",
        "name": "<string, required>",
        "type": "<string, required, vat or sales_tax>"
}

The tax_summaries array has an entry per tax registered during the register period. The entries correspond to the [Tax Summary>(#tax-summary) also used in the sale entity.

register_close_statement.source

{
    "market_id": "<identifier of the market in which the register close was performed>",
    "market_name": "<market name>",
    "shop_id": "<identifier of the shop in which the register close was performed>",
    "shop_name": "<name of the shop in which the register close was performed>",
    "register_id": "<identifier of the register that was closed>",
    "register_name": "<register name>",
    "cashier_id": "<cashier identifier>",
    "cashier_name": "<cashier name>"
}