Skip to content

Troubleshooting Guide

This section documents real issues encountered while integrating the SDK, in the order a developer is likely to encounter them, along with their root cause and solution. Each entry corresponds to an issue that was actually diagnosed during SDK development.

1. Kotlin Long ↔ Swift Type Mismatch

Symptom

Compile error or runtime cast exception around amount fields, for example:

swift
Int32(item.harga) as! KotlinLong   // Invalid — these are unrelated types

Cause

Kotlin Long is exposed to Swift as the boxed class KotlinLong, while Kotlin Int is exposed as native Int32.

These types cannot be cast into each other.

Fix

Wrap Long fields explicitly, while leaving Int fields unchanged.

swift
// Long fields (e.g. PaymentDetails.amount)
amount: KotlinLong(value: Int64(item.harga))

// Int fields (e.g. ItemDetails.amount)
amount: Int32(item.harga)

2. "View is not in the window hierarchy" when presenting on iOS

Symptom

text
Attempt to present <ComposeHostingViewController> on
<PresentationHostingController...> whose view is not in the window hierarchy.

Cause

The SDK's UIViewController is presented in the same execution flow that dismisses a SwiftUI .sheet.

The presenting controller has not yet returned to the window hierarchy.

Fix

Dismiss the sheet first, then present the SDK after observing the state change using .onChange, instead of relying on a fixed delay.

swift
@State private var selectedItem: Item? = nil
@State private var pendingItem: Item? = nil

.sheet(item: $selectedItem) { item in
    ConfirmationSheet(onConfirm: {
        pendingItem = item
        selectedItem = nil
    })
}
.onChange(of: selectedItem) { newValue in
    if newValue == nil,
       let item = pendingItem {

        pendingItem = nil

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
            presentPaymentGateway(item: item)
        }
    }
}

More Robust Variant

Resolve the presenter from the application's key window and retry while the current presenter is still being dismissed.

swift
func resolvePresenter(
    attempt: Int = 0,
    _ completion: @escaping (UIViewController) -> Void
) {
    guard let root = UIApplication.shared.connectedScenes
        .compactMap({ $0 as? UIWindowScene }).first?
        .windows.first(where: { $0.isKeyWindow })?
        .rootViewController else {
        return
    }

    var top = root

    while let presented = top.presentedViewController {
        top = presented
    }

    if top.isBeingDismissed || top.view.window == nil,
       attempt < 10 {

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            resolvePresenter(attempt: attempt + 1, completion)
        }

        return
    }

    completion(top)
}

3. IrLinkageError: HttpTimeout.Plugin

Symptom

Application crashes during the first Compose render on iOS.

text
kotlin.internal.IrLinkageError:

Can not get instance of singleton 'Plugin':
No class found for symbol

io.ktor.client.plugins/HttpTimeout.Plugin|null[0]

Cause

Different Ktor versions are declared across source sets.

Example:

  • commonMainktor-client-core:3.1.3
  • iosMainktor-client-darwin:2.3.7

Ktor 2.x and 3.x are incompatible internally.

Fix

Align every Ktor dependency to the exact same version.

kotlin
val ktorVersion = "3.1.3"

implementation("io.ktor:ktor-client-darwin:$ktorVersion")

Also remove duplicate Android dependencies pinned to an older version.

After Updating Versions

Run a clean build.

bash
./gradlew clean
./gradlew assemblePaymentGatewaySDKReleaseXCFramework

4. Exception Appears as NSTaggedDate

Symptom

LLDB displays:

text
Exception (__NSTaggedDate *)

2001-01-01 00:00:00 UTC

Cause

This is not the real exception.

It occurs when a Kotlin Throwable crosses the Objective-C bridge without being caught correctly.

Common causes include:

  • Callback signature mismatch
  • Debugger stopping at the wrong stack frame

Fix

Create an Objective-C exception breakpoint.

bash
breakpoint set -E objc

Restart the application completely.

bash
po $arg1

The real exception will now be visible.

Alternative

Wrap risky initialization inside try/catch.

kotlin
val viewModel = remember {
    try {
        PaymentViewModel(
            token = token,
            jsonData = json,
            onResultCallback = onResult
        )
    } catch (e: Throwable) {

        Napier.e(
            tag = "INIT",
            message = "VIEWMODEL CONSTRUCT FAILED: ${e::class.simpleName} - ${e.message}"
        )

        throw e
    }
}

5. Callback Closure Signature Mismatch

Symptom

Old Swift code still assumes:

swift
(Boolean) -> Unit

Example:

swift
) { success in
    if success as? Bool == true {
        print("Success")
    } else {
        print("Failed")
    }
}

Fix

Use the new PaymentSdkResult object.

swift
) { result in

    if result.isSuccess {
        print("Success, link: \(result.link ?? "-")")
    } else {
        print("Failed: \(result.responseMessage ?? "-")")
    }
}

Prevention

Whenever a public callback signature changes:

  • Rebuild the .xcframework
  • Perform a full clean before testing

6. LLDB / Xcode Messages That Can Be Ignored

The following debugger messages are expected and do not indicate SDK issues.

MessageWhy It's Harmless
could not execute support code to read Objective-C class dataLLDB limitation when inspecting optimized binaries
Can't show file for stack frame ... Trace.uikit.ktDebug symbols reference the CI build server path
Unable to simultaneously satisfy constraints ... TUIKeyplane.rightInternal iOS keyboard layout warning
RTIInputSystemClient ... requires a valid sessionIDInternal iOS text-input session warning

Annex A PaymentViewModel Public Surface

Reference summary of the public functions exposed by PaymentViewModel.

FunctionPurpose
updateCardNumber(number)Updates card number, formats spacing, detects card type
updateHolder(name)Updates cardholder name and converts it to uppercase
onExpiryDateChange(value)Formats expiry date as MM/YY
onCvvChange(value) / updateCvv(value)Updates CVV; updateCvv() also flips the card preview
flipCard(showBack)Flips the card preview manually
openNfc(context) / closeNfc(context)Starts/stops NFC session
autofillFromNfc(number, expiry, name)Populates card information from NFC
validateAll()Validates every field
onPayClicked(scope)Performs validation then triggers payment
processPayment(scope)Builds PaymentRequest and calls PaymentGateway.charge()
onSuccessDone() / onFailedDone()Invokes host callback with PaymentSdkResult
reset()Resets CardState

Annex B PaymentSdkResult Reference

kotlin
data class PaymentSdkResult(
    val isSuccess: Boolean,
    val responseCode: String? = null,
    val responseMessage: String? = null,
    val link: String? = null,
)

fun PaymentResponse.toSdkResult(
    isSuccess: Boolean
): PaymentSdkResult =
    PaymentSdkResult(
        isSuccess = isSuccess,
        responseCode = responseCode,
        responseMessage = responseMessage,
        link = data?.link,
    )

Both onSuccessDone() and onFailedDone() create this DTO before invoking the host application's callback.

Annex C Reference Implementation: iOS Host App

Contains the complete SwiftUI reference implementation demonstrating:

  • MainViewController() exported from Kotlin Multiplatform
  • Safe presentation after dismissing SwiftUI .sheet
  • Building PaymentRequest
  • Creating Base64 credentials
  • Launching the SDK
  • Receiving PaymentSdkResult

The full source code is included in the original documentation.

Annex D Gradle Configuration Reference

Key Gradle configuration illustrating proper Ktor version alignment.

kotlin
val ktorVersion = "3.1.3"

implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("io.ktor:ktor-client-auth:$ktorVersion")

implementation("io.ktor:ktor-client-okhttp:$ktorVersion")

implementation("io.ktor:ktor-client-darwin:$ktorVersion")

Important: Every Ktor dependency across all source sets must use the exact same version to avoid IrLinkageError.

iFortepay API Documentation