@paltronics/skywire-pos-client
v0.6.0
Published
TypeScript client for the SkyWire POS API — terminal authentication, terminals, menus, orders, and payments.
Maintainers
Readme
@paltronics/skywire-pos-client
A typed TypeScript client for the SkyWire POS API — terminal authentication, terminals,
menus, orders, and payments. Zero runtime dependencies (uses the built-in fetch), ships with
full type definitions, and works in Node 18+, browsers, and edge runtimes.
Authentication is handled for you: the client exchanges your API key + terminal key for a short-lived terminal token, caches it, refreshes it before expiry, and transparently re-authenticates and retries once if the server rejects a token.
Install
npm install @paltronics/skywire-pos-clientRequires a runtime with a global
fetch(Node 18+, modern browsers, edge). For older Node, pass afetchimplementation via options (see Configuration).
Quick start
import { createSkywirePosClient, OrderType, PaymentProcessingMethod } from '@paltronics/skywire-pos-client';
const client = createSkywirePosClient({
baseUrl: 'https://pos.example.com',
apiKey: process.env.SKYWIRE_API_KEY!,
terminalKey: process.env.SKYWIRE_TERMINAL_KEY!,
});
// 1. Read configuration
const terminal = await client.getTerminal();
const { menus } = await client.getActiveMenus();
// 2. Build an order
let order = await client.createOrder({
name: 'Kiosk 3',
serviceType: OrderType.Togo,
checks: [
{
sales: [
{
quantity: 1,
productId: '0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9',
productGroupId: '9f8e7d6c-5b4a-3021-fedc-ba9876543210',
revenueTypeId: '11112222-3333-4444-5555-666677778888',
},
],
},
],
});
await client.sendSalesToKitchen(order.id);
// 3. Take payment
const [paymentType] = await client.listPaymentTypes();
if (paymentType.requiresInitialization) {
order = await client.initializePayment({ orderId: order.id, tenderTypeId: paymentType.id, checkNumber: 1 });
}
const { order: paid } = await client.pay({
orderId: order.id,
checkNumber: 1,
tenderTypeId: paymentType.id,
paymentProcessingMethod: PaymentProcessingMethod.AuthAndCapture,
});
// 4. Finish
if (paid.totals.balance === 0) {
await client.closeOrder(paid.id);
await client.emailReceipt({ orderId: paid.id, emailAddresses: ['[email protected]'] });
}Configuration
createSkywirePosClient(options):
| Option | Type | Default | Notes |
|--------|------|---------|-------|
| baseUrl | string | — | API server origin, e.g. https://pos.example.com. A trailing /api or / is accepted and normalized. |
| apiKey | string | — | Sent as the X-Api-Key header. |
| terminalKey | string | — | Identifies the terminal to authenticate as. |
| tokenExpirationBufferSeconds | number | 120 | Refresh the token this many seconds before it expires. |
| maxRetries | number | 2 | Extra attempts for safe (GET) requests on transient failures. |
| timeoutMs | number | 20000 | Abort a single attempt after this many ms (fresh per retry). 0 disables. A timed-out request surfaces with status: 0. |
| fetch | typeof fetch | global fetch | Custom fetch (testing / non-standard runtimes). |
Each client is bound to one terminal and manages its own token. To serve multiple terminals, create one client per terminal key.
Methods
| Method | HTTP | Returns |
|--------|------|---------|
| authenticate() | POST /api/terminalauthentication | AuthorizedTerminalResponse |
| getTerminal() | GET /api/terminal/{id} | TerminalResponse |
| getActiveMenus(targetDateUtc?) | GET /api/menu/active/all | ActiveMenusResponse |
| getActiveMenuPriceOverrides(targetDateUtc?) | GET /api/menu/active/price-overrides | ActivePriceOverridesResponse |
| listPaymentTypes() | GET /api/payments | PaymentTypeResponse[] |
| initializePayment(req) | POST /api/payments/preinit | OrderResponse |
| inquirePayment(req) | POST /api/payments/inquire | PaymentInquireResponse |
| pay(req) | POST /api/payments/pay | PaymentCompletedResponse |
| createOrder(req) | POST /api/order | OrderResponse |
| getOrder(orderId) | GET /api/order/{id} | OrderResponse |
| addSales(req) | POST /api/order/AddSales | OrderResponse |
| deleteSales(req) | POST /api/order/DeleteSales | OrderResponse |
| updateSaleQuantity(req) | POST /api/order/UpdateSaleQuantity | OrderResponse |
| updateServiceType(req) | PUT /api/order/UpdateServiceType | OrderResponse |
| sendSalesToKitchen(orderId) | PUT /api/order/SendSalesToKitchen | OrderResponse |
| closeOrder(orderId) | PUT /api/order/Close | OrderResponse |
| deleteOrder(orderId) | DELETE /api/order?orderId= | OrderResponse |
| associateCustomerAccount(req) | POST /api/order/AssociateCustomerAccount | OrderResponse |
| emailReceipt(req) | POST /api/order/Email | void |
| textReceipt(req) | POST /api/order/Text | void |
| exportItemizedReceipts(req) | POST /api/order/ItemizedReceipts | ReceiptExportResponse[] |
Error handling
Every non-2xx response throws a SkywirePosApiError. Branch on errorCode (stable) rather than
message (human-facing). Authentication failures throw SkywirePosAuthError (a subclass).
import { SkywirePosApiError, SkywirePosAuthError, PosErrorCodes } from '@paltronics/skywire-pos-client';
try {
await client.closeOrder(orderId);
} catch (err) {
if (err instanceof SkywirePosAuthError) {
// bad credentials, or 401 after a re-auth attempt
} else if (err instanceof SkywirePosApiError) {
if (err.errorCode === PosErrorCodes.ORDER_CLOSED) {
// 409 — already closed
}
console.error(err.status, err.errorCode, err.message, err.details);
}
}SkywirePosApiError fields: status (HTTP status, or 0 for a network failure), errorCode
(or null), message, details (populated for invalid_model), and body (raw parsed body).
A rejected payment surfaces as SkywirePosApiError with HTTP 402 and a stable errorCode —
PosErrorCodes.PAYMENT_DECLINED (declined / insufficient funds) or PosErrorCodes.PAYMENT_REFERRAL
(needs voice authorization). The processor's own status code is preserved in details. Treat these
as expected outcomes (prompt for another tender), not outages. A request that exceeds timeoutMs
surfaces like any network failure, with status: 0.
Enums
Enum values are transmitted as integers. Use the exported enums instead of magic numbers:
OrderType—DineInTogoDeliveryOnlinePaymentProcessingMethod—AuthorizeAuthAndCaptureStraightCapturePaymentCategory—CashCreditDebitRoomRewardsOtherModifierType—RequiredOptionalReceiptExportFormat—Html
Retries & idempotency
- Safe requests (GET) are retried up to
maxRetriestimes on network errors,408,429, and5xx, with exponential backoff. - Mutating requests (POST/PUT/DELETE) are not retried on transient failures, because they may have already applied server-side.
- A
401on any request triggers a single re-authenticate-and-retry, regardless of method. - Each attempt is bounded by
timeoutMs(default20s). A timed-out attempt is treated like a network error — retried for GETs, otherwise surfaced withstatus: 0.
Development
npm install
npm run build # bundle ESM + CJS + type declarations into dist/
npm run typecheck # tsc --noEmit
npm test # run the vitest suitePublishing
The package is scoped and set to public access. To publish:
npm run build
npm publishprepublishOnly runs the build automatically, and only dist/ and README.md are included in
the published tarball.
