Skip to content

OnNShiftCheckoutShippingOptionTax Hook

Shipping options returned by nShift carry a price but not always a tax rate. The nShift Checkout app needs a tax amount (or factor) per option so the delivery's shippingTax lands correctly when an option is selected.

The app resolves this per option from several sources. The OnNShiftCheckoutShippingOptionTax hook lets your rules compute custom shipping tax — for example a flat rate for a tax jurisdiction that nShift doesn't price, or a carrier-specific override.

When it fires

The hook fires once per call to getOptions (not once per option), right after options are fetched from nShift and before they're persisted on the session ticket. Firing once lets a listener that wants to price several options do it with a single registry or graph traversal.

The hook only fires when the caller passes a context to getOptions. The portal's Select shipping product action always supplies one; a bare module caller that passes context = nothing skips the hook entirely (see Shipping Module — Context).

Resolution order

For each option, the app picks the effective tax from the first source that yields a value:

  1. nShift's own taxRate (when present on the option) — used as a shippingTaxFactor.
  2. This hook — a listener emission for that optionId. An emitted shippingTax (absolute) wins over an emitted shippingTaxFactor.
  3. Line-derived defaultshippingTaxFactor = max(taxFactor) across the context's order lines. This matches European VAT markets where shipping inherits the highest line tax rate. Skipped when no line has a positive taxFactor.
  4. Otherwise — both shippingTax and shippingTaxFactor stay nothing and no shipping tax is set on the delivery.

Because nShift's own rate takes precedence, your hook only changes the outcome for options where nShift returned no taxRate.

Listening to the hook

Declare a rule with a param input that matches OnNShiftCheckoutShippingOptionTax. The input carries the same delivery-rooted context as OnNShiftCheckoutVariables plus the freshly fetched options. Only declare the fields your rule reads.

filtrera
param input: {
  hook: 'OnNShiftCheckoutShippingOptionTax'
  delivery: {
    deliveryId: uuid | nothing
    deliveryAddress: {
      countryCode: text | nothing
      postalCode: text | nothing
      state: text | nothing
    }
    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 }
    }
  }
  options: [{
    optionId: uuid
    carrierId: text
    carrierProductId: text
    name: text
    price: number
    nShiftTaxRate: number | nothing
  }]
}

The delivery branch is identical to the variables hook context — same caveats about deliveryId / order.orderId being uuid | nothing.

options[] fieldDescription
optionIdThe option's id. Echo it back on each effect so the app can map your value to the right option.
carrierIdnShift carrier id — useful for carrier-specific rules.
carrierProductIdnShift carrier product id within the carrier.
nameDisplay name of the option.
priceThe option's price, in the request's currency — multiply by your factor to compute an amount.
nShiftTaxRatenShift's own tax rate for the option, when present. If set, it already takes precedence over your emission for that option.

Emitting tax

Emit one effect per option you want to set tax for. Two effect types are accepted:

filtrera
// Absolute amount in the request's currency
from {
  effect   = 'custom'
  type     = 'shippingTax'
  optionId = <option.optionId>
  value    = 39
}

// Or a factor (e.g. 0.25 == 25%)
from {
  effect   = 'custom'
  type     = 'shippingTaxFactor'
  optionId = <option.optionId>
  value    = 0.25
}
FieldDescription
effectMust be 'custom'.
type'shippingTax' (absolute amount) or 'shippingTaxFactor' (factor in [0, 1]). Other types are ignored.
optionIdThe optionId from the matching input.options[] entry.
valueThe amount or factor. Must be a number.

For the same optionId, an emitted shippingTax wins over a shippingTaxFactor. Within a single type, the last listener emission wins. Options you don't emit for fall through to the line-derived default.

Example: flat US shipping tax

apps.nshift-checkout's default is max(line.taxFactor), appropriate for European VAT markets. For US destinations we want a flat factor instead, applied to every option.

filtrera
import 'iterators'

param input: {
  hook: 'OnNShiftCheckoutShippingOptionTax'
  delivery: {
    deliveryAddress: { countryCode: text | nothing }
  }
  options: [{
    optionId: uuid
  }]
}

let isUs = input.delivery.deliveryAddress.countryCode match
  'US' |> true
  |> false

from isUs match
  true |>
    input.options
    select o => {
      effect   = 'custom'
      type     = 'shippingTaxFactor'
      optionId = o.optionId
      value    = 0.08
    }
    buffer
  |> []

Example: carrier-specific absolute amount

Override a single carrier's options with a fixed tax amount, leaving the rest to fall through to the default.

filtrera
import 'iterators'

param input: {
  hook: 'OnNShiftCheckoutShippingOptionTax'
  options: [{
    optionId: uuid
    carrierId: text
    price: number
  }]
}

from
  input.options
  where o => o.carrierId == 'postnord'
  select o => {
    effect   = 'custom'
    type     = 'shippingTax'
    optionId = o.optionId
    value    = o.price * 0.25
  }
  buffer

See Also

© 2024 Hantera AB. All rights reserved.