Skip to content

Powered by Grav CMS + Helios Theme

Custom fields, settings and zones

The extension platform: custom fields, the add-on settings store, admin zones, schema extensions, hook points, product revisions and part payment.

What an add-on uses to put its own data and its own screens into a KahunaCart store: field definitions on any entity, the values stored under them, a namespaced settings store, the zones a widget can mount into, the schema extensions and hook points a plugin registers against, and the payment routes that act on a stored transaction or token. The product revision, schedule, prerequisite and part-payment routes are filed here too, because each of them is a record kept beside a core row rather than a change to one. See Custom fields and the settings store and Admin sections.

List custom fields

GET /api/v1/kahunacart/custom-fields Permission kahunacart.settings

Every custom field defined on this store, optionally for one entity — product, variant, order, order_item, customer, address, or an entity an add-on registered — with its type, visibility (public, readonly or internal), whether the admin list can sort and filter on it, its validation rules, its default and the blueprint fragment the admin renders it from. Also lists the entities fields may hang off and the types and visibilities a new field may declare.

Parameters

Name Type Description
entity optional string Only the fields on this entity.

Create a custom field

POST /api/v1/kahunacart/custom-fields Permission kahunacart.settings

Define a custom field on an entity. The name is lowercase snake_case and unique on the entity; the type is one of text, textarea, integer, boolean, select or json and cannot change afterwards; visibility decides where values travel — public reaches templates and the API, readonly reaches templates but cannot be written through the API, internal never reaches a template. A select needs validate.options; an integer may carry validate.min and validate.max; text may carry validate.max_length and validate.pattern.

Parameters

Name Type Description
entity required string
One of: product, variant, order, order_item, customer, address
name required string
type optional string
One of: text, textarea, integer, boolean, select, json
label_key optional string A translation key or a plain label.
help_key optional string
visibility optional string
One of: public, readonly, internal
sortable optional boolean
filterable optional boolean
required optional boolean
default optional string
validate optional object
position optional integer

Update a custom field

PATCH /api/v1/kahunacart/custom-fields/{id} Permission kahunacart.settings

Change a custom field's label, help, visibility, sortable and filterable flags, required flag, default, validation or position. The entity, the name and the type cannot change, because the values already stored were written against them.

Parameters

Name Type Description
id required integer
label_key optional string
help_key optional string
visibility optional string
One of: public, readonly, internal
sortable optional boolean
filterable optional boolean
required optional boolean
default optional string
validate optional object
position optional integer

Delete a custom field

DELETE /api/v1/kahunacart/custom-fields/{id} Permission kahunacart.settings

Delete a custom field definition and every value stored under it, on every row of its entity. The answer says how many values went with it. A field an add-on declares comes back on the next request unless the add-on stops declaring it.

Parameters

Name Type Description
id required integer

Read custom field values

GET /api/v1/kahunacart/custom-fields/values/{entity}/{entityId} Permission kahunacart.orders.view

Every custom field value on one row of an entity — a product, a variant, an order, an order line, a customer or an address — with the field definitions beside them. Internal fields are included: this is the admin's view, not a template's.

Parameters

Name Type Description
entity required string
entityId required integer

Write custom field values

PUT /api/v1/kahunacart/custom-fields/values/{entity}/{entityId} Permission kahunacart.orders.view

Set custom field values on one row of an entity. Every value is checked against its field's type and validation before any is written, an unknown field name is refused, a readonly field is refused, and null clears a value so its default answers again. Needs the permission that writes the row — products.manage for a product or variant, orders.manage for an order or line, customers.manage for a customer or address.

Parameters

Name Type Description
entity required string
entityId required integer
values required object Field name to value.

Read a settings namespace

GET /api/v1/kahunacart/settings-store/{namespace} Permission kahunacart.orders.view

Every key in one namespace of the add-on settings store with its current value, or its default when nothing has been written, plus the key's scope and permission. Only the keys the caller's permissions allow are returned; a per-user key answers the caller's own value. A namespace no add-on has registered is a 404. The base plugin's own namespace is layout, which holds each zone's widget order and the caller's dev-mode switch.

Parameters

Name Type Description
namespace required string

Write a settings namespace

PUT /api/v1/kahunacart/settings-store/{namespace} Permission kahunacart.orders.view

Write one or more keys in a namespace of the add-on settings store. Each key's own permission is checked and the whole write is refused if any fails; each value goes through the key's validator and the whole write is refused if any is rejected. Reorder a zone's widgets by writing layout with the zone id as the key and the widget ids as the value; switch dev mode on for yourself with dev_mode: true in the same namespace.

Parameters

Name Type Description
namespace required string
values required object Key to value.

List extension zones

GET /api/v1/kahunacart/zones Permission kahunacart.orders.view

The zone catalogue: every place an add-on can mount a widget in the admin or on the storefront, by its stable id — order.details.side, product.details, cart.express and the rest — with the widgets currently mounted in each, already in the merchant's order, the saved layouts, and whether the caller has dev mode switched on. Zones an add-on declared on its own screens follow the core ones.

List schema extensions

GET /api/v1/kahunacart/schema-extensions Permission kahunacart.settings

What every installed add-on has declared it adds to KahunaCart's own responses, and where. Each registration names the extension point, the namespace the data lands under inside the reserved extensions object, the plugin that owns that namespace, and every key it promises with its declared type. Check conflicts first. If two plugins claim the same namespace the second one is refused rather than overwritten, and the entry names both plugins, so you can tell from here which add-on's data never reaches a client without going near a log file.

List compensating hook points

GET /api/v1/kahunacart/hook-points Permission kahunacart.settings

The five points in an order's life where an add-on may do something that has to be undone if a later step fails — order_completed, payment_captured, fulfillment_created, order_canceled and order_refunded — with every handler attached to each, the plugin that owns it, and whether it declared an undo. Check non_compensating. If a handler has no undo and a later step fails, what that handler did stays done and somebody has to undo it by hand. runs counts what has run, what has been undone, and what failed on the way.

List a product's revisions

GET /api/v1/kahunacart/products/{id}/revisions Permission kahunacart.products.manage

Every version of one product, newest first, with what wrote it (a save, a restore or a scheduled change), the note and the admin who made it, and the product's publish window. A row with pending: true is a queued change that has not happened yet. It describes the future rather than the past, and it is never pruned. Snapshots are not included here because they are large. restore_product_revision fetches the one it applies.

Parameters

Name Type Description
id required integer Product id

Restore a product revision

POST /api/v1/kahunacart/products/{id}/revisions/{revision}/restore Permission kahunacart.products.manage

Put a product back exactly as one of its revisions found it — variants, options, media, categories, tags, attributes, kit children, relations, quantity tiers, translations and custom-field values, ids included. Two things are left alone on purpose: stock on a variant that still exists, because putting it back would un-sell whatever has sold since, and product files and releases, because they point at files on disk. The restore writes a NEW revision rather than rewinding history, so the state you restored from is still there to go back to. Refused with 409 if the revision's slug now belongs to another product.

Parameters

Name Type Description
id required integer Product id
revision required integer The revision number to restore, as listed by list_product_revisions

Response

Answers with the restored product and the number of the new revision the restore wrote. A revision whose slug now belongs to another product is a 409 naming it.

Duplicate a product

POST /api/v1/kahunacart/products/{id}/duplicate Permission kahunacart.products.manage

Copy a product into a new draft and answer with the copy, in the same form create_product answers. The copy carries the description, type, visibility, SEO fields, quantity rules, tax and shipping settings, variants with their option combinations and prices, option axes and values, option prices, categories, tags, attribute values, custom-field values, customer-group restrictions, related products, prerequisites, kit items, price-list prices, quantity tiers, translations and the media — the picture files themselves are copied, not shared. It is titled "Copy of <title>" with a slug derived from that, it is a draft whatever the original was, and its publish and unpublish dates are cleared. Each copied variant comes out with no SKU, so the store's own SKU template mints one, and with zero stock. Downloadable files and releases do not come across, and neither do reviews, sales figures, history, revisions, wishlist entries or download grants.

Parameters

Name Type Description
id required integer Id of the product to copy

Set a product's publish window

PUT /api/v1/kahunacart/products/{id}/schedule Permission kahunacart.products.manage

Give a product a time to go live and a time to come down, as UTC epoch seconds. A product given a future publish time is put into draft on the spot, so scheduling it for next Tuesday does not leave it live for the rest of today. The background worker publishes it at its time and marks it dirty on the catalog index in the same tick. Send null to clear either date. Leave a key out and that date is untouched, so setting a take-down date never silently cancels a publish date you already set.

Parameters

Name Type Description
id required integer Product id
publish_at optional integer or null UTC epoch seconds to publish at; null clears it
unpublish_at optional integer or null UTC epoch seconds to take it down at; null clears it

List a product's prerequisites

GET /api/v1/kahunacart/products/{id}/prerequisites Permission kahunacart.products.manage

What a customer has to have bought before they may buy this product. Each row names the required product by id and carries its current title and slug, so an editor can draw the list without a second request per row. A required that comes back null means the product has since been deleted. The rule is broken, and the storefront refuses the add to cart rather than treating a broken rule as no rule.

Parameters

Name Type Description
id required integer Product id

Set a product's prerequisites

PUT /api/v1/kahunacart/products/{id}/prerequisites Permission kahunacart.products.manage

Replace the list wholesale. An empty list clears it. mode is any — one completed purchase from the list is enough, which is the upgrade path from three earlier editions — or all, which is part three of a course. Only a COMPLETED order satisfies a prerequisite: pending and cancelled orders do not, and which statuses count is read off the order-status registry's complete flag rather than off the word "completed", so a merchant's own Dispatched counts.

Parameters

Name Type Description
id required integer Product id
mode optional string Whether one completed purchase from the list is enough, or every one is needed
One of: any, all
Default: any
prerequisites required array of object The required products, in the order they should be listed. Keys: `required_product_id`, `message_key`

List payment collections

GET /api/v1/kahunacart/payment-collections Permission kahunacart.orders.view

The orders that have been part paid, with what each owes, what has been authorised, captured and refunded, and what is still outstanding. A collection is open while money is still owed, settled when everything owed has been captured, and refunded when everything captured has gone back. An order is NOT counted as paid until its balance is paid. The payment status sits at partially_paid, which has counts_as_paid turned off, so no report treats a deposit as a completed sale.

Parameters

Name Type Description
status optional string Only collections in this state
One of: open, settled, refunded, voided
page optional integer At least 1.
per_page optional integer At least 1, at most 200.
sort optional string A column to sort every row by before it is paged; leave out for the usual order
One of: order, status, owed, captured, refunded, balance, updated
dir optional string
One of: asc, desc
Default: asc
q optional string Words to search for in the order number and the email on the order; every word has to be there, in any order. Applied before the sort and the page, and total counts what it found

Take a deposit on an order

POST /api/v1/kahunacart/orders/{id}/deposit Permission kahunacart.orders.manage

Open a payment collection on one order and record the deposit session against it. Give amount as a decimal string in the store currency, or percent as a decimal string, or neither — in which case the store's own policy decides, reading the products on the order first and falling back to checkout.deposits.default_percent. Idempotent on the order. Asking twice returns the collection that already exists rather than opening a second one. Refused with 409 if checkout.deposits.enabled is off.

Parameters

Name Type Description
id required integer Order id
amount optional string The deposit as a decimal amount in the store currency. Wins over percent
percent optional string The deposit as a percentage of the order total, as a decimal string
provider optional string Which gateway the deposit is taken through; the order's own provider by default

Ask for the balance on a part-paid order

POST /api/v1/kahunacart/orders/{id}/collect-balance Permission kahunacart.orders.manage

Send the balance request email to a part-paid order's customer and record the send on the order. Opens the balance session if one is not open already, queues the message, and answers with the signed pay link the message carries so you can read it out over the phone. The link charges whatever is outstanding rather than the order total, so there is one payment route for the deposit and for the balance. Refused with 409 on an order with no deposit on it, on one that has been paid in full, and when the balance request email is switched off on the Notifications screen. Refused with 422 when the order carries no email address.

Parameters

Name Type Description
id required integer Order id

Capture an authorised payment

POST /api/v1/kahunacart/transactions/{id}/capture Permission kahunacart.orders.manage

Take some or all of the money a provider is holding against an order. The transaction id is the authorisation's ledger row, on authorization in get_order. Omit the amount to take the whole authorised amount; give amount_minor or amount to take less, and the provider releases the rest. The order then counts as paid: the payment-received email goes out, onKahunaCartOrderPaid and onKahunaCartPaymentCaptured fire, and download grants are issued. Refused, with a reason and before the provider is called, when the amount exceeds what was authorised, the authorisation was already captured or voided, or the provider cannot capture through its API.

Parameters

Name Type Description
id required integer Authorisation transaction id, from get_order
amount_minor optional integer How much to take, in minor units. Omit to take the whole authorised amount.
amount optional string The same as a decimal in the order currency, for example "12.50".

Void an authorised payment

POST /api/v1/kahunacart/transactions/{id}/void Permission kahunacart.orders.manage

Release the money a provider is holding against an order without taking any of it, and cancel the order — the goods go back on the shelf, the coupon is released and download grants are revoked, exactly as Cancel does, and the history says why. Refused, with a reason and before the provider is called, when the authorisation was already captured (refund it instead), already voided, or the provider cannot void through its API.

Parameters

Name Type Description
id required integer Authorisation transaction id, from get_order
reason optional string Recorded on the ledger row and in the order history

List a customer's saved payment methods

GET /api/v1/kahunacart/customers/{id}/payment-tokens Permission kahunacart.customers.view

The payment methods a provider keeps on file for one customer, as this store knows them: the provider, a display label, the brand, the last four digits and the expiry the provider reports, and which one is the default. Never a card number — the store holds none. available says whether any registered provider can keep a method at all.

Parameters

Name Type Description
id required integer Customer id

Remove a saved payment method

DELETE /api/v1/kahunacart/payment-tokens/{id} Permission kahunacart.customers.manage

Ask the provider to forget a stored payment method, then retire the store's own record of it. Fails, and leaves the record standing, when the provider refuses or its plugin is not enabled — the method is never shown as gone while the platform can still charge it.

Parameters

Name Type Description
id required integer Saved payment method id, from list_payment_tokens

Get a zone widget script

GET /api/v1/kahunacart/zones/widgets/{id}.js Permission kahunacart.orders.view

Serve the JavaScript for one mounted zone widget, as the bytes are on disk

Writing a custom field value takes the permission that writes the row it sits on: kahunacart.products.manage for a product or variant, kahunacart.orders.manage for an order or an order line, kahunacart.customers.manage for a customer or an address, and kahunacart.settings for an entity an add-on registered.

Every value in a settings-store write is checked before any is written, and the whole write is refused if one fails.

GET /kahunacart/schema-extensions answers with every registration an add-on made through onKahunaCartRegisterSchemaExtensions: the extension point, the namespace, the owning plugin and every declared key with its type, plus the namespaces in use and any clash that was refused. Read conflicts first. A plugin whose namespace was refused is running with none of its data reaching a client, and nothing else in the store says so. Everything an add-on returns lands inside one reserved extensions object keyed by its namespace, so no key KahunaCart documents ever moves.

GET /kahunacart/hook-points answers with the five points a compensating hook can attach to (order_completed, payment_captured, fulfillment_created, order_canceled and order_refunded), the handlers on each, which of them declared no undo under non_compensating, and how many runs are in each state. A handler with no compensation is a decision rather than an omission: when a later step in the same flow fails, what it did stays done and somebody has to undo it by hand. There is one run row per point, handler and subject, and a run row is never deleted when its compensation runs, because "this ran and was then undone" is a different fact from "this never ran".

The revision routes read and restore the snapshot KahunaCart takes on every product save, capped at catalog.revisions.keep. A restore puts every row the product owns back with its ids and writes a new revision recording the restore, so history is never rewound. Stock is never restored onto a variant that still exists, and files and releases are not restored at all. A restore whose slug now belongs to a different product answers 409. PUT /kahunacart/products/{id}/schedule sets publish_at and unpublish_at as UTC epoch seconds, and setting a future publish_at moves the product to Draft now rather than leaving it live until then. Prerequisites are replaced wholesale, an empty list clears them, and only an order at a status flagged complete satisfies one. All five need kahunacart.products.manage.

GET /kahunacart/payment-collections lists the part-paid orders with what each still owes, and POST /kahunacart/orders/{id}/deposit opens a collection and takes the deposit. Leave the amount out to use the store's own policy, or send an amount or a percent. The call is idempotent on the order, because an order owes what it owes however many times somebody presses the button. A part-paid order is not a paid order: its payment status is Part paid, whose counts_as_paid flag is false, so no report and no fulfillment queue has to know deposits exist to get the right answer. The balance is collected through the pay link the order already has. Both routes need checkout.deposits.enabled.