Skip to content

@dukkan.one/app-sdk/commerce

Money in minor units with each value's own decimals, order status transitions, totals reconciliation, unfulfilled lines, refundable amounts, and sync by updated_at.

Package
@dukkan.one/app-sdk@0.3.0
API revision
2026-09-10
Import
import { … } from "@dukkan.one/app-sdk/commerce"
On this page

Functions

addMoney#

function addMoney(a: Money, b: Money): Money

Adds two amounts of the same currency and exponent; throws on a mismatch so mixed currencies never add silently.

allowedNextStatuses#

function allowedNextStatuses(from: OrderStatus): readonly SettableOrderStatus[]

Transitions an app may request from from (the server's matrix, settable subset).

buildCreateOrderLines#

function buildCreateOrderLines(lines: readonly PricedLine[], fx: { from: CurrencyExponent; to: CurrencyExponent; rate: ScaledRate | null; }): CreateOrderItem[]

Builds CreateOrderRequest.items from lines priced in the pricing currency. With a rate, every unit price is converted per unit like checkout; with rate: null (order in the pricing currency) the prices pass through unchanged.

canTransition#

function canTransition(from: OrderStatus, to: SettableOrderStatus): boolean

True when the platform's status matrix allows a PATCH /orders/{id}/status from from to to.

compareMoney#

function compareMoney(a: Money, b: Money): -1 | 0 | 1

Compares two amounts of the same currency: -1, 0 or 1.

convertLinePrice#

function convertLinePrice(line: { unit_price_minor: number; quantity: number; }, from: CurrencyExponent, to: CurrencyExponent, rate: ScaledRate): ConvertedLine

Prices one line in the order currency the way checkout does: round the UNIT price first, then multiply by the quantity. Converting the line total instead can differ by a minor unit, and then the app's quote and the storefront would disagree on the same basket.

convertMinorAmount#

function convertMinorAmount(amountMinor: number, from: CurrencyExponent, to: CurrencyExponent, rate: ScaledRate): number

Converts ONE minor amount from the pricing currency to the order currency with the platform's { scaled, scale } rate, rounding half up to the target's decimals (what checkout's roundByCurrency does to a unit price). Exact integer arithmetic: no float drift at the .5 boundary.

exchangeRateFor#

function exchangeRateFor(store: Pick<Store, "pricing_currency" | "fx">, currency: string): ScaledRate | null | undefined

The exchange_rate to send with POST /orders for currency, from the store's FX table: null when the order is in the pricing currency (the platform rejects a rate there), the store-adjusted rate otherwise, or undefined when the store has no rate for that currency.

formatMoney#

function formatMoney(value: Money, locale?: string, options?: { numberingSystem?: "latn" | "arab"; display?: "symbol" | "code" | "name"; }): string

Locale-aware formatting through Intl using the value's own exponent. ar yields Arabic script digits by default; pass numberingSystem: "latn" for Western digits, which Dukkan's admin uses for technical values.

isCustomLine#

function isCustomLine(item: Pick<OrderItem, "kind" | "variant_id">): boolean

True for a line with no catalog link (kind: "custom", variant_id: null): never touches inventory, refunds by amount only.

isFullyFulfilled#

function isFullyFulfilled(order: OrderDetail): boolean

True when every order line has been fulfilled in full.

isSettableStatus#

function isSettableStatus(status: string): status is SettableOrderStatus

True when a string is one of the statuses an app may set through the API.

isZeroMoney#

function isZeroMoney(value: Money): boolean

True when the amount is exactly zero.

money#

function money(amountMinor: number, currency: string, decimals: number): Money

Money helpers over the platform's { amount_minor, currency, decimals }. Every function scales by the value's OWN decimals (SYP is zero-decimal on Dukkan, which no ISO table knows) and refuses to mix currencies.

moneyFromDecimalString#

function moneyFromDecimalString(text: string, currency: string, decimals: number): Money

Parses a major-unit string the merchant typed ("12.5") into minor units for the given exponent.

moneyToDecimalString#

function moneyToDecimalString(value: Money): string

Major-unit decimal string ("2500", "12.50") without float arithmetic.

reconcileOrderTotals#

function reconcileOrderTotals(order: OrderDetail): ReconciliationResult

Checks the two accounting identities the platform guarantees (docs/apps/money): line totals against the subtotal according to line_pricing, and subtotal - discount + shipping + tax = total. Use it in cash-book and reporting apps instead of your own sum. Custom lines (kind: "custom", variant_id: null) count like any other line: they carry their own total.

refundableAmount#

function refundableAmount(order: Order): Money

Captured funds still refundable: paid minus refunded (what the platform gates refunds on).

subtractMoney#

function subtractMoney(a: Money, b: Money): Money

Subtracts b from a in the same currency; the result may be negative.

sumMoney#

function sumMoney(values: readonly Money[], zero: Money): Money

Adds a list of amounts starting from zero (which fixes the currency and exponent of an empty list).

syncByUpdatedAt#

function syncByUpdatedAt<T extends Timestamped>(list: (query: { updated_at_min?: string; cursor?: string; limit?: number; }) => Promise<Page<T>>, options: SyncOptions<T>): Promise<SyncResult>

Pages through a list endpoint ordered by updated_at from a high-water mark, handing every page of changed records to onPage, and returns the new mark. Rerunnable: an interrupted run resumes from the last mark you saved.

syncOrders#

const syncOrders: (list: (query: { updated_at_min?: string; cursor?: string; limit?: number; }) => Promise<Page<Order>>, options: SyncOptions<Order>) => Promise<SyncResult>

syncByUpdatedAt over client.orders.

syncProducts#

const syncProducts: (list: (query: { updated_at_min?: string; cursor?: string; limit?: number; }) => Promise<Page<Product>>, options: SyncOptions<Product>) => Promise<SyncResult>

syncByUpdatedAt over client.products.

unfulfilledLines#

function unfulfilledLines(order: OrderDetail): UnfulfilledLine[]

Quantities not yet covered by a live fulfillment (cancelled ones release their lines), mirroring the server's over-fulfilment check.

Interfaces

ConvertedLine#

interface ConvertedLine

A converted line: the unit price rounded in the order currency, and quantity × unit.

Members
NameTypeDescription
unit_price_minorRequirednumber
total_minorRequirednumber

CurrencyExponent#

interface CurrencyExponent

A currency's minor-unit exponent on Dukkan (SYP 0, USD 2, JOD 3); read it from GET /store or a Money value, never from an ISO table.

Members
NameTypeDescription
decimalsRequirednumber

PricedLine#

interface PricedLine

One quoted line in the PRICING currency: a catalog variant or a custom line with a name.

Members
NameTypeDescription
variant_idRequiredstring | null
namestring | undefined
quantityRequirednumber
unit_price_minorRequirednumber

Unit price in minor units of the pricing currency.

ReconciliationResult#

interface ReconciliationResult

Outcome of reconcileOrderTotals: whether the lines, discounts, shipping and total agree, and by how much they differ.

Members
NameTypeDescription
okRequiredboolean
problemsRequiredArray<{ rule: "lines_vs_subtotal" | "totals_identity"; expected: Money; actual: Money; }>

Which identity failed, with the two sides, when ok is false.

ScaledRate#

interface ScaledRate

An FX rate as the platform carries it: rate = scaled / 10^scale, oriented 1 pricing-currency unit = rate order-currency units.

Members
NameTypeDescription
scaledRequirednumber
scaleRequirednumber

SyncOptions#

interface SyncOptions<T>

Incremental sync (docs/apps/webhooks#reconcile): webhooks are at-least-once and can lag; a periodic walk of updated_at_min catches what a receiver missed. The high-water mark is the newest updated_at seen, minus a small overlap so a write that committed at the same second is never skipped.

Members
NameTypeDescription
sincestring | null | undefined

RFC 3339 instant of the last completed sync; omit for a full walk.

overlapSecondsnumber | undefined

Seconds subtracted from since to overlap the previous run (default 60).

onPageRequired(items: T[], page: Page<T>) => Promise<boolean | void> | boolean | void

Called per page; return false to stop early.

limitnumber | undefined

SyncResult#

interface SyncResult

What a sync run reports: records seen and the high-water mark to persist for the next run.

Members
NameTypeDescription
highWaterMarkRequiredstring | null

Store this as since for the next run.

itemsRequirednumber
pagesRequirednumber

UnfulfilledLine#

interface UnfulfilledLine

An order line and the quantity of it that still awaits a fulfillment.

Members
NameTypeDescription
order_item_idRequiredstring
orderedRequirednumber
fulfilledRequirednumber
remainingRequirednumber

Types

CreateOrderItem#

type CreateOrderItem = Schemas["CreateOrderItem"]

Fulfillment#

type Fulfillment = Schemas["Fulfillment"]

Money#

type Money = components["schemas"]["Money"]

Order#

type Order = Schemas["Order"]

OrderDetail#

type OrderDetail = Schemas["OrderDetail"]

OrderItem#

type OrderItem = Schemas["OrderItem"]

Store#

type Store = Schemas["Store"]