Skip to content

Powered by Grav CMS + Helios Theme

Shipping rates and parcels

What a shipping method charges, the profiles that split an order into parcels, and the drivers a live carrier plugs into.

Shipping is the group about zones, methods and whether a method is offered at all. This one is about the money: the rate rules behind a method, the cost expression that can replace them, the profiles that decide how many parcels an order becomes, and the contract a carrier's own rate card arrives through. See Shipping.

Read a shipping method's rate rules

GET /api/v1/kahunacart/shipping/methods/{id}/rates Permission kahunacart.settings

Every rate type on one method — base, per item, per kilogram, percentage, per shipping class — plus the minimum and maximum clamps, the cost expression, the three-valued shipping-class policy and the carrier the method is bound to. Amounts come back as decimal strings in the store currency; the cost expression is in minor units. Also returns the closed token list a cost expression may use. On a store that sells in more than one currency it also returns currency_amounts, priced_in and missing_in for the method.

Parameters

Name Type Description
id required integer Shipping method id

Set a shipping method's rate rules

PUT /api/v1/kahunacart/shipping/methods/{id}/rates Permission kahunacart.settings

Replace the rate rules on one method. Only the fields you send are written, so a client that has never heard of percent_rate cannot clear one. Rates are tabulated in a fixed order and then clamped, minimum first then maximum. A cost_expression replaces the tabulated figure and is refused with the offending token named if it does not parse. Saving also clears the carrier rate cache.

Parameters

Name Type Description
id required integer Shipping method id
per_item optional string or null Decimal amount per unit in the package
per_kg optional string or null Decimal amount per kilogram of the package
percent_rate optional string or null A percentage of the package goods, as a plain decimal
min_total_cost optional string or null The floor, applied after tabulation
max_total_cost optional string or null The ceiling, applied after the floor
cost_expression optional string or null A formula in minor units over the closed token list, e.g. 1000 + ( 200 * [qty] ) + [fee percent="10" min_fee="400"]
class_policy optional string allow carries everything; disallow refuses the named classes; require offers the method only for them
One of: allow, disallow, require
class_policy_classes optional array of string The shipping classes the policy names
class_costs_mode optional string Whether the per-class figures are added to the tabulated rate (add, the default and what they have always meant) or replace it for a parcel holding a line of that class (replace). The two clamps apply either way.
One of: add, replace
carrier_code optional string or null The carrier driver that prices this method. KahunaCart ships none
carrier_service_code optional string or null Which of that carrier's services
currency_amounts optional object The method's rates in the other currencies, as on a method in a zone: currency code to {cost, free_minimum, per_item, per_kg, min_total_cost, max_total_cost, per_class, weight_rules, cost_expression}. Written with the base fields in one transaction, so a refusal changes nothing.

Check a shipping cost expression

POST /api/v1/kahunacart/shipping/expressions/validate Permission kahunacart.settings

Parse a cost expression without saving it and answer whether it is valid, with the offending token named when it is not. Expressions are evaluated by KahunaCart's own parser over a closed grammar — decimal literals, + - * / ( ), the tokens [qty], [cost], [weight], [item_count] and the one function form [fee percent="N" min_fee="N" max_fee="N"]. Nothing else parses, and no PHP function name ever will.

Parameters

Name Type Description
expression required string

List shipping profiles

GET /api/v1/kahunacart/shipping/profiles Permission kahunacart.settings

The profiles a store packs by, in assembly order, with how many products point at each. Two are built in: default ships and digital does not. An order is packed into one parcel per profile its lines mention.

Create a shipping profile

POST /api/v1/kahunacart/shipping/profiles Permission kahunacart.settings

Add a profile for a part of the catalogue that travels separately. The code is normalised to lowercase letters, digits, dashes and underscores, and must be unique. ships: false makes a profile that produces a parcel with nothing to post, which is what the built-in digital profile is.

Parameters

Name Type Description
code required string
name optional string
position optional integer The order parcels are assembled in
ships optional boolean Whether a parcel of this profile goes in the post at all

Update a shipping profile

PUT /api/v1/kahunacart/shipping/profiles/{id} Permission kahunacart.settings

Change a profile's code, name, position or whether it ships. Renaming a profile does not repoint the products that use it — they point at the id.

Parameters

Name Type Description
id required integer Shipping profile id
code optional string
name optional string
position optional integer
ships optional boolean

Delete a shipping profile

DELETE /api/v1/kahunacart/shipping/profiles/{id} Permission kahunacart.settings

Refused while any product still points at the profile, and refused outright for the built-in default and digital profiles, which are what an unassigned product falls back to.

Parameters

Name Type Description
id required integer Shipping profile id

List an order's parcels

GET /api/v1/kahunacart/orders/{id}/packages Permission kahunacart.orders.view

The packages one order was priced as: which profile assembled each, which method priced it, what it cost, what it weighed, which lines went in it and any carrier metadata for the method that actually won. The order's shipping total is the sum of these. Needs kahunacart.orders.view rather than the shipping settings permission.

Parameters

Name Type Description
id required integer Order id

List carrier rate drivers

GET /api/v1/kahunacart/shipping/carriers Permission kahunacart.settings

The live carrier rate drivers installed in this store, the shipping methods bound to them and whether each binding resolves. KahunaCart ships no carrier — the drivers are the KahunaCart Shipping add-on — so an empty list is the correct answer on a store without it. Also returns the timeout, the cache lifetime and the nominated fallback method. Each driver carries a services list — the services it says it has, with a service_code, a label, an optional carrier_code for a driver that fronts several carriers, and the countries it runs in. Listing services is optional, so a driver that cannot enumerate them answers an empty list rather than leaving the key out. Each bound method carries service_known: true when its carrier_service_code is in that driver's list, false when it is not, and null when there is no list to check against. False is a warning and never a refusal — the method is still offered and still priced.

Rebuild the carrier rate cache

POST /api/v1/kahunacart/shipping/carriers/cache/clear Permission kahunacart.settings

Drop every stored carrier quote so the next checkout asks the carriers again. Quotes expire on their own after shipping.carrier_rates.cache_ttl seconds and saving a shipping method clears them too; this is the button for a merchant whose courier changed a rate card this morning.

A method's price is tabulated top to bottom and then clamped, and the order is the contract: the base rate, then the per-item rate multiplied by the quantity in the parcel, then the per-kilogram rate, then the percentage of the parcel's item price, then the per-class figure once per distinct shipping class the parcel holds, then the minimum and last the maximum. Both clamps are inclusive at the boundary. Every intermediate is minor units, every rate that is not a whole number of minor units goes through exact rational arithmetic, and the result is rounded once at the end. min_total_cost_minor is the clamp and min_total is the free-shipping threshold. The two names are far apart on purpose.

A cost_expression on a method replaces the whole table above and leaves both clamps in force. Everything in an expression is minor units, so Woo's 10 + ( 2 * [qty] ) is written 1000 + ( 200 * [qty] ) here, and a negative result is floored at zero. The token list is closed: decimal literals, + - * /, brackets, [qty], [item_count], [cost], [weight] and [fee percent="N" min_fee="N" max_fee="N"]. There are no function names, no variables you can invent and no path from a character a merchant types to a PHP callable. Anything outside the list is refused where it is typed, with the offending token named, by POST /kahunacart/shipping/expressions/validate as the merchant writes it and again by the save. Division by zero evaluates to zero and is noted in the adjustment's audit trail rather than taking a checkout down. shipping.expressions.enabled: false refuses to save an expression and ignores any already stored.

PUT /kahunacart/shipping/methods/{id}/rates also carries the three-valued class policy. allow is the default and consults nothing. disallow takes the method off when the parcel holds a line of a named class, require takes it off unless the parcel holds one. A method removed by policy comes back present, disabled and naming the class, as class_disallowed or class_required. A policy naming no classes refuses nothing. The route writes only the fields it is handed, so a client that has never heard of percent_rate cannot clear one.

A shipping profile says which consignment a product belongs to, and an order is packed into one package per profile its lines mention. default and digital ship with the plugin, a product with no profile of its own falls back to digital when it is a download and default otherwise, and an existing store therefore packs every order into one parcel and is charged what it always was. Packages come out in profile position order, the order's shipping total is the sum of its packages, and a package whose profile does not ship gets no method and no cost and is still written down. There is no weight or dimension packing. Deleting the default or digital profile is refused, and so is deleting a profile a product still points at.

KahunaCart ships no carrier. GET /kahunacart/shipping/carriers lists the drivers an add-on has registered, and a stock install answers with none. A carrier never blocks a checkout: an excluded service, a decline, a timeout, a driver that failed and a store with shipping.carrier_rates.enabled: false all come back the same way, as the method present, disabled and carrying a reason code of carrier_excluded, calculator_declined or carrier_unavailable, with the nominated fallback method carrying the order. A quote is cached on a hash of the destination, the parcel contents, its weight, its declared value and the currency, for shipping.carrier_rates.cache_ttl seconds. Saving a method's rates clears the set, and POST /kahunacart/shipping/carriers/cache/clear is the button for the merchant whose account manager changed a rate card this morning.