Skip to main content

Android SDK

The Omise Android SDK provides a comprehensive solution for integrating payment processing into your Android applications. Built with Kotlin and fully compatible with Java, it offers secure tokenization, native Material Design components, and support for all major payment methods in Southeast Asia.

Overviewโ€‹

The Android SDK enables you to:

  • Tokenize credit cards securely without sensitive data touching your servers
  • Create payment sources for alternative payment methods
  • Implement 3D Secure authentication with Chrome Custom Tabs
  • Build custom payment forms with Material Design components
  • Support biometric authentication with fingerprint and face unlock
  • Handle errors gracefully with comprehensive exception handling

Key Featuresโ€‹

  • Native Kotlin API with coroutines support
  • Full Java compatibility for legacy projects
  • Pre-built Material Design UI components
  • Jetpack Compose support
  • Comprehensive input validation
  • Network resilience with automatic retries
  • Type-safe request builders
  • AndroidX and Material Components support

Requirementsโ€‹

  • Android API Level 21 (Android 5.0) or higher
  • Android Studio Arctic Fox or later
  • Kotlin 1.5+ or Java 8+
  • Gradle 7.0+
  • AndroidX libraries

Installationโ€‹

Add the SDK to your app's build.gradle:

dependencies {
implementation 'co.omise:omise-android:4.0.0'
}

For Kotlin DSL (build.gradle.kts):

dependencies {
implementation("co.omise:omise-android:4.0.0")
}

Mavenโ€‹

<dependency>
<groupId>co.omise</groupId>
<artifactId>omise-android</artifactId>
<version>4.0.0</version>
</dependency>

Permissionsโ€‹

Add to your AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

Quick Startโ€‹

1. Import the SDKโ€‹

// Kotlin
import co.omise.android.Client
import co.omise.android.models.Token
import co.omise.android.models.Source
// Java
import co.omise.android.Client;
import co.omise.android.models.Token;
import co.omise.android.models.Source;

2. Initialize the Clientโ€‹

// Kotlin
class PaymentManager(context: Context) {
private val client = Client("pkey_test_5xyzyx5xyzyx5xyzyx5")
}
// Java
public class PaymentManager {
private final Client client;

public PaymentManager(Context context) {
client = new Client("pkey_test_5xyzyx5xyzyx5xyzyx5");
}
}

3. Create a Tokenโ€‹

Using Kotlin Coroutinesโ€‹

import kotlinx.coroutines.*
import co.omise.android.api.RequestListener

class PaymentViewModel : ViewModel() {

fun createToken(
name: String,
number: String,
expirationMonth: Int,
expirationYear: Int,
securityCode: String
) {
viewModelScope.launch {
try {
val token = suspendCancellableCoroutine<Token> { continuation ->
val request = Token.CreateTokenRequest(
name = name,
number = number,
expirationMonth = expirationMonth,
expirationYear = expirationYear,
securityCode = securityCode
)

client.send(request, object : RequestListener<Token> {
override fun onRequestSucceed(model: Token) {
continuation.resume(model)
}

override fun onRequestFailed(throwable: Throwable) {
continuation.resumeWithException(throwable)
}
})
}

Log.d("Payment", "Token created: ${token.id}")
sendTokenToServer(token.id)

} catch (e: Exception) {
Log.e("Payment", "Error creating token", e)
handleError(e)
}
}
}
}

Using Callbacksโ€‹

// Kotlin
fun createToken(
name: String,
number: String,
expirationMonth: Int,
expirationYear: Int,
securityCode: String
) {
val request = Token.CreateTokenRequest(
name = name,
number = number,
expirationMonth = expirationMonth,
expirationYear = expirationYear,
securityCode = securityCode
)

client.send(request, object : RequestListener<Token> {
override fun onRequestSucceed(model: Token) {
Log.d("Payment", "Token created: ${model.id}")
sendTokenToServer(model.id)
}

override fun onRequestFailed(throwable: Throwable) {
Log.e("Payment", "Error creating token", throwable)
handleError(throwable)
}
})
}

Javaโ€‹

// Java
public void createToken(
String name,
String number,
int expirationMonth,
int expirationYear,
String securityCode
) {
Token.CreateTokenRequest request = new Token.CreateTokenRequest(
name,
number,
expirationMonth,
expirationYear,
securityCode
);

client.send(request, new RequestListener<Token>() {
@Override
public void onRequestSucceed(Token token) {
Log.d("Payment", "Token created: " + token.getId());
sendTokenToServer(token.getId());
}

@Override
public void onRequestFailed(Throwable throwable) {
Log.e("Payment", "Error creating token", throwable);
handleError(throwable);
}
});
}

Configurationโ€‹

Client Configurationโ€‹

// Basic configuration
val client = Client("pkey_test_5xyzyx5xyzyx5xyzyx5")

// Advanced configuration
val config = Client.Config(
publicKey = "pkey_test_5xyzyx5xyzyx5xyzyx5",
apiVersion = "2019-05-29",
timeout = 60000L, // milliseconds
enableDebug = BuildConfig.DEBUG
)

val client = Client(config)

Network Configurationโ€‹

import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit

val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()

val client = Client(
publicKey = "pkey_test_5xyzyx5xyzyx5xyzyx5",
httpClient = okHttpClient
)

Creating Tokensโ€‹

Basic Card Tokenizationโ€‹

fun tokenizeCard(
name: String,
number: String,
expirationMonth: Int,
expirationYear: Int,
securityCode: String,
callback: (Result<Token>) -> Unit
) {
val request = Token.CreateTokenRequest(
name = name,
number = number,
expirationMonth = expirationMonth,
expirationYear = expirationYear,
securityCode = securityCode
)

client.send(request, object : RequestListener<Token> {
override fun onRequestSucceed(model: Token) {
callback(Result.success(model))
}

override fun onRequestFailed(throwable: Throwable) {
callback(Result.failure(throwable))
}
})
}

With Billing Addressโ€‹

fun tokenizeCardWithAddress() {
val address = Token.Address(
street1 = "123 Wireless Road",
street2 = "Lumpini",
city = "Pathum Wan",
state = "Bangkok",
postalCode = "10330",
country = "TH"
)

val request = Token.CreateTokenRequest(
name = "John Doe",
number = "4242424242424242",
expirationMonth = 12,
expirationYear = 2025,
securityCode = "123",
billingAddress = address
)

client.send(request, tokenCallback)
}

Validation Before Tokenizationโ€‹

import co.omise.android.util.CardValidator

fun validateAndTokenize(
number: String,
expirationMonth: Int,
expirationYear: Int,
securityCode: String
): Result<Unit> {
// Validate card number
if (!CardValidator.isValidCardNumber(number)) {
return Result.failure(ValidationException("Invalid card number"))
}

// Validate expiry
if (!CardValidator.isValidExpiry(expirationMonth, expirationYear)) {
return Result.failure(ValidationException("Invalid expiration date"))
}

// Validate CVV
val brand = CardValidator.getCardBrand(number)
if (!CardValidator.isValidCVV(securityCode, brand)) {
return Result.failure(ValidationException("Invalid security code"))
}

// Proceed with tokenization
createToken(number, expirationMonth, expirationYear, securityCode)
return Result.success(Unit)
}

class ValidationException(message: String) : Exception(message)

Creating Payment Sourcesโ€‹

Internet Bankingโ€‹

fun createInternetBankingSource(
amount: Long,
bank: InternetBanking
) {
val request = Source.CreateSourceRequest(
amount = amount,
currency = "thb",
type = SourceType.InternetBanking(bank)
)

client.send(request, object : RequestListener<Source> {
override fun onRequestSucceed(model: Source) {
// Redirect user to authorize payment
model.authorizeUri?.let { uri ->
openAuthorizeUrl(uri)
}
}

override fun onRequestFailed(throwable: Throwable) {
handleError(throwable)
}
})
}

// Usage
createInternetBankingSource(
amount = 100000L, // 1,000.00 THB
bank = InternetBanking.BAY
)

Mobile Bankingโ€‹

fun createMobileBankingSource(
amount: Long,
bank: MobileBanking
) {
val request = Source.CreateSourceRequest(
amount = amount,
currency = "thb",
type = SourceType.MobileBanking(bank)
)

client.send(request, sourceCallback)
}

// Usage
createMobileBankingSource(
amount = 50000L, // 500.00 THB
bank = MobileBanking.SCB
)

PromptPayโ€‹

fun createPromptPaySource(amount: Long) {
val request = Source.CreateSourceRequest(
amount = amount,
currency = "thb",
type = SourceType.PromptPay
)

client.send(request, object : RequestListener<Source> {
override fun onRequestSucceed(model: Source) {
// Display QR code to user
model.scanQRCodeUrl?.let { url ->
displayQRCode(url)
}
}

override fun onRequestFailed(throwable: Throwable) {
handleError(throwable)
}
})
}

TrueMoney Walletโ€‹

fun createTrueMoneySource(
amount: Long,
phoneNumber: String
) {
val request = Source.CreateSourceRequest(
amount = amount,
currency = "thb",
type = SourceType.TrueMoney,
phoneNumber = phoneNumber
)

client.send(request, sourceCallback)
}

// Usage
createTrueMoneySource(
amount = 100000L,
phoneNumber = "0812345678"
)

Alipayโ€‹

fun createAlipaySource(amount: Long) {
val request = Source.CreateSourceRequest(
amount = amount,
currency = "thb",
type = SourceType.Alipay
)

client.send(request, sourceCallback)
}

UI Componentsโ€‹

Built-in Credit Card Formโ€‹

The SDK provides a pre-built credit card form Activity:

import co.omise.android.ui.CreditCardActivity

class PaymentActivity : AppCompatActivity() {

private val creditCardLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
val token = result.data?.getParcelableExtra<Token>("token")
token?.let { handleToken(it) }
}
}

fun openCreditCardForm() {
val intent = Intent(this, CreditCardActivity::class.java).apply {
putExtra(CreditCardActivity.EXTRA_PKEY, "pkey_test_5xyzyx5xyzyx5xyzyx5")
putExtra(CreditCardActivity.EXTRA_AMOUNT, 100000L)
putExtra(CreditCardActivity.EXTRA_CURRENCY, "thb")
}
creditCardLauncher.launch(intent)
}

private fun handleToken(token: Token) {
Log.d("Payment", "Token received: ${token.id}")
sendTokenToServer(token.id)
}
}

Payment Method Chooserโ€‹

import co.omise.android.ui.PaymentMethodChooserActivity

class PaymentActivity : AppCompatActivity() {

private val paymentMethodLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
when (result.resultCode) {
RESULT_OK -> {
val token = result.data?.getParcelableExtra<Token>("token")
val source = result.data?.getParcelableExtra<Source>("source")

token?.let { handleToken(it) }
source?.let { handleSource(it) }
}
}
}

fun openPaymentMethodChooser() {
val intent = Intent(this, PaymentMethodChooserActivity::class.java).apply {
putExtra(PaymentMethodChooserActivity.EXTRA_PKEY, "pkey_test_5xyzyx5xyzyx5xyzyx5")
putExtra(PaymentMethodChooserActivity.EXTRA_AMOUNT, 100000L)
putExtra(PaymentMethodChooserActivity.EXTRA_CURRENCY, "thb")
}
paymentMethodLauncher.launch(intent)
}
}

Custom Payment Form with Jetpack Composeโ€‹

import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun PaymentForm(
viewModel: PaymentViewModel = viewModel()
) {
var cardNumber by remember { mutableStateOf("") }
var expiryDate by remember { mutableStateOf("") }
var cvv by remember { mutableStateOf("") }
var cardholderName by remember { mutableStateOf("") }
val isLoading by viewModel.isLoading.collectAsState()

Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
OutlinedTextField(
value = cardNumber,
onValueChange = { cardNumber = it },
label = { Text("Card Number") },
placeholder = { Text("4242 4242 4242 4242") },
modifier = Modifier.fillMaxWidth()
)

Spacer(modifier = Modifier.height(8.dp))

Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = expiryDate,
onValueChange = { expiryDate = it },
label = { Text("Expiry") },
placeholder = { Text("MM/YY") },
modifier = Modifier.weight(1f)
)

OutlinedTextField(
value = cvv,
onValueChange = { cvv = it },
label = { Text("CVV") },
placeholder = { Text("123") },
modifier = Modifier.weight(1f)
)
}

Spacer(modifier = Modifier.height(8.dp))

OutlinedTextField(
value = cardholderName,
onValueChange = { cardholderName = it },
label = { Text("Cardholder Name") },
placeholder = { Text("John Doe") },
modifier = Modifier.fillMaxWidth()
)

Spacer(modifier = Modifier.height(16.dp))

Button(
onClick = {
viewModel.createToken(
name = cardholderName,
number = cardNumber.replace(" ", ""),
expirationMonth = expiryDate.take(2).toIntOrNull() ?: 0,
expirationYear = 2000 + (expiryDate.takeLast(2).toIntOrNull() ?: 0),
securityCode = cvv
)
},
enabled = !isLoading,
modifier = Modifier.fillMaxWidth()
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = MaterialTheme.colorScheme.onPrimary
)
} else {
Text("Pay Now")
}
}
}
}

Input Validationโ€‹

Card Number Validationโ€‹

import co.omise.android.util.CardValidator
import co.omise.android.models.CardBrand

// Validate card number format
val cardNumber = "4242424242424242"
val isValid = CardValidator.isValidCardNumber(cardNumber)

// Detect card brand
val brand = CardValidator.getCardBrand(cardNumber)
when (brand) {
CardBrand.VISA -> println("Visa card")
CardBrand.MASTERCARD -> println("Mastercard")
CardBrand.JCB -> println("JCB card")
else -> println("Unknown brand")
}

// Format card number with spaces
val formatted = CardValidator.formatCardNumber(cardNumber) // "4242 4242 4242 4242"

Expiry Date Validationโ€‹

// Validate expiry date
val month = 12
val year = 2025

val isValid = CardValidator.isValidExpiry(month, year)

// Check if card is expired
val isExpired = CardValidator.isExpired(month, year)

CVV Validationโ€‹

// Validate CVV for card brand
val cvv = "123"
val cardBrand = CardBrand.VISA

val isValid = CardValidator.isValidCVV(cvv, cardBrand)

Real-time Validation with TextWatcherโ€‹

import android.text.TextWatcher
import android.text.Editable

class CardNumberTextWatcher(
private val onValidationChange: (Boolean) -> Unit
) : TextWatcher {

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}

override fun afterTextChanged(editable: Editable?) {
val cardNumber = editable?.toString()?.replace(" ", "") ?: ""
val isValid = CardValidator.isValidCardNumber(cardNumber)
onValidationChange(isValid)
}
}

// Usage
cardNumberEditText.addTextChangedListener(
CardNumberTextWatcher { isValid ->
submitButton.isEnabled = isValid
}
)

3D Secure Authenticationโ€‹

Handling 3D Secure with Chrome Custom Tabsโ€‹

import androidx.browser.customtabs.CustomTabsIntent
import android.net.Uri

class PaymentActivity : AppCompatActivity() {

fun processPaymentWithToken(tokenId: String) {
// Create charge on your server
createChargeOnServer(tokenId) { charge ->
charge.authorizeUri?.let { uri ->
handle3DSecure(uri)
} ?: handleSuccessfulPayment(charge)
}
}

private fun handle3DSecure(authorizeUri: String) {
val customTabsIntent = CustomTabsIntent.Builder()
.setShowTitle(true)
.setToolbarColor(ContextCompat.getColor(this, R.color.colorPrimary))
.build()

customTabsIntent.launchUrl(this, Uri.parse(authorizeUri))
}

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)

// Handle return from 3D Secure
intent?.data?.let { uri ->
if (uri.toString().startsWith("myapp://payment/callback")) {
verifyChargeStatus()
}
}
}

private fun verifyChargeStatus() {
// Verify charge status on your server
lifecycleScope.launch {
val charge = fetchChargeFromServer()
if (charge.status == "successful") {
handleSuccessfulPayment(charge)
} else {
handleFailedPayment(charge)
}
}
}
}

Add to AndroidManifest.xml:

<activity android:name=".PaymentActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myapp"
android:host="payment"
android:pathPrefix="/callback" />
</intent-filter>
</activity>

Error Handlingโ€‹

Error Typesโ€‹

try {
createToken(...)
} catch (e: Exception) {
when (e) {
is OmiseException -> {
// API error from server
Log.e("Payment", "API Error: ${e.message}")
handleAPIError(e)
}
is IOException -> {
// Network error
Log.e("Payment", "Network Error", e)
showNetworkError()
}
is IllegalArgumentException -> {
// Invalid request parameters
Log.e("Payment", "Invalid parameters", e)
showValidationError()
}
else -> {
Log.e("Payment", "Unknown error", e)
showGenericError()
}
}
}

API Error Handlingโ€‹

fun handleAPIError(error: OmiseException) {
when (error.code) {
"invalid_card" -> {
showAlert("Invalid Card", "Please check your card details")
}
"insufficient_fund" -> {
showAlert("Insufficient Funds", "Your card has insufficient funds")
}
"failed_processing" -> {
showAlert("Payment Failed", "Unable to process your payment")
}
"invalid_security_code" -> {
showAlert("Invalid CVV", "Please check your security code")
}
else -> {
showAlert("Error", error.message ?: "An error occurred")
}
}
}

Retry Logic with Coroutinesโ€‹

suspend fun createTokenWithRetry(
maxAttempts: Int = 3,
initialDelay: Long = 1000L,
factor: Double = 2.0
): Token {
var currentDelay = initialDelay
repeat(maxAttempts - 1) { attempt ->
try {
return createToken()
} catch (e: Exception) {
// Don't retry on validation errors
if (e is IllegalArgumentException) {
throw e
}

Log.w("Payment", "Attempt ${attempt + 1} failed, retrying in ${currentDelay}ms")
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong()
}
}

// Last attempt
return createToken()
}

Best Practicesโ€‹

Securityโ€‹

// โœ… DO: Use test key for development
val testKey = if (BuildConfig.DEBUG) {
"pkey_test_5xyzyx5xyzyx5xyzyx5"
} else {
BuildConfig.OMISE_PUBLIC_KEY
}

// โŒ DON'T: Hardcode production keys
// val productionKey = "pkey_5xyzyx5xyzyx5xyzyx5"

// โœ… DO: Store keys in gradle.properties
// gradle.properties:
// OMISE_PUBLIC_KEY=pkey_5xyzyx5xyzyx5xyzyx5

// build.gradle:
// buildConfigField "String", "OMISE_PUBLIC_KEY", "\"${OMISE_PUBLIC_KEY}\""

// โŒ DON'T: Log sensitive data
// Log.d("Payment", "Card number: $cardNumber")

// โœ… DO: Use sanitized logging
Log.d("Payment", "Token created: ${token.id}")

Memory Managementโ€‹

class PaymentFragment : Fragment() {
private var _binding: FragmentPaymentBinding? = null
private val binding get() = _binding!!

private val client by lazy {
Client("pkey_test_5xyzyx5xyzyx5xyzyx5")
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

Background Processingโ€‹

class PaymentViewModel(
private val client: Client
) : ViewModel() {

private val _tokenState = MutableStateFlow<TokenState>(TokenState.Idle)
val tokenState: StateFlow<TokenState> = _tokenState.asStateFlow()

fun createToken(
name: String,
number: String,
expirationMonth: Int,
expirationYear: Int,
securityCode: String
) {
viewModelScope.launch {
_tokenState.value = TokenState.Loading

try {
val token = suspendCancellableCoroutine<Token> { continuation ->
val request = Token.CreateTokenRequest(
name, number, expirationMonth, expirationYear, securityCode
)

client.send(request, object : RequestListener<Token> {
override fun onRequestSucceed(model: Token) {
continuation.resume(model)
}

override fun onRequestFailed(throwable: Throwable) {
continuation.resumeWithException(throwable)
}
})
}

_tokenState.value = TokenState.Success(token)

} catch (e: Exception) {
_tokenState.value = TokenState.Error(e)
}
}
}
}

sealed class TokenState {
object Idle : TokenState()
object Loading : TokenState()
data class Success(val token: Token) : TokenState()
data class Error(val exception: Exception) : TokenState()
}

Testingโ€‹

Test Modeโ€‹

// Use test public key
val client = Client("pkey_test_5xyzyx5xyzyx5xyzyx5")

// Test card numbers
val testCards = mapOf(
"4242424242424242" to "Successful",
"4111111111111111" to "Failed",
"4000000000000002" to "3D Secure required"
)

Unit Testingโ€‹

import org.junit.Test
import org.junit.Assert.*
import org.mockito.Mockito.*

class PaymentViewModelTest {

@Test
fun `createToken should emit success state on successful tokenization`() = runTest {
// Arrange
val mockClient = mock(Client::class.java)
val viewModel = PaymentViewModel(mockClient)
val expectedToken = Token(id = "tokn_test_123")

// Act
viewModel.createToken("John Doe", "4242424242424242", 12, 2025, "123")

// Assert
assertTrue(viewModel.tokenState.value is TokenState.Success)
assertEquals(expectedToken.id, (viewModel.tokenState.value as TokenState.Success).token.id)
}

@Test
fun `createToken should emit error state on failure`() = runTest {
// Arrange
val mockClient = mock(Client::class.java)
val viewModel = PaymentViewModel(mockClient)

// Act
viewModel.createToken("John Doe", "1234567890123456", 12, 2025, "123")

// Assert
assertTrue(viewModel.tokenState.value is TokenState.Error)
}
}

UI Testing with Espressoโ€‹

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.rules.ActivityScenarioRule
import org.junit.Rule
import org.junit.Test

class PaymentActivityTest {

@get:Rule
val activityRule = ActivityScenarioRule(PaymentActivity::class.java)

@Test
fun testPaymentFlow() {
// Enter card details
onView(withId(R.id.cardNumberEditText))
.perform(typeText("4242424242424242"))

onView(withId(R.id.expiryEditText))
.perform(typeText("1225"))

onView(withId(R.id.cvvEditText))
.perform(typeText("123"), closeSoftKeyboard())

onView(withId(R.id.cardholderNameEditText))
.perform(typeText("John Doe"), closeSoftKeyboard())

// Submit payment
onView(withId(R.id.submitButton))
.perform(click())

// Verify success
onView(withText("Payment Successful"))
.check(matches(isDisplayed()))
}
}

Troubleshootingโ€‹

Common Issuesโ€‹

Issue: "Invalid public key" error

// Solution: Check your public key format
// Test keys: pkey_test_*
// Live keys: pkey_*

val client = Client("pkey_test_5xyzyx5xyzyx5xyzyx5")

Issue: Network timeout errors

// Solution: Increase timeout in OkHttpClient
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build()

val client = Client(
publicKey = "pkey_test_...",
httpClient = okHttpClient
)

Issue: ProGuard removes SDK classes

# Add to proguard-rules.pro
-keep class co.omise.android.** { *; }
-keepclassmembers class co.omise.android.** { *; }

Issue: SSL/TLS errors on older devices

// Enable TLS 1.2 on Android 4.4 and below
dependencies {
implementation 'org.conscrypt:conscrypt-android:2.5.2'
}
// Initialize in Application class
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Security.insertProviderAt(Conscrypt.newProvider(), 1)
}
}

Issue: Chrome Custom Tabs not available

// Fallback to regular browser
fun openUrl(url: String) {
val customTabsIntent = CustomTabsIntent.Builder().build()

try {
customTabsIntent.launchUrl(this, Uri.parse(url))
} catch (e: ActivityNotFoundException) {
// Fallback to regular browser
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
}

Debug Modeโ€‹

// Enable debug logging
val config = Client.Config(
publicKey = "pkey_test_...",
enableDebug = true
)

val client = Client(config)

// This will log all network requests and responses

Migration Guideโ€‹

Migrating from v3.x to v4.xโ€‹

The v4.x release includes significant API changes:

Before (v3.x):

val request = Token.CreateRequest(
"John Doe",
"4242424242424242",
12,
2025,
"123"
)

client.send(request, object : RequestListener<Token> {
override fun onRequestSucceed(model: Token) {
// Handle success
}

override fun onRequestFailed(error: Throwable) {
// Handle error
}
})

After (v4.x):

val request = Token.CreateTokenRequest(
name = "John Doe",
number = "4242424242424242",
expirationMonth = 12,
expirationYear = 2025,
securityCode = "123"
)

client.send(request, object : RequestListener<Token> {
override fun onRequestSucceed(model: Token) {
// Handle success
}

override fun onRequestFailed(throwable: Throwable) {
// Handle error
}
})

Breaking Changes:

  1. Minimum Android API level is now 21 (was 19)
  2. Migrated to AndroidX (removed support library)
  3. Request classes renamed for clarity
  4. Updated to Material Components
  5. Named parameters required for request builders

Frequently Asked Questionsโ€‹

Can I use this SDK with Jetpack Compose?

Yes, the SDK works perfectly with Jetpack Compose. You can use the API calls in your ViewModels and observe state in your composables.

Does the SDK support Kotlin Flow?

While the SDK doesn't provide native Flow support, you can easily wrap the callbacks in Flow:

fun createTokenFlow(request: Token.CreateTokenRequest): Flow<Token> = flow {
val token = suspendCancellableCoroutine<Token> { continuation ->
client.send(request, object : RequestListener<Token> {
override fun onRequestSucceed(model: Token) {
continuation.resume(model)
}

override fun onRequestFailed(throwable: Throwable) {
continuation.resumeWithException(throwable)
}
})
}
emit(token)
}

Can I customize the built-in UI components?

Yes, you can customize colors and themes by extending the SDK's theme or building your own UI using the core APIs.

How do I handle Play Store submission?

The SDK is Play Store compliant. Make sure to:

  1. Use test keys during development
  2. Store production keys securely (e.g., in gradle.properties)
  3. Don't log sensitive data in production
  4. Add ProGuard rules if using code obfuscation

Can I save card details for future use?

The SDK creates tokens that you send to your server. Use the Customers API on your backend to attach tokens to customers for future charges.

Does the SDK work with React Native?

While there's no official React Native package, you can create a native module that wraps the Android SDK.

What's the difference between a token and a source?

  • Tokens represent credit/debit cards for immediate charging
  • Sources represent alternative payment methods requiring additional steps

How long are tokens valid?

Tokens expire after 30 minutes if not used. Once used, they're stored permanently.

Can I test with real card numbers?

No, always use test card numbers in test mode. Real cards will be declined.

Does the SDK support multiple currencies?

Yes, the SDK supports all currencies available in your Omise account. Specify the currency when creating sources.

Next Stepsโ€‹

  1. Set up your account to get your API keys
  2. Integrate on your server to create charges from tokens
  3. Test your integration with test cards
  4. Go live with production keys

Supportโ€‹

Need help with the Android SDK?