
Payment UI toolkit enabling secure card tokenization via the Conekta API, with built-in tokenizer, crypto and HTTP client, pluggable UI components and CDN-loaded card-brand icons.
Kotlin Multiplatform payment UI library for Android, iOS, and Web. Provides card tokenization components built on top of the Conekta API.
| Module | Artifact | Description | Platforms |
|---|---|---|---|
shared |
io.conekta:conekta-elements-shared |
Core business logic, HTTP client, tokenizer, crypto | Android, iOS, JS |
compose |
io.conekta:conekta-elements-compose |
Compose Multiplatform UI components | Android, iOS |
webApp |
@conekta/elements (npm) |
React + TypeScript library | Web |
dependencies {
implementation("io.conekta:conekta-elements-compose:0.2.0-beta.2")
}This includes conekta-elements-shared transitively. No need to add both.
dependencies {
implementation("io.conekta:conekta-elements-shared:0.2.0-beta.2")
}Artifacts are published to Maven Central. No additional repository configuration required.
For GitHub Packages (pre-release builds):
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
maven {
url = uri("https://maven.pkg.github.com/conekta/conekta-elements")
credentials {
username = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GP_USER")
password = providers.gradleProperty("gpr.token").orNull ?: System.getenv("GP_TOKEN")
}
}
}
}The compose module uses Coil 3 to load card brand icons from Conekta's CDN. Initialize it in your Application class:
import coil3.ImageLoader
import coil3.SingletonImageLoader
import io.conekta.compose.ConektaImageLoader
class MyApp : Application(), SingletonImageLoader.Factory {
override fun newImageLoader(context: Context): ImageLoader {
return ConektaImageLoader.newImageLoader(context)
}
}Register in AndroidManifest.xml:
<application android:name=".MyApp" ... >The compose module produces composeKit.xcframework, distributed as a zip asset in every GitHub release.
Add the package dependency in Xcode via File > Add Package Dependencies using the repository URL, or add a binary target in your Package.swift:
let package = Package(
...
targets: [
.binaryTarget(
name: "composeKit",
url: "https://github.com/conekta/conekta-elements/releases/download/0.2.0-beta.2/composeKit.xcframework.zip",
checksum: "e5fc1a7030756eea3f8cc4c229c178baac9369e4c3ba3238274f03824fdb02b2"
),
.target(
name: "YourTarget",
dependencies: ["composeKit"]
)
]
)The checksum is available in checksum.txt included in the release assets.
composeKit.xcframework.zip from the latest release
composeKit.xcframework into your Xcode projectnpm install @conekta/elementsSee webApp/README.md for full documentation.
All platforms require a Conekta public key to tokenize cards. Get yours from the Conekta Dashboard:
import io.conekta.compose.tokenizer.ConektaTokenizer
import io.conekta.elements.tokenizer.models.TokenizerConfig
import io.conekta.elements.tokenizer.models.TokenizerError
@Composable
fun PaymentScreen() {
ConektaTokenizer(
config = TokenizerConfig(
publicKey = "key_xxxxx",
merchantName = "My Store",
collectCardholderName = true,
),
onSuccess = { result ->
println("Token: ${result.token}")
println("Last four: ${result.lastFour}")
},
onError = { error ->
when (error) {
is TokenizerError.ValidationError -> println("Validation: ${error.message}")
is TokenizerError.NetworkError -> println("Network: ${error.message}")
is TokenizerError.ApiError -> println("API ${error.code}: ${error.message}")
}
},
)
}import SwiftUI
import composeKit
struct ContentView: View {
@State private var showingAlert = false
@State private var alertMessage = ""
var body: some View {
ConektaTokenizerView(
config: TokenizerConfig(
publicKey: "key_xxxxx",
merchantName: "My Store",
collectCardholderName: true
),
onSuccess: { result in
alertMessage = "Token: \(result.token)"
showingAlert = true
},
onError: { error in
if let apiError = error as? TokenizerError.TokenizerApiError {
alertMessage = "\(apiError.code): \(apiError.message)"
} else if let networkError = error as? TokenizerError.TokenizerNetworkError {
alertMessage = networkError.message
} else {
alertMessage = "Payment could not be processed."
}
showingAlert = true
}
)
}
}import { ConektaProvider, ExpressCheckout } from '@conekta/elements';
function App() {
return (
<ConektaProvider publicKey="key_xxx" environment="sandbox">
<ExpressCheckout
amount={10000}
currency="MXN"
onPaymentCompleted={(result) => console.log(result)}
/>
</ConektaProvider>
);
}| Tool | Version | Notes |
|---|---|---|
| JDK | 17 (17.0.7-tem) |
Managed via .sdkmanrc
|
| Node.js | 18 | Managed via .nvmrc
|
| Android SDK | compileSdk 36, minSdk 24 | |
| Xcode | 15+ | iOS builds only (macOS required) |
| Kotlin | 2.1.0 | Via Gradle plugin |
Install tooling:
# Java (via SDKMAN)
sdk env install
# Node.js (via nvm)
nvm installconekta-elements/
├── shared/ # Core KMP module (Android, iOS, JS)
│ └── src/
│ ├── commonMain/ # Shared business logic
│ │ └── kotlin/io/conekta/elements/
│ │ ├── tokenizer/
│ │ │ ├── api/ # HTTP client + API service
│ │ │ ├── crypto/ # AES + RSA encryption (expect/actual)
│ │ │ ├── formatters/ # Card number/CVV/expiry formatters
│ │ │ ├── models/ # TokenizerConfig, TokenResult, TokenizerError
│ │ │ └── validators/ # Form validation (Luhn, expiry, CVV)
│ │ └── assets/ # CDN URLs for card brand images
│ ├── androidMain/ # JCE crypto implementation
│ ├── iosMain/ # CommonCrypto/Security implementation
│ ├── jsMain/ # crypto-js + jsencrypt implementation
│ └── commonTest/ # Shared tests
├── compose/ # Compose Multiplatform UI module (Android, iOS)
│ └── src/
│ ├── commonMain/ # Shared composables
│ │ └── kotlin/io/conekta/compose/
│ │ ├── tokenizer/ # ConektaTokenizer composable
│ │ ├── components/ # TextField, CardBrandIcon, etc.
│ │ ├── theme/ # Colors, fonts
│ │ └── i18n/ # Localization (ES/EN)
│ └── androidHostTest/ # Robolectric UI tests
├── webApp/ # React + TypeScript web library
├── examples/ # Android + iOS example apps
│ ├── android/
│ └── ios/
├── buildSrc/ # Custom Gradle tasks (string validation, resources sync)
├── Package.swift # SPM binary target config
├── Makefile # Common build commands
└── .github/workflows/ # CI pipeline
All common tasks are available via make:
# Build
make build # Clean build all modules
make build-ci # Build with configuration cache (CI)
make build-XCFramework # Build iOS XCFramework
# Test
make shared-test # Run shared module tests (Android, iOS, JS) + coverage
make compose-test # Run compose UI tests (Robolectric) + coverage
make ios-test # Run iOS simulator tests only
# Lint
make lint-check # Check code style (ktlint)
make lint-fix # Auto-fix code style issues
# Publish
make publish-local # Publish to Maven Local (for local development)
make publish # Publish to remote repositories
# JS
make js-build # Build JS browser distributionOr use Gradle directly:
# Compile specific targets
./gradlew :shared:compileKotlinIosSimulatorArm64
./gradlew :shared:compileKotlinJs
./gradlew :compose:compileKotlinIosArm64
# Run specific test suites
./gradlew :shared:testAndroidHostTest
./gradlew :shared:jsNodeTest
./gradlew :shared:iosSimulatorArm64Test
./gradlew :compose:testAndroidHostTest
# String resource validation
./gradlew :compose:validateStringsOrder
./gradlew :compose:validateStringsSpelling
# Coverage reports
./gradlew :shared:koverXmlReport
./gradlew :compose:koverXmlReport# All tests across all platforms
make shared-test # shared: Android + iOS + JS + Kover coverage
make compose-test # compose: Robolectric Android UI tests + Kover coverage
# Quick verification (compile only, no tests)
./gradlew :shared:compileKotlinIosSimulatorArm64 :shared:compileKotlinJs :compose:compileKotlinIosArm64
# Full local validation (same as CI)
make lint-check && make shared-test && make compose-testCoverage reports are generated at:
shared/build/reports/kover/ (XML + HTML)compose/build/reports/kover/ (XML + HTML)The project uses ktlint with the following configuration (.editorconfig):
@Composable functions: exempt from naming rulesmake lint-check # Verify code style
make lint-fix # Auto-fix issuesmake publish-localThis publishes both shared and compose modules to ~/.m2/repository/. Consumer apps can use it by adding mavenLocal() to their repositories.
Handled by the deploy-maven-central.yml workflow on release. Requires signing keys configured as repository secrets.
Handled by the deploy.yml workflow. Requires GP_USER and GP_TOKEN environment variables.
make build-XCFrameworkProduces compose/build/XCFrameworks/release/composeKit.xcframework/ with Compose resources embedded in each slice.
The CI runs on every push to main and on pull requests (.github/workflows/ci.yml):
| Job | Runner | Steps |
|---|---|---|
validate_ios |
macOS | iOS shared tests, verify XCFramework resources |
build_kmp |
Linux | Version validation, build, shared tests, compose tests, ktlint, string validation, publish dry-run |
build_js |
Linux | JS build, web tests + coverage, lint, npm publish dry-run, Chromatic |
sonarcloud |
Linux | SonarQube analysis with merged coverage from KMP + JS |
Working example apps are available in the examples/ directory:
| Platform | Directory | Setup |
|---|---|---|
| Android | examples/android/ |
Set public key in MainActivity.kt
|
| iOS | examples/ios/ |
Set public key in Local.xcconfig
|
See examples/README.md for detailed setup instructions.
Kotlin Multiplatform payment UI library for Android, iOS, and Web. Provides card tokenization components built on top of the Conekta API.
| Module | Artifact | Description | Platforms |
|---|---|---|---|
shared |
io.conekta:conekta-elements-shared |
Core business logic, HTTP client, tokenizer, crypto | Android, iOS, JS |
compose |
io.conekta:conekta-elements-compose |
Compose Multiplatform UI components | Android, iOS |
webApp |
@conekta/elements (npm) |
React + TypeScript library | Web |
dependencies {
implementation("io.conekta:conekta-elements-compose:0.2.0-beta.2")
}This includes conekta-elements-shared transitively. No need to add both.
dependencies {
implementation("io.conekta:conekta-elements-shared:0.2.0-beta.2")
}Artifacts are published to Maven Central. No additional repository configuration required.
For GitHub Packages (pre-release builds):
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
maven {
url = uri("https://maven.pkg.github.com/conekta/conekta-elements")
credentials {
username = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GP_USER")
password = providers.gradleProperty("gpr.token").orNull ?: System.getenv("GP_TOKEN")
}
}
}
}The compose module uses Coil 3 to load card brand icons from Conekta's CDN. Initialize it in your Application class:
import coil3.ImageLoader
import coil3.SingletonImageLoader
import io.conekta.compose.ConektaImageLoader
class MyApp : Application(), SingletonImageLoader.Factory {
override fun newImageLoader(context: Context): ImageLoader {
return ConektaImageLoader.newImageLoader(context)
}
}Register in AndroidManifest.xml:
<application android:name=".MyApp" ... >The compose module produces composeKit.xcframework, distributed as a zip asset in every GitHub release.
Add the package dependency in Xcode via File > Add Package Dependencies using the repository URL, or add a binary target in your Package.swift:
let package = Package(
...
targets: [
.binaryTarget(
name: "composeKit",
url: "https://github.com/conekta/conekta-elements/releases/download/0.2.0-beta.2/composeKit.xcframework.zip",
checksum: "e5fc1a7030756eea3f8cc4c229c178baac9369e4c3ba3238274f03824fdb02b2"
),
.target(
name: "YourTarget",
dependencies: ["composeKit"]
)
]
)The checksum is available in checksum.txt included in the release assets.
composeKit.xcframework.zip from the latest release
composeKit.xcframework into your Xcode projectnpm install @conekta/elementsSee webApp/README.md for full documentation.
All platforms require a Conekta public key to tokenize cards. Get yours from the Conekta Dashboard:
import io.conekta.compose.tokenizer.ConektaTokenizer
import io.conekta.elements.tokenizer.models.TokenizerConfig
import io.conekta.elements.tokenizer.models.TokenizerError
@Composable
fun PaymentScreen() {
ConektaTokenizer(
config = TokenizerConfig(
publicKey = "key_xxxxx",
merchantName = "My Store",
collectCardholderName = true,
),
onSuccess = { result ->
println("Token: ${result.token}")
println("Last four: ${result.lastFour}")
},
onError = { error ->
when (error) {
is TokenizerError.ValidationError -> println("Validation: ${error.message}")
is TokenizerError.NetworkError -> println("Network: ${error.message}")
is TokenizerError.ApiError -> println("API ${error.code}: ${error.message}")
}
},
)
}import SwiftUI
import composeKit
struct ContentView: View {
@State private var showingAlert = false
@State private var alertMessage = ""
var body: some View {
ConektaTokenizerView(
config: TokenizerConfig(
publicKey: "key_xxxxx",
merchantName: "My Store",
collectCardholderName: true
),
onSuccess: { result in
alertMessage = "Token: \(result.token)"
showingAlert = true
},
onError: { error in
if let apiError = error as? TokenizerError.TokenizerApiError {
alertMessage = "\(apiError.code): \(apiError.message)"
} else if let networkError = error as? TokenizerError.TokenizerNetworkError {
alertMessage = networkError.message
} else {
alertMessage = "Payment could not be processed."
}
showingAlert = true
}
)
}
}import { ConektaProvider, ExpressCheckout } from '@conekta/elements';
function App() {
return (
<ConektaProvider publicKey="key_xxx" environment="sandbox">
<ExpressCheckout
amount={10000}
currency="MXN"
onPaymentCompleted={(result) => console.log(result)}
/>
</ConektaProvider>
);
}| Tool | Version | Notes |
|---|---|---|
| JDK | 17 (17.0.7-tem) |
Managed via .sdkmanrc
|
| Node.js | 18 | Managed via .nvmrc
|
| Android SDK | compileSdk 36, minSdk 24 | |
| Xcode | 15+ | iOS builds only (macOS required) |
| Kotlin | 2.1.0 | Via Gradle plugin |
Install tooling:
# Java (via SDKMAN)
sdk env install
# Node.js (via nvm)
nvm installconekta-elements/
├── shared/ # Core KMP module (Android, iOS, JS)
│ └── src/
│ ├── commonMain/ # Shared business logic
│ │ └── kotlin/io/conekta/elements/
│ │ ├── tokenizer/
│ │ │ ├── api/ # HTTP client + API service
│ │ │ ├── crypto/ # AES + RSA encryption (expect/actual)
│ │ │ ├── formatters/ # Card number/CVV/expiry formatters
│ │ │ ├── models/ # TokenizerConfig, TokenResult, TokenizerError
│ │ │ └── validators/ # Form validation (Luhn, expiry, CVV)
│ │ └── assets/ # CDN URLs for card brand images
│ ├── androidMain/ # JCE crypto implementation
│ ├── iosMain/ # CommonCrypto/Security implementation
│ ├── jsMain/ # crypto-js + jsencrypt implementation
│ └── commonTest/ # Shared tests
├── compose/ # Compose Multiplatform UI module (Android, iOS)
│ └── src/
│ ├── commonMain/ # Shared composables
│ │ └── kotlin/io/conekta/compose/
│ │ ├── tokenizer/ # ConektaTokenizer composable
│ │ ├── components/ # TextField, CardBrandIcon, etc.
│ │ ├── theme/ # Colors, fonts
│ │ └── i18n/ # Localization (ES/EN)
│ └── androidHostTest/ # Robolectric UI tests
├── webApp/ # React + TypeScript web library
├── examples/ # Android + iOS example apps
│ ├── android/
│ └── ios/
├── buildSrc/ # Custom Gradle tasks (string validation, resources sync)
├── Package.swift # SPM binary target config
├── Makefile # Common build commands
└── .github/workflows/ # CI pipeline
All common tasks are available via make:
# Build
make build # Clean build all modules
make build-ci # Build with configuration cache (CI)
make build-XCFramework # Build iOS XCFramework
# Test
make shared-test # Run shared module tests (Android, iOS, JS) + coverage
make compose-test # Run compose UI tests (Robolectric) + coverage
make ios-test # Run iOS simulator tests only
# Lint
make lint-check # Check code style (ktlint)
make lint-fix # Auto-fix code style issues
# Publish
make publish-local # Publish to Maven Local (for local development)
make publish # Publish to remote repositories
# JS
make js-build # Build JS browser distributionOr use Gradle directly:
# Compile specific targets
./gradlew :shared:compileKotlinIosSimulatorArm64
./gradlew :shared:compileKotlinJs
./gradlew :compose:compileKotlinIosArm64
# Run specific test suites
./gradlew :shared:testAndroidHostTest
./gradlew :shared:jsNodeTest
./gradlew :shared:iosSimulatorArm64Test
./gradlew :compose:testAndroidHostTest
# String resource validation
./gradlew :compose:validateStringsOrder
./gradlew :compose:validateStringsSpelling
# Coverage reports
./gradlew :shared:koverXmlReport
./gradlew :compose:koverXmlReport# All tests across all platforms
make shared-test # shared: Android + iOS + JS + Kover coverage
make compose-test # compose: Robolectric Android UI tests + Kover coverage
# Quick verification (compile only, no tests)
./gradlew :shared:compileKotlinIosSimulatorArm64 :shared:compileKotlinJs :compose:compileKotlinIosArm64
# Full local validation (same as CI)
make lint-check && make shared-test && make compose-testCoverage reports are generated at:
shared/build/reports/kover/ (XML + HTML)compose/build/reports/kover/ (XML + HTML)The project uses ktlint with the following configuration (.editorconfig):
@Composable functions: exempt from naming rulesmake lint-check # Verify code style
make lint-fix # Auto-fix issuesmake publish-localThis publishes both shared and compose modules to ~/.m2/repository/. Consumer apps can use it by adding mavenLocal() to their repositories.
Handled by the deploy-maven-central.yml workflow on release. Requires signing keys configured as repository secrets.
Handled by the deploy.yml workflow. Requires GP_USER and GP_TOKEN environment variables.
make build-XCFrameworkProduces compose/build/XCFrameworks/release/composeKit.xcframework/ with Compose resources embedded in each slice.
The CI runs on every push to main and on pull requests (.github/workflows/ci.yml):
| Job | Runner | Steps |
|---|---|---|
validate_ios |
macOS | iOS shared tests, verify XCFramework resources |
build_kmp |
Linux | Version validation, build, shared tests, compose tests, ktlint, string validation, publish dry-run |
build_js |
Linux | JS build, web tests + coverage, lint, npm publish dry-run, Chromatic |
sonarcloud |
Linux | SonarQube analysis with merged coverage from KMP + JS |
Working example apps are available in the examples/ directory:
| Platform | Directory | Setup |
|---|---|---|
| Android | examples/android/ |
Set public key in MainActivity.kt
|
| iOS | examples/ios/ |
Set public key in Local.xcconfig
|
See examples/README.md for detailed setup instructions.