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"
}
}
Market Price
Every time you specify a price in Ka-ching you have to think about markets. If you have more than one market defined on your account, it's likely that you have different prices for each product in each market. For example a can of Coca Cola could be 10 kr in Denmark and 13 kr in Sweden.
So there are to ways to specify a price in Ka-ching. Take a look at the examples below.
Same price for all markets
{
"retail_price": 10
}
Difference price for each market
{
"retail_price": {
"dk": 10,
"no": 13,
"se": 13
}
}
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 (or return) 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>",
"error": "<string, optional, filled if success is false>",
"metadata: "<dictionary, optional, with misc. data related to the payment>",
"receipts: "<array, optional, array with report models representing receipts>"
}
]
The metadata
and receipts
fields are usually ignored by integrators as the content is usually only relevant in the Ka-ching system. If you need or want to parse it, please contact us for further info about the data.
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, name and address is only populated if the customer has an organization number, with the assumption that it's then a business customer.
{
"identifier": "<string, required>",
"name": "<string, optional>",
"street": "<string, optional>",
"city": "<string, optional>",
"postal_code": "<string, optional>",
"city": "<string, optional>",
"country": "<string, optional>",
"country_code": "<string, ISO 3166-1 alpha-2, optional>"
"email": "<string, optional>",
"phone": "<string, optional>",
"organization_number": "<string, for business customers, optional>"
}
sale.summary.line_items
A line item contains information about the product that it represents:
{
"barcode": "<string, optional>",
"base_price": "<number, required>",
"behavior": [...], optional see below
"cost_price": "<number, optional>",
"dimensions": [...], optional see below
"discounts": [...], optional see below
"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": [...], optional see below
"taxes": [...], optional see below
"total": "<number, required>",
"total_tax_amount": "<number, required>",
"variant_id": "<string, optional>",
"vat_amount": "<number, required>",
"comment": "<string, optional>"
}
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.
sale.summary.line_items[n].behavior
If the behavior field is present it means that the line item has special behavior. Currently we have 8 different line item behaviors:
shipping
giftcard
voucher
giftcard_use
voucher_use
expense
customer_account_deposit
container_deposit
Only one of these fields can be present on one line item. Not all accounts use all of these behaviors, so it's not always necessary to implement handling of all of them right away.
sale.summary.line_items[n].behavior.shipping
{
"address" : [...], optional,
"customer_info" : [...], optional,
"method_id" : "<string, optional>
}
The shipping data is related to all line items with the same value in ecom_id
as the line item with the shipping behavior. At the moment we only support 1 ecommerce order per checkout in POS, but in the future it may be possible to have more than 1 per checkout. So to process an ecommerce order made in POS, find all items with ecom_id
!= null and group them per ecom_id
value. In each group a line item with shipping behavior should be present.
sale.summary.line_items[n].behavior.shipping.address
{
"city" : "<string, optional>,
"country" : "<string, optional>,
"country_code" : "<string, two digit ISO country code, optional>,
"name" : "<string, optional>,
"postal_code" : "<string, optional>,
"street" : "<string, optional>
}
sale.summary.line_items[n].behavior.shipping.customer_info
{
"email" : "<string, optional>,
"phone" : "<string, optional>
}
sale.summary.line_items[n].giftcard
{
"code" : "<string, required>",
"integration_id" : "<string, required>",
"payment_type_id" : "giftcard.integration",
"report": [...], optional, used by Ka-ching to generate receipt so it's safe to ignore
}
sale.summary.line_items[n].voucher
{
"code" : "<string, required>",
"integration_id" : "<string, required>",
"payment_type_id" : "voucher.integration",
"report": [...], optional, used by Ka-ching to generate receipt so it's safe to ignore
}
sale.summary.line_items[n].giftcard_use
{
"code" : "<string, required>",
"integration_id" : "<string, required>",
"payment_type_id" : "giftcard.integration"
}
sale.summary.line_items[n].voucher_use
{
"code" : "<string, required>",
"integration_id" : "<string, required>",
"payment_type_id" : "voucher.integration"
}
sale.summary.line_items[n].expense
{
"identifier" : "<string, required>",
"image_urls" : <string array, required>
}
sale.summary.line_items[n].customer_account_deposit
{
"customer_id": "<string, required>",
"payment_type_id": "<string, required>"
}
sale.summary.line_items[n].container_deposit
{
"product_id": "<string, required>",
"tag": "<string, required>",
"rule_id": "<string, required>",
"parent_line_item_id": "<string, required>"
}
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>"
}
Checkouts
To understand the Checkout
entity for existing integratores, we'll start with a brief history of the Sale
entity
First step - keeping separate concepts separate
When we built the first version of the Ka-ching POS, we had some previous experience relating to fiscalization and the issues with having to identify sales and returns for the purpose of Electronic Journals and statistics. With this previous experience a ‘transaction’ or ‘checkout’ (the process of a customer making a purchase or a return or both) was identified as a ‘return’ in case the total was negative and a ‘sale’ otherwise. This was not a very accurate representation of the truth, so with Ka-ching we decided to be more detailed in our modelling of the world.
We built the ‘Sale’ entity and this entity could either represent an actual sale or a return - but not both at once. With this we were much more explicit about logging and counting of what was going on. Initially we could actually also only handle either a sale or a return in a single ‘transaction’ or ‘checkout’ - so back then there was a 1 to 1 between the checkout and the ‘Sale’ entity created.
Everything in a single checkout
After a while we then got the possibility to handle both sales and returns in a single ‘transaction’ or ‘checkout’. In order to stay compatible with previous versions - and also with the fiscalization logging and journalling, we built this flow so that we still create ‘Sale’ entities, but from a single ‘checkout’ we then generate several ‘Sale’ entities where one may represent a return and another a sale. All sale entities are marked with the same ‘checkout_identifier’ to show that they come from the same checkout.
Frozen baskets
The concept of being able to produce several sales from a single checkout had further advantages. There is a key difference between the basket of an ongoing sale - and then a basket representing returned items. The former is ‘live’ and totals, taxes and discounts are evaluated continuously as the contents change. The latter is static or frozen - it actually represents a summary of a previous sale, so it does not make sense to apply discounts on the returned items or try recalculating taxes, etc.
Other entities could take advantage of the concept of frozen baskets. For instance a sales quote that is added to the basket is basically an agreement between the customer and a sales person about a final price for a specific purchase of items. This means that these should again be kept separate from the live basket and always represent the sales quote from when the terms were agreed upon.
The drawback
With regards to external systems, not everyone are able to collect the separate sales that make up an entire checkout. We agree that the task of collecting these into one entity is indeed our responsibility - because otherwise all (or many) third party integrators end up having to implement this logic themselves. The current collection step is enabled by a flag on the sales export configuration. When this is present we have a service that groups sales together - and after a period of idle time the entity is exported if all sales are present. Unfortunately this heuristics about waiting for a certain time period is both error prone (things take different time based on a lot of external conditions) and a bit expensive (having microservices ‘wait’ for something basically means that we have to pay for the awaited idle time). We can do better.
Introduction of the Checkout entity
Having a dedicated Checkout entity is of course the answer to the problem.
The first requirement is the definition of the entity and then being able to create it from a list of sales. Secondly we need to hook this entity up with our export engine, and that basically gives us the bare minimum to start deprecating the sales grouping mentioned above.
But having a dedicated checkout entity is basically a more correct modelling of the environment, so there are many future steps we could take with this entity.
checkout
{
"identifier": <String, required, the identifier for the checkout>,
"voided": <bool, optional, true if the checkout was voided during payment>,
"timing": <see sale.timing, required>,
"source": <see sale.source, required>,
"base_currency_code": "<string, required, ISO 4217 currency code>",
"payments": <see sale.payments>,
"receipt_metadata": <see sale.receipt_metadata, optional>,
"sales": [array of sale parts]
}
checkout.sale Similar to the sale entity, but without the things that are already present on the checkout and would thus be redundant:
{
"identifier": <String, required, the identifier for the sale>,
"sequence_number": <number, required, sequence number for the sale>,
"basket": <see sale.summary>,
"fiscal_rules_data": <see sale.fiscal_rules_data, optional>
}
Note the renaming of the 'summary' from a sale to the term 'basket' here. We think that this provides a better description of the contained data.
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>",
"comment": "<string, optional, reason for reconciliation difference entered by cashier>",
"reconciliation_time": "<unix time stamp with decimals, number>",
"sequence_number": "<number, requred>",
"shop_info": [...],
"source": [...],
"register_summary": [...],
"reconciliations": [...],
"timing": [...]
}
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>",
"credit": "<number, optional, sum of positive payments>",
"debit": "<number, optional, sum of negative payments>",
"transaction_count": "<number, required, number of transactions>",
"transaction_count_credit": "<number, optional, count of positive payment transactions>",
"transaction_count_debit": "<number, optional, count of negative payment transactions>"
}
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>",
"expenses": "<sales summary>",
"cash_drawer_open_count": "<number, required, may be deprecated in the future. The same value can be found in 'event_counts'>",
"cash_total_at_open": "<number, required>",
"id": "<string, required>",
"number_of_aborted_sales": "<number, required, may be deprecated in the future. The same value can be found in 'event_counts'>",
"number_of_initiated_sales": "<number, required, may be deprecated in the future. The same value can be found in 'event_counts'>",
"opened_at": "<unix time stamp with decimals, number>",
"voided_count": "<number, required, may be deprecated in the future. The same value can be found in 'event_counts'>",
"event_counts": <event counts dictionary>
}
The all
key contains a total summary of both sales, returns and expenses. Since the values in the returns
and expenses
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 expenses
key contains a similar summary, but only accounting for expenses.
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, the total paid in base currency>,
"base_currency_total_credit" : <number, optional, as above, but only positive amounts>,
"base_currency_total_debit" : <number, optional, as above, but only negative amounts>,
"base_currency_transaction_count" : <number, the count of payments in base currency>,
"base_currency_transaction_count_credit" : <number, optional, as above, but only counting positive payments>,
"base_currency_transaction_count_debit" : <number, optional, as above, but only counting negative payments>,
"total" : <number, required, the total paid in all currencies (converted to base currency)>,
"total_credit" : <number, optional, as above, but only positive amounts>,
"total_debit" : <number, optional, as above, but only negative amounts>,
"transaction_count" : <number, a count of all transactions>,
"transaction_count_credit" : <number, optional, a count of all transactions with positive amounts>,
"transaction_count_debit" : <number, optional, a count of all transactions with negative amounts>,
"foreign_currencies": "<optional map of totals grouped by currency code>",
"foreign_currencies_credit": "<optional map of positive payments grouped by currency code>",
"foreign_currencies_debit": "<optional map of negative payments grouped by currency code>",
"foreign_currencies_transaction_count": "<optional map of transaction counts grouped by currency code>",
"foreign_currencies_transaction_count_credit": "<optional map of positive transaction counts grouped by currency code>",
"foreign_currencies_transaction_count_debit": "<optional map of negative transaction counts grouped by currency code>"
},
"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 also used in the sale
entity.
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 also used in the sale
entity.
event_counts
{
"app_launch" : <number, optional>,
"app_quit" : <number, optional>,
"cash_drawer_opened" : <number, optional>,
"cashier_selected" : <number, optional>,
"discount_added" : <number, optional>,
"expenses" : <number, optional>,
"line_item_added" : <number, optional>,
"line_item_summaries_added" : <number, optional>,
"register_opened" : <number, optional>,
"sale" : <number, optional>,
"sale_initiated" : <number, optional>
}
The event counts dictionary contains a number of counts of events that happened while the register was opened. These events are the same as those that you can find in the Electronic Journal in the POS.
Here is the full list of events that are currently being counted:
cash_drawer_opened
register_opened
register_closed
return
void
sale
expenses
sale_aborted
sale_initiated
x_report_generated
line_item_added
line_item_removed
line_item_quantity_adjusted
discount_added
discount_removed
customer_added
customer_removed
app_launch
app_quit
cashier_selected
line_item_summaries_added
line_item_summaries_removed
sales_quote_given
basket_saved
post_payment_fiscal_event
pre_copy_print_fiscal_event
payment_reversal
receipt_print_copy
register_close_statement.timing
{
"timestamp" : <number, milliseconds since 1/1-1970, UTC>,
"timestamp_date_string" : "<string, dash-separated date and time components in local timezone. E.g. 2020-05-06-12-27>",
"timestamp_string" : "<string, ISO 8601 representation of UTC time>",
"timestamp_week_string" : "<string, dash-separated weekyear and week in local timezone. E.g. 2020-19. Notice that weekyear and year are two separate concepts>,
"timezone" : "<string, TZ database name, e.g. Europe/Copenhagen>"
}
Expense
An expense is represented by an id
, a name
and an optional list of taxes
:
{
"expenses": [
{
"id": "0001",
"name": "Forplejning",
"taxes": [ {
"name": "Ingen moms",
"rate": 0,
"type": "vat"
} ]
},
{
"id": "0002",
"name": "Forplejning ude af huset",
"taxes": [ {
"name": "Kvartmoms",
"rate": 0.0625,
"type": "vat"
} ]
},
{
"id": "0003",
"name": "Andet"
}
]
}
The name
property is an L10nString
and may thus optionally contain localization information.
The taxes
property is either a flat list of taxes or an object with lists keyed by a market identifier.
Order
An order is a model describing an order or reservation that's been made in a different location than the pick up point. It is used when using our Reserve & Collect, Click & Collect, Ship from Store and Online order features.
Ka-ching can both receive incoming orders from external systems (or from other shops) and place orders to external systems (or to other shops).
An order contains information about items, quantities, prices and discounts. It can also contain basic information about the customer who's pickup up the order as well as information about whether it's already been paid or not.
{
"order_identifier": "<string, required>",
"basket": "<order basket, required>",
"customer": "<order customer, optional>",
"payment": "<order payment, optional>",
"shipping": <shipping data, optional>"
}
If an order does not have a payment reference, it is considered to be a 'Reserve & Collect' order which is paid upon collection in the shop.
If an order has a payment reference, but no shipping information, it is considered to be a 'Click & Collect' order, which is pre-paid but must be collected in the shop.
If an order has both payment and shipping information, it is considered to be a 'ship from store' order which is shipped to the customer by the shop receiving the order.
order.customer
{
"customer_identifier": "<string, optional>",
"name": "<string, optional>",
"email": "<string, optional>",
"phone_number": "<string, optional>",
}
order.payment
{
"reference": "<string, required>"
}
A payment reference into an external system.
order.shipping
{
"address": "<address information, required>",
"method_id": "<string, optional>",
"method_title": "<string, optional>",
"method_subtitle": "<string, optional>",
}
This information is presented when the order is prepared in Ka-ching POS. The optional method id, title and subtitle may be used to suggest an alternative shipping method than the default. These values are also shown in POS along with the order, so they can be taken into account by the cashier that is shipping off the order.
order.shipping.address
{
"name": "<string, optional>",
"street": "<string, optional>",
"city": "<string, optional>",
"postal_code": "<string, optional>",
"country": "<string, optional>",
"country_code": "<string, optional>",
}
order.basket
When placing orders in Ka-ching from external systems, we support a lean and simplified representation of a basket - as described below.
When Ka-ching places orders in different shops internally, the full fledged sale.summary
entity (described above) is being used instead.
This is also the case when using the 'Outgoing order export' for receiving e-com orders.
{
"currency": "<string, required, ISO 4217 currency code>",
"line_items": [<incoming order line items, required>],
"discounts": [<incoming order discounts, for discounts affecting the total, optional>]
}
order.basket.line_items[n]
{
"product_identifier": "<string, required>",
"variant_identifier": "<string, optional>",
"barcode": "<string, optional>",
"image_url": "<string, optional>",
"discounts": [<incoming order discounts, for discounts affecting this particular line item,optional>],
}
order.discount
{
"name": "<string, required>",
"type": "<string, required, can be amount, amount_per_item, percentage, new_price or new_price_per_item>",
"discounts": "<incoming order discount, for discounts affecting the total, optional>"
}
Customer
The bare minimum information for a customer is the customer id. Any other details like name, address, email and phone number will be of extra help for the cashier.
The additional_info
may be used to present some custom free text to the cashier that may be of relevance. It will be displayed to the cashier and could for instance include information about birthdays, customer rating or similar details from the CRM system.
[
{
"identifier": "<string, required>",
"name": "<string, optional>",
"street": "<string, optional>",
"city": "<string, optional>",
"postal_code": "<string, optional>",
"country": "<string, optional>",
"country_code": "<string, optional>",
"phone": "<string, optional>",
"email": "<string, optional>",
"additional_info": "<string, optional>",
"organization_number": "<string, optional>",
"balance": { // optional, DEPRECATED. This format is still supported, but the shorter version below without the identifier is the preferred.
"identifier": "<string, required>",
"balance": <number, required>
},
"balance": <number, optional>,
"balance_overdue": <number, optional>,
"credit_limit": <number, optional>,
"block_sales": <boolean, optional>,
}
]
As can be seen, all properties other than the identifier are optional. The more of the properties that are filled out, the better the integration will be.
Additional info
The additional_info
field is plain text that will be shown to the cashier when looking at the customer details. This can be used to convey useful extra information.
Notice the country_code
field. This may be used in conjunction with the Shipping Options
integration, where the integration can limit the accepted country codes
for shipping.
B2B customers
For B2B customers - or customers that represent other businesses, the system offers more customization: A customer is interpreted to be a 'B2B' customer if an 'organization number' is present. This value represents a VAT number or what in Danish is called a 'CVR' number.
When a sale is made to a non-B2B customer, we only store the customer identifier on the sale in order to avoid any privacy issues. But when selling to businesses we store information like the name and address, so this can be added directly to the invoices that we produce.
Credit limit and balance
Often - and especially in B2B situations - a business may choose to give their customers credit. If they do, then the 'balance' and 'credit limit' become relevant.
In case a customer has a credit limit of 1,000 DKK and a current balance of -500 (meaning that the customer owes your business 500 DKK), then the customer is still allowed a credit of 500 DKK.
This means that purchases using the payment types 'Customer Account' and 'Invoice' (which are both considered to be payment types that give the customer credit) are blocked if the purchase amount exceeds 500. Or to put it more algorithmically:
If the payment type is 'invoice' or 'customer account' and the PURCHASE AMOUNT - BALANCE > CREDIT LIMIT then the payment will be blocked and the customer needs to choose another method of payment.
Example: Purchase amount: 501 Balance: -500 Credit limit 1000 501 - (-500) = 1001 1001 > 1000, so payment is blocked.
Balance overdue and Customer Account
In case your business uses the 'Customer Account' feature, then it is possible for the customer to make a deposit to the account in the shop.
If a customer has a balance of -500 then this balance could be due to several unpaid invoices. For instance there may be an invoice for 300 and one for 200.
If the invoice for 300 is overdue, then this could be represented as:
{
"identifier": "1234",
"balance": -500,
"balance_overdue": -300
}
When adding this customer to a sale, the 'balance overdue' information will be used to prompt the cashier to ask the customer to make a 'customer account' deposit for the overdue amount of 300.
Blocking sales
Setting block_sales
to true
disallows sales to the customer entirely. This can be used to express that the customer has proven bad credit and dealings with the customer may be illegal.