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.
概要
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
- サポート 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
要件
- 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
インストール
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>
クイックスタート
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);
}
});
}
構成
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
)
トークンの作成
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)
支払いソースの作成
インターネットバンキング
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)
}
})
}
// 使用方法
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)
}
// 使用方法
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)
}
// 使用方法
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コンポーネント
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")
}
}
}
}
入力検証
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)
}
}
// 使用方法
cardNumberEditText.addTextChangedListener(
CardNumberTextWatcher { isValid ->
submitButton.isEnabled = isValid
}
)
3Dセキュア認証
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 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 エラーハンドリング
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()
}
ベストプラクティス
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()
}
テスト
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 テスト
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 テスト 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()))
}
}
トラブルシューティング
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)))
}
}
デバッグモード
// Enable debug logging
val config = Client.Config(
publicKey = "pkey_test_...",
enableDebug = true
)
val client = Client(config)
// This will log all network requests and responses
移行ガイド
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