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โ
Gradle (Recommended)โ
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)
}
}
}
}
Deep Link Configurationโ
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:
- Minimum Android API level is now 21 (was 19)
- Migrated to AndroidX (removed support library)
- Request classes renamed for clarity
- Updated to Material Components
- 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:
- Use test keys during development
- Store production keys securely (e.g., in gradle.properties)
- Don't log sensitive data in production
- 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.
Related Resourcesโ
- Mobile SDKs Overview - Compare all mobile SDKs
- iOS SDK - iOS development
- Charges API - Create charges from tokens
- Customers API - Save cards for future use
- GitHub Repository - Source code and examples
- API Reference - Complete API documentation
Next Stepsโ
- Set up your account to get your API keys
- Integrate on your server to create charges from tokens
- Test your integration with test cards
- Go live with production keys
Supportโ
Need help with the Android SDK?
- GitHub Issues: github.com/omise/omise-android/issues
- Email: support@omise.co
- Documentation: docs.omise.co