Releases: koardlabs/koard-ios
Release list
1.0.20
[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 intry/catchand handle the failure — e.g. keep showing your "link account" prompt.prepare()can now throw the newKoardMerchantSDKError.readerTokenInvalid. Handle it in yourcatch; 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
[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 thedidBecomeActiveobserver) 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.accountNotLinkedinstead of auto-linking. Guard for it
and drive linking explicitly via the publiclinkAccount()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 byprepare()when the
device isn't linked to the merchant's Tap to Pay account, so callers can guard
it and explicitly calllinkAccount()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, becauseprepare()(run fromdidBecomeActive) auto-linked and
the retry loop re-presented the sheet.prepare()now surfaces
accountNotLinkedand leaves linking to the explicitlinkAccount()call
(see Behavior changes), breaking the loop. - Reader stuck reporting
.preparingon an unlinked account.prepare()
set the reader status to.preparingbefore the linked-account check; when the
account wasn't linked it threw without clearing the status, so callers polling
statussaw.preparingforever (the UI showed "preparing card reader"
indefinitely). The status is now reset to.notReadybefore the
accountNotLinkederror is thrown.
1.0.18
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-@frozenenums (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 throwKoardMerchantSDKError.TTPPaymentFailed(.canceled).
Previously this surfaced as.TTPPaymentFailed(.paymentCardReaderNilResult).
If you were treating.paymentCardReaderNilResultas "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 asKoardMerchantSDKError.network(description:underlying:)
instead of a rawURLError. The originalURLErroris available via
underlying. If you didcatch 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
.serveror.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
.unauthorizedinstead 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
ReceivedNilTokenErroron 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
opaqueReceivedNilTokenError. The SDK now tracks the JWT's realexp
claim, refuses to reuse an expired token, and surfaces the real HTTP status. readerBusyerrors when switching location / charging too quickly.
PaymentCardReaderpermits one operation at a time; overlapping
prepare / linkAccount / isAccountLinked / read calls collided
(PaymentCardReaderErrorerror 18). Reader operations are now serialized — a
sale started whileprepare()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
genericpaymentCardReaderNilResult; 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/loginroute 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.deviceTypeon transaction responses (KoardTransaction/
TransactionResponse) — optionalString?reporting the device that
originated the transaction. Additive and decoded viadecodeIfPresent, 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 vialogin(...)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/…) →.serverwith a
readable message and the status code.
1.0.17
What’s new
Partial approval flow — preauth(...) 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
KoardDescribableErrornow conforms toLocalizedError, exposing
errorDescriptionandfailureReasondirectly.
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
1.0.15
1.0.14
1.0.13
1.0.12
- Fix issue with unicast event stream not logging outside of the SDK
Full Changelog: 1.0.11...1.0.12