Appearance
iOS
Step 1 Embed the framework
Drag PaymentGatewaySDK.xcframework into your Xcode project and add it under Frameworks, Libraries, and Embedded Content with Embed & Sign.
Step 2 Build the PaymentRequest and generate the token
Swift mirrors the Kotlin model closely, with two important type differences (see callout below):
swift
let paymentRequest = PaymentRequest(
externalId: "1Ktru19Cp7",
orderId: "ONxeTcEBE6",
currency: "IDR",
source: "payment_page",
paymentMethod: "card",
paymentChannel: "BRIQC",
paymentMode: "CLOSE",
paymentDetails: PaymentDetails(
amount: KotlinLong(value: Int64(amountValue)), // Long -> KotlinLong wrapper
isCustomerPayingFee: false,
transactionDescription: "Order #1234",
expiredTime: ""
),
itemDetails: [
ItemDetails(
itemId: "SKU-1",
name: "Shirt",
amount: Int32(unitPrice), // Int -> Int32, no wrapper needed
qty: 1,
description: ""
)
],
// ...customerDetails, billingAddress, shippingAddress,
// cardDetails, returnUrl, callbackUrl, paymentOptions
)
let credential = "\(merchantId):\(secretUnbound):\(hashKey)"
let token = Data(credential.utf8).base64EncodedString()
let json = JsonHelper().toJson(request: paymentRequest)Kotlin → Swift numeric type mapping
Kotlin
Longis exposed in Swift as the boxed classKotlinLong— never cast a SwiftInt32directly to it.Always wrap using:
swiftKotlinLong(value: Int64(yourValue))Kotlin
Intis exposed in Swift as the nativeInt32— no wrapper needed.swiftInt32(yourValue)Mixing these up (e.g.
Int32(value)asKotlinLong) compiles inconsistently across toolchains and fails at runtime with a cast exception.
Step 3 Launch the SDK screen
swift
let vc = MainViewControllerKt.MainViewController(
token: token,
json: json
) { result in
if result.isSuccess {
print("Success, link: \(result.link ?? "-")")
} else {
print("Failed: \(result.responseMessage ?? "-")")
}
}
vc.modalPresentationStyle = .fullScreen
DispatchQueue.main.async {
presenter.present(vc, animated: true)
}Always present on the main thread, from a stable presenter
- Compose Multiplatform enforces that
setContentruns on the main thread. Presenting from a background queue raises anIllegalStateExceptioninside Compose's threading check.- Never present immediately after dismissing a SwiftUI
.sheetin the same call — the presenting view controller may not yet be back in the window hierarchy.- Wait for the sheet's dismissal to complete (for example, via
onChange(of:)on the@Statebinding) before presenting the SDK's view controller.- See Annex C for the complete pattern used in the reference app.
Wiring the NFC Scan Button (Both Platforms)
PaymentScreen exposes onOpenNfc and onCloseNfc callbacks that the host composable/view must forward into PaymentViewModel, since the context required to start a scan is platform-specific.
| Android | iOS | |
|---|---|---|
| Context value passed | A resolved Activity | Unit (CoreNFC needs no UIKit context) |
| openNfc call | viewModel.openNfc(activity) | viewModel.openNfc(Unit) |
| closeNfc call | viewModel.closeNfc(activity) | viewModel.closeNfc(Unit) |
iOS wiring example
Matching the MainViewController pattern shown earlier:
swift
PaymentScreen(
viewModel: viewModel,
onOpenNfc: {
viewModel.openNfc(Unit)
},
onCloseNfc: {
viewModel.closeNfc(Unit)
}
)