ข้ามไปยังเนื้อหาหลัก

Android SDK

Omise Android SDK ให้บริการโซลูชันที่ครอบคลุมสำหรับการผสานรวมการประมวลผลการชำระเงินเข้ากับแอปพลิเคชัน Android ของคุณ สร้างขึ้นด้วย Kotlin และเข้ากันได้อย่างสมบูรณ์กับ Java โดยให้บริการการสร้าง token ที่ปลอดภัย คอมโพเนนต์ Material Design แบบ native และรองรับวิธีการชำระเงินหลักทั้งหมดในเอเชียตะวันออกเฉียงใต้

ภาพรวม

Android SDK ช่วยให้คุณสามารถ:

  • สร้าง token บัตรเครดิต อย่างปลอดภัยโดยไม่ให้ข้อมูลที่ละเอียดอ่อนเข้าถึงเซิร์ฟเวอร์ของคุณ
  • สร้าง payment source สำหรับวิธีการชำระเงินทางเลือก
  • ใช้งาน 3D Secure authentication ด้วย Chrome Custom Tabs
  • สร้างฟอร์มการชำระเงินที่กำหนดเอง ด้วยคอมโพเนนต์ Material Design
  • รองรับการยืนยันตัวตนแบบไบโอเมตริกซ์ ด้วยลายนิ้วมือและการปลดล็อกใบหน้า
  • จัดการข้อผิดพลาดอย่างเหมาะสม ด้วยการจัดการข้อยกเว้นที่ครอบคลุม

คุณสมบัติหลัก

  • Native Kotlin API พร้อมรองรับ coroutines
  • ความเข้ากันได้กับ Java อย่างสมบูรณ์สำหรับโปรเจกต์เก่า
  • คอมโพเนนต์ UI Material Design ที่สร้างไว้ล่วงหน้า
  • รองรับ Jetpack Compose
  • การตรวจสอบความถูกต้องของข้อมูลนำเข้าที่ครอบคลุม
  • ความยืดหยุ่นของเครือข่ายด้วยการลองใหม่อัตโนมัติ
  • ตัวสร้างคำขอที่ปลอดภัยต่อประเภท
  • รองรับ AndroidX และ Material Components

ข้อกำหนด

  • Android API Level 21 (Android 5.0) หรือสูงกว่า
  • Android Studio Arctic Fox หรือใหม่กว่า
  • Kotlin 1.5+ หรือ Java 8+
  • Gradle 7.0+
  • ไลบรารี AndroidX

การติดตั้ง

Gradle (แนะนำ)

เพิ่ม SDK เข้าไปใน build.gradle ของแอปคุณ:

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

สำหรับ 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>

สิทธิ์

เพิ่มเข้าไปใน AndroidManifest.xml ของคุณ:

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

เริ่มต้นอย่างรวดเร็ว

1. Import 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. เริ่มต้น 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. สร้าง Token

ใช้ 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)
}
}
}
}

ใช้ 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

// การกำหนดค่าพื้นฐาน
val client = Client("pkey_test_5xyzyx5xyzyx5xyzyx5")

// การกำหนดค่าขั้นสูง
val config = Client.Config(
publicKey = "pkey_test_5xyzyx5xyzyx5xyzyx5",
apiVersion = "2019-05-29",
timeout = 60000L, // milliseconds
enableDebug = BuildConfig.DEBUG
)

val client = Client(config)

การกำหนดค่าเครือข่าย

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
)

การสร้าง Token

การสร้าง Token บัตรพื้นฐาน

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))
}
})
}

พร้อมที่อยู่สำหรับการเรียกเก็บเงิน

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)
}

การตรวจสอบก่อนสร้าง Token

import co.omise.android.util.CardValidator

fun validateAndTokenize(
number: String,
expirationMonth: Int,
expirationYear: Int,
securityCode: String
): Result<Unit> {
// ตรวจสอบหมายเลขบัตร
if (!CardValidator.isValidCardNumber(number)) {
return Result.failure(ValidationException("หมายเลขบัตรไม่ถูกต้อง"))
}

// ตรวจสอบวันหมดอายุ
if (!CardValidator.isValidExpiry(expirationMonth, expirationYear)) {
return Result.failure(ValidationException("วันหมดอายุไม่ถูกต้อง"))
}

// ตรวจสอบ CVV
val brand = CardValidator.getCardBrand(number)
if (!CardValidator.isValidCVV(securityCode, brand)) {
return Result.failure(ValidationException("รหัสความปลอดภัยไม่ถูกต้อง"))
}

// ดำเนินการสร้าง token
createToken(number, expirationMonth, expirationYear, securityCode)
return Result.success(Unit)
}

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

การสร้าง Payment Source

ธนาคารอินเทอร์เน็ต

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) {
// นำผู้ใช้ไปยังหน้ายืนยันการชำระเงิน
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) {
// แสดง QR code ให้กับผู้ใช้
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

ฟอร์มบัตรเครดิตที่สร้างไว้ในตัว

SDK ให้บริการ 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)
}
}

ตัวเลือกวิธีการชำระเงิน

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)
}
}

ฟอร์มการชำระเงินที่กำหนดเองด้วย 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")
}
}
}
}

การตรวจสอบความถูกต้องของข้อมูลนำเข้า

การตรวจสอบหมายเลขบัตร

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

// ตรวจสอบรูปแบบหมายเลขบัตร
val cardNumber = "4242424242424242"
val isValid = CardValidator.isValidCardNumber(cardNumber)

// ตรวจหายี่ห้อบัตร
val brand = CardValidator.getCardBrand(cardNumber)
when (brand) {
CardBrand.VISA -> println("บัตร Visa")
CardBrand.MASTERCARD -> println("Mastercard")
CardBrand.JCB -> println("บัตร JCB")
else -> println("ยี่ห้อไม่รู้จัก")
}

// จัดรูปแบบหมายเลขบัตรด้วยช่องว่าง
val formatted = CardValidator.formatCardNumber(cardNumber) // "4242 4242 4242 4242"

การตรวจสอบวันหมดอายุ

// ตรวจสอบวันหมดอายุ
val month = 12
val year = 2025

val isValid = CardValidator.isValidExpiry(month, year)

// ตรวจสอบว่าบัตรหมดอายุหรือไม่
val isExpired = CardValidator.isExpired(month, year)

การตรวจสอบ CVV

// ตรวจสอบ CVV สำหรับยี่ห้อบัตร
val cvv = "123"
val cardBrand = CardBrand.VISA

val isValid = CardValidator.isValidCVV(cvv, cardBrand)

การตรวจสอบแบบเรียลไทม์ด้วย 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 Secure

การจัดการ 3D Secure ด้วย Chrome Custom Tabs

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

class PaymentActivity : AppCompatActivity() {

fun processPaymentWithToken(tokenId: String) {
// สร้างการเรียกเก็บเงินบนเซิร์ฟเวอร์ของคุณ
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)

// จัดการการกลับมาจาก 3D Secure
intent?.data?.let { uri ->
if (uri.toString().startsWith("myapp://payment/callback")) {
verifyChargeStatus()
}
}
}

private fun verifyChargeStatus() {
// ตรวจสอบสถานะการเรียกเก็บเงินบนเซิร์ฟเวอร์ของคุณ
lifecycleScope.launch {
val charge = fetchChargeFromServer()
if (charge.status == "successful") {
handleSuccessfulPayment(charge)
} else {
handleFailedPayment(charge)
}
}
}
}

เพิ่มเข้าไปใน 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>

การจัดการข้อผิดพลาด

ประเภทข้อผิดพลาด

try {
createToken(...)
} catch (e: Exception) {
when (e) {
is OmiseException -> {
// ข้อผิดพลาด API จากเซิร์ฟเวอร์
Log.e("Payment", "API Error: ${e.message}")
handleAPIError(e)
}
is IOException -> {
// ข้อผิดพลาดเครือข่าย
Log.e("Payment", "Network Error", e)
showNetworkError()
}
is IllegalArgumentException -> {
// พารามิเตอร์คำขอไม่ถูกต้อง
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("บัตรไม่ถูกต้อง", "กรุณาตรวจสอบรายละเอียดบัตรของคุณ")
}
"insufficient_fund" -> {
showAlert("ยอดเงินไม่เพียงพอ", "บัตรของคุณมียอดเงินไม่เพียงพอ")
}
"failed_processing" -> {
showAlert("การชำระเงินล้มเหลว", "ไม่สามารถประมวลผลการชำระเงินของคุณได้")
}
"invalid_security_code" -> {
showAlert("CVV ไม่ถูกต้อง", "กรุณาตรวจสอบรหัสความปลอดภัยของคุณ")
}
else -> {
showAlert("ข้อผิดพลาด", error.message ?: "เกิดข้อผิดพลาด")
}
}
}

ลอจิกการลองใหม่ด้วย 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) {
// อย่าลองใหม่สำหรับข้อผิดพลาดการตรวจสอบ
if (e is IllegalArgumentException) {
throw e
}

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

// ความพยายามครั้งสุดท้าย
return createToken()
}

แนวทางปฏิบัติที่ดีที่สุด

ความปลอดภัย

// ✅ ควรทำ: ใช้ test key สำหรับการพัฒนา
val testKey = if (BuildConfig.DEBUG) {
"pkey_test_5xyzyx5xyzyx5xyzyx5"
} else {
BuildConfig.OMISE_PUBLIC_KEY
}

// ❌ ไม่ควรทำ: ฮาร์ดโค้ด production key
// val productionKey = "pkey_5xyzyx5xyzyx5xyzyx5"

// ✅ ควรทำ: เก็บ key ใน gradle.properties
// gradle.properties:
// OMISE_PUBLIC_KEY=pkey_5xyzyx5xyzyx5xyzyx5

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

// ❌ ไม่ควรทำ: บันทึกข้อมูลที่ละเอียดอ่อน
// Log.d("Payment", "Card number: $cardNumber")

// ✅ ควรทำ: ใช้การบันทึกที่ทำความสะอาดแล้ว
Log.d("Payment", "Token created: ${token.id}")

การจัดการหน่วยความจำ

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
}
}

การประมวลผลเบื้องหลัง

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 public key
val client = Client("pkey_test_5xyzyx5xyzyx5xyzyx5")

// หมายเลขบัตรทดสอบ
val testCards = mapOf(
"4242424242424242" to "สำเร็จ",
"4111111111111111" to "ล้มเหลว",
"4000000000000002" to "ต้องการ 3D Secure"
)

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 ด้วย 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() {
// กรอกรายละเอียดบัตร
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())

// ส่งการชำระเงิน
onView(withId(R.id.submitButton))
.perform(click())

// ตรวจสอบความสำเร็จ
onView(withText("การชำระเงินสำเร็จ"))
.check(matches(isDisplayed()))
}
}

การแก้ไขปัญหา

ปัญหาทั่วไป

ปัญหา: ข้อผิดพลาด "Invalid public key"

// วิธีแก้: ตรวจสอบรูปแบบ public key ของคุณ
// Test keys: pkey_test_*
// Live keys: pkey_*

val client = Client("pkey_test_5xyzyx5xyzyx5xyzyx5")

ปัญหา: ข้อผิดพลาด network timeout

// วิธีแก้: เพิ่ม timeout ใน OkHttpClient
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build()

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

ปัญหา: ProGuard ลบคลาส SDK

# เพิ่มเข้าไปใน proguard-rules.pro
-keep class co.omise.android.** { *; }
-keepclassmembers class co.omise.android.** { *; }

ปัญหา: ข้อผิดพลาด SSL/TLS บนอุปกรณ์เก่า

// เปิดใช้งาน TLS 1.2 บน Android 4.4 และต่ำกว่า
dependencies {
implementation 'org.conscrypt:conscrypt-android:2.5.2'
}
// เริ่มต้นในคลาส Application
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Security.insertProviderAt(Conscrypt.newProvider(), 1)
}
}

ปัญหา: Chrome Custom Tabs ไม่พร้อมใช้งาน

// ใช้เบราว์เซอร์ปกติแทน
fun openUrl(url: String) {
val customTabsIntent = CustomTabsIntent.Builder().build()

try {
customTabsIntent.launchUrl(this, Uri.parse(url))
} catch (e: ActivityNotFoundException) {
// ใช้เบราว์เซอร์ปกติแทน
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
}

โหมดดีบัก

// เปิดใช้งานการบันทึกดีบัก
val config = Client.Config(
publicKey = "pkey_test_...",
enableDebug = true
)

val client = Client(config)

// จะบันทึกคำขอและการตอบกลับเครือข่ายทั้งหมด

คู่มือการย้ายข้อมูล

การย้ายจาก v3.x ไปยัง v4.x

รุ่น v4.x รวมถึงการเปลี่ยนแปลง API ที่สำคัญ:

ก่อน (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
}
})

หลัง (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
}
})

การเปลี่ยนแปลงที่ทำลายความเข้ากันได้:

  1. Android API level ขั้นต่ำตอนนี้เป็น 21 (เดิมเป็น 19)
  2. ย้ายไปยัง AndroidX (ลบ support library)
  3. เปลี่ยนชื่อคลาสคำขอเพื่อความชัดเจน
  4. อัปเดตเป็น Material Components
  5. ต้องการพารามิเตอร์ที่มีชื่อสำหรับตัวสร้างคำขอ

คำถามที่พบบ่อย

ฉันสามารถใช้ SDK นี้กับ Jetpack Compose ได้หรือไม่?

ได้ SDK ทำงานได้อย่างสมบูรณ์แบบกับ Jetpack Compose คุณสามารถใช้การเรียก API ใน ViewModel ของคุณและสังเกต state ใน composable ของคุณ

SDK รองรับ Kotlin Flow หรือไม่?

แม้ว่า SDK จะไม่ให้การรองรับ Flow แบบ native แต่คุณสามารถ wrap callback ใน 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)
}

ฉันสามารถปรับแต่งคอมโพเนนต์ UI ที่สร้างไว้ในตัวได้หรือไม่?

ได้ คุณสามารถปรับแต่งสีและธีมโดยการขยายธีมของ SDK หรือสร้าง UI ของคุณเองโดยใช้ API หลัก

ฉันจะจัดการการส่ง Play Store ได้อย่างไร?

SDK เป็นไปตามข้อกำหนดของ Play Store ตรวจสอบให้แน่ใจว่า:

  1. ใช้ test key ระหว่างการพัฒนา
  2. เก็บ production key อย่างปลอดภัย (เช่น ใน gradle.properties)
  3. อย่าบันทึกข้อมูลที่ละเอียดอ่อนใน production
  4. เพิ่มกฎ ProGuard หากใช้การทำให้โค้ดไม่ชัดเจน

ฉันสามารถบันทึกรายละเอียดบัตรเพื่อใช้ในอนาคตได้หรือไม่?

SDK สร้าง token ที่คุณส่งไปยังเซิร์ฟเวอร์ของคุณ ใช้ Customers API บน backend ของคุณเพื่อแนบ token เข้ากับลูกค้าสำหรับการเรียกเก็บเงินในอนาคต

SDK ทำงานกับ React Native ได้หรือไม่?

แม้ว่าจะไม่มีแพ็คเกจ React Native อย่างเป็นทางการ แต่คุณสามารถสร้าง native module ที่ wrap Android SDK ได้

ความแตกต่างระหว่าง token และ source คืออะไร?

  • Token แทนบัตรเครดิต/เดบิตสำหรับการเรียกเก็บเงินทันที
  • Source แทนวิธีการชำระเงินทางเลือกที่ต้องการขั้นตอนเพิ่มเติม

Token มีอายุการใช้งานนานแค่ไหน?

Token หมดอายุหลังจาก 30 นาทีหากไม่ได้ใช้ เมื่อใช้แล้วจะถูกเก็บอย่างถาวร

ฉันสามารถทดสอบด้วยหมายเลขบัตรจริงได้หรือไม่?

ไม่ได้ ใช้หมายเลขบัตรทดสอบเสมอในโหมดทดสอบ บัตรจริงจะถูกปฏิเสธ

SDK รองรับหลายสกุลเงินหรือไม่?

ได้ SDK รองรับสกุลเงินทั้งหมดที่มีในบัญชี Omise ของคุณ ระบุสกุลเงินเมื่อสร้าง source

ทรัพยากรที่เกี่ยวข้อง

  • ภาพรวม Mobile SDK - เปรียบเทียบ mobile SDK ทั้งหมด
  • iOS SDK - การพัฒนา iOS
  • Charges API - สร้างการเรียกเก็บเงินจาก token
  • Customers API - บันทึกบัตรเพื่อใช้ในอนาคต
  • GitHub Repository - โค้ดต้นฉบับและตัวอย่าง
  • API Reference - เอกสาร API ที่สมบูรณ์

ขั้นตอนถัดไป

  1. ตั้งค่าบัญชีของคุณ เพื่อรับ API key
  2. ผสานรวมบนเซิร์ฟเวอร์ของคุณ เพื่อสร้างการเรียกเก็บเงินจาก token
  3. ทดสอบการผสานรวมของคุณ ด้วยบัตรทดสอบ
  4. เปิดใช้งานจริง ด้วย production key

การสนับสนุน

ต้องการความช่วยเหลือเกี่ยวกับ Android SDK หรือไม่?