nShift Checkout — Shipping Module
If you're building a Hantera app that needs nShift shipping options on its own ingress — for example a storefront-checkout bridge that renders carrier products inside a third-party checkout iframe — you can import the public getOptions function from the nShift Checkout app's Filtrera module.
OAuth tokens, nShift sessions, and per-session caching are handled for you behind a session ticket actor. You call one function, get back options, render them, and on selection write two delivery dynamic fields — the app's rules take it from there.
Module path
apps/nshift-checkout/nshift.module.hrcDeclaring the dependency
A consumer app cannot import this module by path alone. Declare a requires.modules entry in your h_app.yaml, naming the module path and the export type you use. The declaration is type-checked against the producer's real source when your app is activated.
requires:
modules:
apps/nshift-checkout/nshift.module.hrc:
exports:
getOptions:
type: >-
(sessionActorId: uuid | nothing, request: {
totalPrice: number, totalVolumeCm3: number, totalWeightKg: number,
localeId: text, currencyCode: text, languageCode: text,
packages: [value], variables: { text -> value },
receiver: {
name: text, address1: text, postalCode: text,
city: text, country: text, phone: text | nothing,
email: text | nothing
}
}, context: {
delivery: {
deliveryId: uuid | nothing, deliveryAddress: value,
dynamic: { text -> value },
lines: [{
orderLineId: uuid | nothing,
productNumber: text | nothing, quantity: number,
dynamic: { text -> value }, taxFactor: number | nothing
}],
order: {
orderId: uuid | nothing, channelKey: text,
currencyCode: text, locale: text | nothing,
dynamic: { text -> value }
}
}
} | nothing) => {
sessionActorId: uuid,
options: [{
optionId: text, sessionActorId: uuid,
shippingProductNumber: text, carrierId: text,
carrierProductId: text, name: text, title: text | nothing,
price: number, originalPrice: number | nothing,
shippingTax: number | nothing,
shippingTaxFactor: number | nothing,
priceDescription: text | nothing,
logoUrl: text | nothing, texts: [text] | nothing,
valid: bool | nothing,
additionalValues: { text -> value } | nothing,
pickupPoints: [value] | nothing, raw: { text -> value }
}]
} | { error: { code: text, message: text, details: value } }Inline the record shapes
Cross-app module contracts don't support named-type aliasing. Inline the request, context, and option record shapes in the requires.modules block — as above — rather than referencing NShiftCheckoutShippingRequest or NShiftCheckoutVariablesContext by name. The producer's real source is the source of truth and is type-checked against your declaration at activation.
See Declaring App Dependencies for the surrounding context.
Importing
import {
getOptions
} from 'apps/nshift-checkout/nshift.module.hrc'getOptions(sessionActorId, request, context)
The single function you call.
getOptions(
sessionActorId: uuid | nothing,
request: NShiftCheckoutShippingRequest,
context: NShiftCheckoutVariablesContext | nothing
) => NShiftCheckoutOptionsResult | { error: { code: text, message: text, details: value } }| Parameter | Type | Description |
|---|---|---|
sessionActorId | uuid | nothing | The id of an existing nShiftCheckoutSession ticket, if any. Pass nothing on the first call and the value returned by a previous call on subsequent calls. |
request | NShiftCheckoutShippingRequest | The lookup request — totals, locale, receiver, and provider-specific variables. Must include channelKey (or nshiftCheckoutConnectionId) in variables. See below. |
context | NShiftCheckoutVariablesContext | nothing | The delivery-rooted context the hooks operate on. See Context. |
Context
context is the same delivery-rooted record consumed by the OnNShiftCheckoutVariables hook (delivery + its order + order lines, each with dynamic). It is used internally for two things:
- Variable resolution.
getOptionsfiresOnNShiftCheckoutVariableswith this context, collects listener-emitted variables, and seedschannelKey. Any keys you pass inrequest.variableswin over hook-resolved values for the same key. - Shipping-tax resolution.
getOptionsfires theOnNShiftCheckoutShippingOptionTaxhook with this context plus the freshly fetched options, then stamps each option with the resolvedshippingTax/shippingTaxFactor.
context is optional to keep the surface forward-compatible. Pass it whenever you have one. If you pass nothing:
- The variables hook does not fire (only
request.variablesyou supply directly are used). - The shipping-tax hook does not fire, and options come back with
shippingTaxandshippingTaxFactorbothnothing.
Both delivery.deliveryId and delivery.order.orderId are uuid | nothing, to allow fetching options before an order is creation, for example for a cart/checkout scenario.
Returns either an NShiftCheckoutOptionsResult on success or an error record on failure. See Errors.
The function decides whether to create a new nShift session, refresh an existing one in place, or simply return previously cached options based on the supplied sessionActorId and a fingerprint of request. Consumers don't need to think about the underlying session-ticket model.
Resolving the connection
The module resolves the nShift checkout connection id from one of two places, in order:
- Direct override:
request.variables.nshiftCheckoutConnectionId(text). - Channel registry lookup: if the request includes
request.variables.channelKey, the module readschannels/{channelKey}.nshiftCheckoutConnectionId.
A storefront-facing consumer typically passes channelKey and lets the channel registry resolve the connection. A test or admin caller may pass nshiftCheckoutConnectionId directly.
If neither resolves to a connection id, the function returns the NSHIFT_NO_CHECKOUT_CONNECTION error.
Request
{
totalPrice: number
totalVolumeCm3: number
totalWeightKg: number
localeId: text
currencyCode: text
languageCode: text
packages: [value]
variables: { text -> value }
receiver: {
name: text
address1: text
postalCode: text
city: text
country: text
phone: text | nothing
email: text | nothing
}
}| Field | Description |
|---|---|
totalPrice | Cart/delivery total used by nShift's freemium / threshold rules. Pass 0 if not applicable. |
totalVolumeCm3 | Total volume in cm³. Pass 0 if you don't track volume. |
totalWeightKg | Total weight in kg. Pass 0 if you don't track weight. |
localeId | Locale identifier (e.g. sv-SE). nShift uses it for translated option names and currency/decimal formatting. |
currencyCode | ISO 4217 currency code. |
languageCode | ISO 639-1 language code (e.g. sv). Usually the first segment of localeId. |
packages | Per-package details (dimensions, content classification). Provider-defined; pass [] if not used. |
variables | Provider-specific variables (e.g. fromwarehouse). Must include channelKey or nshiftCheckoutConnectionId. See OnNShiftCheckoutVariables. |
receiver | Destination address. phone and email are optional. |
Successful result
{
sessionActorId: uuid
options: [NShiftCheckoutOption]
}| Field | Description |
|---|---|
sessionActorId | The id of the nShiftCheckoutSession ticket actor that backs this set of options. Pass it on the next call to getOptions to reuse or refresh the session efficiently. |
options | The available options, in the order returned by nShift. Each option also carries sessionActorId so a UI can pass the two selection keys forward atomically. |
NShiftCheckoutOption
{
optionId: text
sessionActorId: uuid
shippingProductNumber: text
carrierId: text
carrierProductId: text
name: text
title: text | nothing
price: number
originalPrice: number | nothing
shippingTax: number | nothing
shippingTaxFactor: number | nothing
priceDescription: text | nothing
logoUrl: text | nothing
texts: [text] | nothing
valid: bool | nothing
additionalValues: { text -> value } | nothing
pickupPoints: [value] | nothing
raw: { text -> value }
}| Field | Description |
|---|---|
optionId | nShift's own unique id for this option within the session. This is the selection key — pair it with sessionActorId to select. |
sessionActorId | Repeats the result-level sessionActorId per option so it can ride along in a single UI blob. |
shippingProductNumber | Stable id formed as <carrierId>-<carrierProductId>. Suitable for display or as a public-facing product number. |
carrierId | nShift carrier id. |
carrierProductId | nShift carrier product id within the carrier. |
name | Display name (localized to localeId). |
title | Alternate display title, when nShift provides one. |
price | Effective price. |
originalPrice | Pre-discount price, when the option is on offer. |
shippingTax | Absolute shipping tax amount in the request's currency, when resolved. Takes precedence over shippingTaxFactor. See below. |
shippingTaxFactor | Shipping tax as a factor (e.g. 0.25 for 25%), when resolved. See below. |
priceDescription | Short price annotation (e.g. Free over 500 SEK). |
logoUrl | Carrier logo URL. |
texts | Additional descriptive lines. |
valid | Whether nShift considers the option currently valid for this session. |
additionalValues | Provider-specific extra fields. |
pickupPoints | Pickup points for parcel-shop options. Pass through verbatim; surface a picker if your carrier requires one. |
raw | The unmodified option record from nShift. Useful when you need a field this type doesn't expose. |
Shipping tax resolution
shippingTax and shippingTaxFactor are mutually exclusive: at most one is set on a given option, and both may be nothing when no rate or amount could be determined. shippingTax is an absolute amount and takes precedence over shippingTaxFactor (a factor in [0, 1]).
For each option, getOptions resolves the effective tax in this order:
- nShift's own
taxRate(when present in the option payload) — used asshippingTaxFactor. OnNShiftCheckoutShippingOptionTaxhook emission for thisoptionId— an emittedshippingTaxwins over ashippingTaxFactor.- Line-derived default —
shippingTaxFactor = max(taxFactor)across the context's order lines. Skipped when no line carries a positivetaxFactor. - Otherwise — both fields stay
nothing.
See OnNShiftCheckoutShippingOptionTax Hook for how to compute custom rates. Because the hook only fires when a context is supplied, options come back with both fields nothing if you call getOptions with context = nothing.
Selection
Selection is not a separate module function. To select an option, write two fields onto the relevant delivery's dynamic fields (the field names are the contract):
{
nShiftCheckoutSessionActorId = <option.sessionActorId>
nShiftCheckoutOptionId = <option.optionId>
}When those two fields land on a delivery (typically because the portal's select-product flow ran, or because commerce copied them from cart.deliveryDynamicFields during cart-to-order conversion), the app's bridge rule:
- Records the selection on the session ticket and completes it.
- The session's
OnTicketCompleterule then writes the rich option fields onto the delivery (shippingProductNumber,shippingPrice,shippingDescription, carrier metadata) and clears the two selection keys. - The same rule schedules the
createPartialShipmentjob, which dispatches the shipment to nShift after commit.
Optional fields the bridge will forward to the session ticket if present alongside the two selection keys:
| Field | Type | Used for |
|---|---|---|
nShiftCheckoutPickupPointId | text | Carriers that require a pickup point. If omitted, the shipment job falls back to the first pickup point on the selected option, if any. |
nShiftCheckoutTimeSlotId | text | Carriers that support time slots. |
Errors
On any failure, the function returns an error record of shape { error: { code, message, details } }. Handle it in your caller — don't assume the result is always a successful one.
code | When it happens |
|---|---|
NSHIFT_NO_CLIENT_ID | The app's clientId is unset — finish setup before calling. |
NSHIFT_NO_CLIENT_SECRET | The app's clientSecret is unset. |
NSHIFT_NO_CHECKOUT_CONNECTION | No checkout connection id could be resolved from request.variables or the channel registry. |
NSHIFT_TOKEN_FAILED | OAuth token request failed (network error or non-200 from nShift). |
NSHIFT_SESSION_FAILED | Session creation returned a non-200 or an unexpected payload from nShift. |
NSHIFT_OPTIONS_FAILED | Shipping-options request returned a non-200 or an unexpected payload from nShift. |
NSHIFT_TICKET_WRITE_FAILED | Failed to persist the session ticket (e.g. transient platform error). |
details carries the underlying response when available — useful when surfacing or logging the failure.
Example: a storefront-facing ingress
A typical pattern is to expose nShift as a small HTTP ingress your storefront posts to. With structured body parsing the host parses the JSON body and binds each top-level field to a matching param of the declared type — so you don't need to deserialize manually. See HTTP Ingress Parameters for the full mechanism.
import 'text'
import { getOptions } from 'apps/nshift-checkout/nshift.module.hrc'
param channelKey: text
param sessionActorId: uuid | nothing
param currencyCode: text
param localeId: text
param totalPrice: number
param receiver: {
name: text
address1: text
postalCode: text
city: text
country: text
phone: text | nothing
email: text | nothing
}
let languageCode = localeId explode '-' first match
(l: text) |> l
|> localeId
// Inject `channelKey` so the module resolves the checkout connection id
// from the channel registry.
let request = {
totalPrice = totalPrice
totalVolumeCm3 = 0
totalWeightKg = 0
localeId = localeId
currencyCode = currencyCode
languageCode = languageCode
packages = []
variables = { channelKey = channelKey }
receiver = receiver
}
// A pre-order storefront flow has no delivery yet, so pass `nothing` for
// context. The OnNShiftCheckoutVariables and OnNShiftCheckoutShippingOptionTax
// hooks don't fire, and options come back without shipping tax. If you can
// build a delivery-rooted context (see the variables-hook page), pass it here
// to enable merchant variable rules and per-option shipping tax.
let result = getOptions(sessionActorId, request, nothing)
from result match
{ error: { code: text } } |> {
statusCode = 502
content = result.error
}
{ sessionActorId: uuid, options: [value] } |> {
statusCode = 200
content = {
sessionActorId = result.sessionActorId
options =
result.options
select o => {
id = o.optionId
sessionActorId = o.sessionActorId
name = o.name
price = o.price
carrierId = o.carrierId
productId = o.carrierProductId
}
}
}To select an option, your storefront writes the two nShiftCheckout* keys onto the cart's deliveryDynamicFields (so they ride along to the delivery at cart-to-order conversion), or directly onto a portal-style delivery's dynamic. No separate module call is required.
See Also
- nShift Checkout overview — Set up the app and configure channels.
OnNShiftCheckoutVariablesHook — Customize the request via merchant rules. Fires on every call togetOptionsthat supplies a context.OnNShiftCheckoutShippingOptionTaxHook — Compute per-option shipping tax.- Declaring App Dependencies