Skip to content

Releases: koardlabs/koard-ios

1.0.20

Choose a tag to compare

@OhBehive OhBehive released this 04 Jul 16:44

[1.0.20] - 2026-07-03

Behavior changes for integrators

  • linkAccountAsync() now throws when linking fails or is declined (previously it never threw). Wrap the call in try/catch and handle the failure — e.g. keep showing your "link account" prompt.
  • prepare() can now throw the new KoardMerchantSDKError.readerTokenInvalid. Handle it in your catch; a retry typically recovers.
  • Changing the API key resets the session — after reconfiguring the SDK with a different key, log in again before making requests.

Fixed

  • Reconfiguring the SDK with a different API key now starts a fresh session instead of reusing the previous one.
  • The card reader no longer briefly shows "ready" after logout, or when switching merchant or location.
  • An unlinked device no longer flashes "preparing" before reporting that the account isn't linked.
  • Account-linking failures are now reported to the caller instead of being silently ignored.

Added

  • KoardMerchantSDKError.readerTokenInvalid — reported when the reader token is rejected (for example, a token used against the wrong environment).

Changed

  • Card-reader preparation is faster.

1.0.19

Choose a tag to compare

@OhBehive OhBehive released this 03 Jul 01:18
81ba5b3

[1.0.19] - 2026-07-02

⚠️ Behavior changes for integrators

  • prepare() no longer auto-links the Tap to Pay account. Previously
    prepare() (which also runs from the didBecomeActive observer) silently
    presented the Apple account-linking sheet when the device wasn't linked. If
    the merchant declined, it threw and got re-presented on every app activation —
    an endless link prompt. prepare() now throws the new
    KoardMerchantSDKError.accountNotLinked instead of auto-linking. Guard for it
    and drive linking explicitly via the public linkAccount() API (this restores
    merchant-side control over how and when linking is presented). The background
    prepare retry loop treats not-linked as terminal and no longer retries it.

Added

  • KoardMerchantSDKError.accountNotLinked — thrown by prepare() when the
    device isn't linked to the merchant's Tap to Pay account, so callers can guard
    it and explicitly call linkAccount() instead of the SDK silently presenting
    the Apple linking sheet.

Fixed

  • Endless Tap to Pay account-linking prompt after a decline. A merchant who
    declined the Apple account-linking sheet was re-prompted on every app
    activation, because prepare() (run from didBecomeActive) auto-linked and
    the retry loop re-presented the sheet. prepare() now surfaces
    accountNotLinked and leaves linking to the explicit linkAccount() call
    (see Behavior changes), breaking the loop.
  • Reader stuck reporting .preparing on an unlinked account. prepare()
    set the reader status to .preparing before the linked-account check; when the
    account wasn't linked it threw without clearing the status, so callers polling
    status saw .preparing forever (the UI showed "preparing card reader"
    indefinitely). The status is now reset to .notReady before the
    accountNotLinked error is thrown.

1.0.18

Choose a tag to compare

@OhBehive OhBehive released this 24 Jun 13:53
43d636d

Changelog

All notable changes to KoardSDK are documented here.

The format is based on Keep a Changelog,
and this project aims to follow Semantic Versioning.

The public SDK ships as a resilient binary KoardSDK.xcframework
(BUILD_LIBRARY_FOR_DISTRIBUTION=YES). The changes below introduce no
breaking API or ABI changes
— all public method signatures are unchanged,
and the two new error cases are additive on non-@frozen enums (source- and
binary-compatible). They do, however, include runtime behavior changes
integrators should be aware of — see ⚠️ Behavior changes under each
release. Recommended bump: minor.

[1.0.18] - 2026-06-24

⚠️ Behavior changes for integrators

These do not break compilation, but they change the error/value you receive at
runtime. Update your catch / switch logic if you key off the old values:

  • Tap to Pay cancellation now has its own error. When the customer cancels
    at the Apple Tap to Pay sheet, sale(...), refund(..., withTap: true), and
    the pre-auth flow now throw KoardMerchantSDKError.TTPPaymentFailed(.canceled).
    Previously this surfaced as .TTPPaymentFailed(.paymentCardReaderNilResult).
    If you were treating .paymentCardReaderNilResult as "canceled", switch to
    .canceled. Other reader read failures now surface as
    .paymentCardReaderError(underlying) instead of a nil result.
  • Network/transport errors are now typed. Offline / timeout / cannot-connect
    failures are thrown as KoardMerchantSDKError.network(description:underlying:)
    instead of a raw URLError. The original URLError is available via
    underlying. If you did catch let e as URLError, switch to matching
    .network.
  • Rate limiting (HTTP 429) is now distinct. A 429 throws the new
    KoardMerchantSDKError.rateLimited(message:). Previously it surfaced as
    .server or .unknown. Callers may use this to back off / retry.
  • Server errors no longer leak decoding failures. HTTP 400/404/5xx with a
    body that isn't the expected JSON (e.g. an HTML 502 page, an empty 500) now
    throw .server(message:) with a clean "Server error (HTTP <code>)" message
    instead of .unknown(<JSON decoding error>) (the old "data couldn't be
    read / unparseable" message).
  • "Not authenticated" precondition is now .unauthorized. Calling a
    payment / refund / pre-auth operation with no active session throws
    .unauthorized instead of .invalidRequest.

Fixed

  • logout() no longer wipes the host app's Keychain. logout() previously
    issued a blanket Keychain delete scoped to the app's service, removing all
    generic-password items the host app owned — not just Koard's. It now deletes
    only the SDK's own keys. (The SDK does not rename its Keychain service, so
    existing logged-in sessions are preserved across this update — no forced
    re-login.)
  • Intermittent ReceivedNilTokenError on Tap to Pay. The merchant session
    token was treated as valid indefinitely; once it passed its real (server-side)
    expiry the SDK kept sending the dead token, the card-reader token endpoint
    returned 401, and that 401 was decoded into a nil token and surfaced as an
    opaque ReceivedNilTokenError. The SDK now tracks the JWT's real exp
    claim, refuses to reuse an expired token, and surfaces the real HTTP status.
  • readerBusy errors when switching location / charging too quickly.
    PaymentCardReader permits one operation at a time; overlapping
    prepare / linkAccount / isAccountLinked / read calls collided
    (PaymentCardReaderError error 18). Reader operations are now serialized — a
    sale started while prepare() is still running waits for readiness instead of
    failing.
  • Card-reader read failures are no longer swallowed. A read error
    (cancellation, etc.) previously returned a nil result that collapsed into a
    generic paymentCardReaderNilResult; the real outcome is now surfaced (see
    Behavior changes).
  • Charges no longer run against a stale location after a location switch.
    The reader session now records which location it was prepared for; a sale
    started after the active location changed re-prepares the session (under the
    reader lock) before charging, instead of charging against the session bound to
    the previously-selected location. Closes a race between "switch location" and
    an immediately-following sale.

Added

  • login(alias:) — log in with a single opaque alias string instead of a
    code + PIN, for integrators who have already resolved the merchant identity
    (e.g. QR scan, SSO callback, or a server-issued provisioning token). It hits
    the same /v1/merchant/login route and yields the same session token as
    login(code:pin:); like that method it is session-auth only — the session
    token is persisted, the alias itself is never stored.
  • deviceType on transaction responses (KoardTransaction /
    TransactionResponse) — optional String? reporting the device that
    originated the transaction. Additive and decoded via decodeIfPresent, so
    existing decoders are unaffected.
  • KoardMerchantSDKError.rateLimited(message:) — HTTP 429.
  • KoardMerchantSDKError.TTPPaymentError.canceled — customer canceled at the
    Tap to Pay sheet (a benign outcome, distinct from a failure).
  • First unit/integration test suite covering JWT-expiry decoding, session-token
    refresh/expiry, the masked-401 → typed-error path, and Keychain logout
    scoping.

Changed

  • login(code:pin:) no longer persists the merchant code or PIN to the
    Keychain. Authentication state is determined solely by the session token;
    credentials are never stored. (Re-authenticate via login(...) after a
    session expires.)
  • HTTP error mapping is now explicit per status code:
    400.server, 401/403.unauthorized, 423.blockedAccount,
    429.rateLimited, other non-2xx (404/408/5xx/…) → .server with a
    readable message and the status code.

1.0.17

Choose a tag to compare

@rtvanals rtvanals released this 13 May 13:39
d741007

What’s new

Partial approval flowpreauth(...) and sale(...) now accept an optional
partialAuthTransactionId parameter to complete the remaining amount of a
partial approval. New properties on KoardTransaction: isPartialApproval,
authorizedAmount, remainingAmount. New StatusReason.partialApproval case.

Enhancements

  • KoardDescribableError now conforms to LocalizedError, exposing
      errorDescription and failureReason directly.

Distribution

KoardSDK now ships as a static framework. The public API is unchanged.

If Xcode warns that KoardSDK.framework is missing an executable, change the
KoardSDK.xcframework entry under your app target’s General → Frameworks &
Libraries from Embed & Sign to Do Not Embed, clean build folder
(Cmd+Shift+K), and rebuild.

What's Changed

Full Changelog: 1.0.16...1.0.17

1.0.16

Choose a tag to compare

@rtvanals rtvanals released this 10 Feb 16:20
98e01cd

What's Changed

Full Changelog: 1.0.15...1.0.16

1.0.15

Choose a tag to compare

@rtvanals rtvanals released this 10 Nov 15:04

What's Changed

  • Update demo code to call SDK to switch locations by @rtvanals in #11

Full Changelog: 1.0.14...1.0.15

1.0.14

Choose a tag to compare

@rtvanals rtvanals released this 30 Aug 14:42
07fa2d8

What's Changed

  • Update token refresh to account for reset device by @rtvanals in #10

Full Changelog: 1.0.13...1.0.14

1.0.13

Choose a tag to compare

@rtvanals rtvanals released this 28 Aug 17:17
086e0a7

What's Changed

Full Changelog: 1.0.12...1.0.13

1.0.12

Choose a tag to compare

@rtvanals rtvanals released this 21 Aug 21:03
  • Fix issue with unicast event stream not logging outside of the SDK

Full Changelog: 1.0.11...1.0.12

1.0.11

Choose a tag to compare

@rtvanals rtvanals released this 16 Aug 02:19

Full Changelog:

  • Optimize publishing reader events

1.0.10...1.0.11