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

Android SDK - ยอมรับการชำระเงิน

เรียนรู้วิธีการยอมรับการชำระเงินในแอปพลิเคชัน Android ของคุณโดยใช้ Omise Android SDK คู่มือนี้ครอบคลุมการสร้างการเรียกเก็บเงิน การจัดการการตรวจสอบสิทธิ์ 3D Secure และการนำไปใช้ในการไหลการชำระเงินต่างๆ พร้อม Kotlin และ Java

ภาพรวม

Omise Android SDK มีอินเตอร์เฟซ Android แบบเนทีฟสำหรับยอมรับการชำระเงินในแอปพลิเคชันมือถือ SDK นี้จัดการ tokenization, 3D Secure flows และมีส่วนประกอบ UI การชำระเงินที่ปรับแต่งได้

ฟีเจอร์หลัก

  • การใช้งาน Android แบบเนทีฟ - สร้างด้วย Kotlin เพื่อการพัฒนา Android สมัยใหม่
  • Tokenization ที่ปลอดภัย - Tokenization บัตรฝั่งไคลเอนต์
  • การสนับสนุน 3D Secure 2.0 - การตรวจสอบสิทธิ์ที่ราบรื่นกับ Chrome Custom Tabs
  • Google Pay Integration - การสนับสนุน Google Pay แบบในตัว
  • Material Design UI - ส่วนประกอบการชำระเงินที่ทันสมัยและปรับแต่งได้
  • Jetpack Compose Support - ส่วนประกอบที่พร้อม Compose

ข้อกำหนดเบื้องต้น

ก่อนที่จะใช้งานการยอมรับการชำระเงิน:

  1. ทำการติดตั้ง SDK เสร็จสิ้น (ดู Developer Tools > Android SDK)
  2. กำหนดค่าคีย์สาธารณะของคุณ
  3. ตั้งค่าแบ็กเอนด์สำหรับการสร้างการเรียกเก็บเงิน
  4. ทดสอบด้วยข้อมูลประจำตัวพัฒนา

ภาพรวมการไหล Android

ขั้นตอนการชำระเงิน Android ทั่วไป:

User enters card details

SDK tokenizes card (client-side)

Token sent to your backend

Backend creates charge with token

3D Secure authentication (if required)

Payment completed

การสร้างการเรียกเก็บเงินพื้นฐาน

ขั้นตอนที่ 1: เก็บและ Tokenize บัตร

import co.omise.android.OmiseClient
import co.omise.android.models.Token
import co.omise.android.models.CardParam

class PaymentActivity : AppCompatActivity() {

private lateinit var omiseClient: OmiseClient

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_payment)

omiseClient = OmiseClient("pkey_test_123")
}

fun processPayment(amount: Long, currency: String) {
val cardParam = CardParam(
name = "John Appleseed",
number = "4242424242424242",
expirationMonth = 12,
expirationYear = 2025,
securityCode = "123"
)

showLoading()

omiseClient.send(cardParam, object : RequestListener<Token> {
override fun onRequestSucceed(model: Token) {
hideLoading()
createCharge(model.id, amount, currency)
}

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

fun createCharge(tokenId: String, amount: Long, currency: String) {
val request = ChargeRequest(
token = tokenId,
amount = amount,
currency = currency,
returnUri = "myapp://payment/complete"
)

ApiClient.createCharge(request) { result ->
when (result) {
is Result.Success -> handleChargeResponse(result.data)
is Result.Error -> handleError(result.exception)
}
}
}
}

ขั้นตอนที่ 2: ใช้แบบฟอร์มการชำระเงินที่สร้างไว้

import co.omise.android.ui.CreditCardActivity
import co.omise.android.models.Token

class CheckoutActivity : AppCompatActivity() {

companion object {
private const val REQUEST_CC = 100
}

fun showPaymentForm() {
val intent = Intent(this, CreditCardActivity::class.java).apply {
putExtra(CreditCardActivity.EXTRA_PKEY, "pkey_test_123")
putExtra(CreditCardActivity.EXTRA_AMOUNT, 100000L)
putExtra(CreditCardActivity.EXTRA_CURRENCY, "THB")
}

startActivityForResult(intent, REQUEST_CC)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)

if (requestCode == REQUEST_CC && resultCode == RESULT_OK) {
val token = data?.getParcelableExtra<Token>(CreditCardActivity.EXTRA_TOKEN)
token?.let { processToken(it) }
}
}

fun processToken(token: Token) {
// Send to backend to create charge
val chargeData = mapOf(
"source" to token.id,
"amount" to 100000,
"currency" to "THB",
"return_uri" to "myapp://payment/complete"
)

ApiClient.post("/charges", chargeData) { result ->
handleChargeResult(result)
}
}
}

การใช้งาน Java

import co.omise.android.OmiseClient;
import co.omise.android.models.Token;
import co.omise.android.models.CardParam;

public class PaymentActivity extends AppCompatActivity {

private OmiseClient omiseClient;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_payment);

omiseClient = new OmiseClient("pkey_test_123");
}

public void processPayment(long amount, String currency) {
CardParam cardParam = new CardParam(
"John Appleseed",
"4242424242424242",
12,
2025,
"123"
);

showLoading();

omiseClient.send(cardParam, new RequestListener<Token>() {
@Override
public void onRequestSucceed(Token token) {
hideLoading();
createCharge(token.getId(), amount, currency);
}

@Override
public void onRequestFailed(Throwable throwable) {
hideLoading();
handleError(throwable);
}
});
}
}

การจัดการการตรวจสอบสิทธิ์ 3D Secure

การใช้งาน 3D Secure Flow

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

class PaymentProcessor : AppCompatActivity() {

companion object {
private const val REQUEST_3DS = 200
}

fun handleChargeResponse(charge: Charge) {
when (charge.status) {
"successful" -> {
showSuccess()
}
"pending" -> {
charge.authorizeUri?.let { uri ->
launch3DSecure(uri)
}
}
"failed" -> {
showError("Payment failed")
}
}
}

fun launch3DSecure(authorizeUri: String) {
val uri = Uri.parse(authorizeUri)

val customTabsIntent = CustomTabsIntent.Builder()
.setShowTitle(true)
.setToolbarColor(ContextCompat.getColor(this, R.color.colorPrimary))
.build()

customTabsIntent.launchUrl(this, uri)
}

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

intent?.data?.let { uri ->
if (uri.scheme == "myapp" && uri.host == "payment") {
// User returned from 3D Secure
verifyPaymentStatus()
}
}
}

fun verifyPaymentStatus() {
showLoading("Verifying payment...")

ApiClient.get("/charges/verify") { result ->
hideLoading()

when (result) {
is Result.Success -> {
if (result.data.status == "successful") {
showSuccess()
} else {
showError("Payment verification failed")
}
}
is Result.Error -> handleError(result.exception)
}
}
}
}
<activity
android:name=".PaymentProcessor"
android:launchMode="singleTask">
<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" />
</intent-filter>
</activity>

3D Secure ด้วย WebView (ทางเลือก)

import android.webkit.WebView
import android.webkit.WebViewClient

class Secure3DActivity : AppCompatActivity() {

private lateinit var webView: WebView

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_3ds)

val authorizeUri = intent.getStringExtra(EXTRA_AUTHORIZE_URI) ?: return

webView = findViewById(R.id.webview)
setup3DSecureWebView(authorizeUri)
}

fun setup3DSecureWebView(authorizeUri: String) {
webView.apply {
settings.javaScriptEnabled = true
webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
url: String?
): Boolean {
if (url?.startsWith("myapp://") == true) {
handle3DSecureReturn(url)
return true
}
return false
}
}
loadUrl(authorizeUri)
}
}

fun handle3DSecureReturn(url: String) {
// Payment completed, verify status
val intent = Intent().apply {
putExtra(EXTRA_RESULT, "completed")
}
setResult(RESULT_OK, intent)
finish()
}
}

การชำระเงินด้วยบัตรที่บันทึกไว้

การแสดงและการเรียกเก็บเงินจากบัตรที่บันทึกไว้

class SavedCardPayment : AppCompatActivity() {

private lateinit var cardsAdapter: CardListAdapter

fun loadSavedCards(customerId: String) {
showLoading()

ApiClient.get("/customers/$customerId/cards") { result ->
hideLoading()

when (result) {
is Result.Success -> {
displayCards(result.data)
}
is Result.Error -> handleError(result.exception)
}
}
}

fun displayCards(cards: List<Card>) {
cardsAdapter = CardListAdapter(cards) { selectedCard ->
chargeSelectedCard(selectedCard)
}

recyclerView.adapter = cardsAdapter
}

fun chargeSelectedCard(card: Card) {
val chargeRequest = mapOf(
"customer" to card.customerId,
"card" to card.id,
"amount" to 100000L,
"currency" to "THB",
"return_uri" to "myapp://payment/complete"
)

showLoading("Processing payment...")

ApiClient.post("/charges", chargeRequest) { result ->
hideLoading()

when (result) {
is Result.Success -> handleChargeResponse(result.data)
is Result.Error -> handleError(result.exception)
}
}
}
}

Card List Adapter

class CardListAdapter(
private val cards: List<Card>,
private val onCardSelected: (Card) -> Unit
) : RecyclerView.Adapter<CardListAdapter.CardViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_card, parent, false)
return CardViewHolder(view)
}

override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
holder.bind(cards[position])
}

override fun getItemCount() = cards.size

inner class CardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val brandIcon: ImageView = itemView.findViewById(R.id.brandIcon)
private val cardNumber: TextView = itemView.findViewById(R.id.cardNumber)
private val expiryDate: TextView = itemView.findViewById(R.id.expiryDate)

fun bind(card: Card) {
brandIcon.setImageResource(getBrandIcon(card.brand))
cardNumber.text = "•••• ${card.lastDigits}"
expiryDate.text = "${card.expirationMonth}/${card.expirationYear}"

itemView.setOnClickListener {
onCardSelected(card)
}
}

private fun getBrandIcon(brand: String): Int {
return when (brand.lowercase()) {
"visa" -> R.drawable.ic_visa
"mastercard" -> R.drawable.ic_mastercard
"amex" -> R.drawable.ic_amex
else -> R.drawable.ic_card_default
}
}
}
}

การบันทึกบัตรใหม่

fun saveNewCard(customerId: String) {
val cardParam = CardParam(
name = binding.cardholderName.text.toString(),
number = binding.cardNumber.text.toString(),
expirationMonth = getExpiryMonth(),
expirationYear = getExpiryYear(),
securityCode = binding.cvv.text.toString()
)

showLoading("Saving card...")

omiseClient.send(cardParam, object : RequestListener<Token> {
override fun onRequestSucceed(token: Token) {
attachCardToCustomer(customerId, token.id)
}

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

fun attachCardToCustomer(customerId: String, tokenId: String) {
val params = mapOf("card" to tokenId)

ApiClient.post("/customers/$customerId/cards", params) { result ->
hideLoading()

when (result) {
is Result.Success -> {
showSuccess("Card saved successfully")
finish()
}
is Result.Error -> handleError(result.exception)
}
}
}

แบบฟอร์มการชำระเงินแบบกำหนดเอง

สร้าง UI แบบกำหนดเองด้วย ViewBinding

class CustomPaymentForm : AppCompatActivity() {

private lateinit var binding: ActivityCustomPaymentBinding
private lateinit var omiseClient: OmiseClient

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCustomPaymentBinding.inflate(layoutInflater)
setContentView(binding.root)

omiseClient = OmiseClient("pkey_test_123")
setupViews()
}

fun setupViews() {
// Card number formatting
binding.cardNumber.addTextChangedListener(CardNumberTextWatcher())

// Expiry formatting
binding.expiry.addTextChangedListener(ExpiryTextWatcher())

// CVV validation
binding.cvv.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
validateCvv(s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})

binding.payButton.setOnClickListener {
if (validateForm()) {
createToken()
}
}
}

fun validateForm(): Boolean {
val cardNumber = binding.cardNumber.text.toString().replace(" ", "")
val cvv = binding.cvv.text.toString()
val name = binding.cardholderName.text.toString()

if (!CardValidator.isValidCardNumber(cardNumber)) {
binding.cardNumber.error = "Invalid card number"
return false
}

if (!CardValidator.isValidCvv(cvv)) {
binding.cvv.error = "Invalid CVV"
return false
}

if (name.isBlank()) {
binding.cardholderName.error = "Cardholder name required"
return false
}

return true
}

fun createToken() {
val (month, year) = parseExpiry(binding.expiry.text.toString())

val cardParam = CardParam(
name = binding.cardholderName.text.toString(),
number = binding.cardNumber.text.toString().replace(" ", ""),
expirationMonth = month,
expirationYear = year,
securityCode = binding.cvv.text.toString()
)

binding.payButton.isEnabled = false
binding.progressBar.visibility = View.VISIBLE

omiseClient.send(cardParam, object : RequestListener<Token> {
override fun onRequestSucceed(token: Token) {
binding.progressBar.visibility = View.GONE
processPayment(token.id)
}

override fun onRequestFailed(throwable: Throwable) {
binding.progressBar.visibility = View.GONE
binding.payButton.isEnabled = true
handleError(throwable)
}
})
}

fun parseExpiry(text: String): Pair<Int, Int> {
val parts = text.split("/")
val month = parts[0].trim().toIntOrNull() ?: 1
val year = parts[1].trim().toIntOrNull() ?: 2025
return Pair(month, year)
}
}

Card Number TextWatcher

class CardNumberTextWatcher : TextWatcher {

private var isFormatting = false

override fun afterTextChanged(s: Editable?) {
if (isFormatting) return

isFormatting = true

val digitsOnly = s.toString().replace(" ", "")
val formatted = formatCardNumber(digitsOnly)

s?.replace(0, s.length, formatted)

isFormatting = false
}

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

private fun formatCardNumber(number: String): String {
val builder = StringBuilder()
for (i in number.indices) {
if (i > 0 && i % 4 == 0) {
builder.append(" ")
}
builder.append(number[i])
}
return builder.toString()
}
}

Jetpack Compose Implementation

@Composable
fun PaymentForm(
onPaymentComplete: (String) -> Unit,
modifier: Modifier = Modifier
) {
var cardNumber by remember { mutableStateOf("") }
var cardholderName by remember { mutableStateOf("") }
var expiry by remember { mutableStateOf("") }
var cvv by remember { mutableStateOf("") }
var isProcessing by remember { mutableStateOf(false) }

Column(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
) {
OutlinedTextField(
value = cardNumber,
onValueChange = { cardNumber = formatCardNumber(it) },
label = { Text("Card Number") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.fillMaxWidth()
)

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

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

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

Row(modifier = Modifier.fillMaxWidth()) {
OutlinedTextField(
value = expiry,
onValueChange = { expiry = formatExpiry(it) },
label = { Text("MM/YY") },
modifier = Modifier.weight(1f)
)

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

OutlinedTextField(
value = cvv,
onValueChange = { if (it.length <= 4) cvv = it },
label = { Text("CVV") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.weight(1f)
)
}

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

Button(
onClick = {
isProcessing = true
processPayment(
cardNumber, cardholderName, expiry, cvv
) { tokenId ->
isProcessing = false
onPaymentComplete(tokenId)
}
},
enabled = !isProcessing,
modifier = Modifier.fillMaxWidth()
) {
if (isProcessing) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
color = Color.White
)
} else {
Text("Pay Now")
}
}
}
}

Google Pay Integration

การใช้ Google Pay

import com.google.android.gms.wallet.*

class GooglePayProcessor : AppCompatActivity() {

private lateinit var paymentsClient: PaymentsClient

companion object {
private const val LOAD_PAYMENT_DATA_REQUEST_CODE = 300
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

paymentsClient = PaymentsUtil.createPaymentsClient(this)
checkGooglePayAvailability()
}

fun checkGooglePayAvailability() {
val request = IsReadyToPayRequest.fromJson(
PaymentsUtil.getIsReadyToPayRequest().toString()
)

paymentsClient.isReadyToPay(request).addOnCompleteListener { task ->
if (task.isSuccessful) {
showGooglePayButton()
}
}
}

fun requestPayment(amount: String, currency: String) {
val paymentDataRequest = PaymentDataRequest.fromJson(
PaymentsUtil.getPaymentDataRequest(amount, currency).toString()
)

AutoResolveHelper.resolveTask(
paymentsClient.loadPaymentData(paymentDataRequest),
this,
LOAD_PAYMENT_DATA_REQUEST_CODE
)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)

when (requestCode) {
LOAD_PAYMENT_DATA_REQUEST_CODE -> {
when (resultCode) {
RESULT_OK -> {
data?.let { intent ->
PaymentData.getFromIntent(intent)?.let { paymentData ->
handleGooglePaySuccess(paymentData)
}
}
}
RESULT_CANCELED -> {
showError("Payment cancelled")
}
AutoResolveHelper.RESULT_ERROR -> {
AutoResolveHelper.getStatusFromIntent(data)?.let {
handleError(it.statusMessage ?: "Unknown error")
}
}
}
}
}
}

fun handleGooglePaySuccess(paymentData: PaymentData) {
val paymentInfo = paymentData.toJson()

// Send to backend to create Omise source and charge
val params = mapOf(
"type" to "googlepay",
"payment_data" to paymentInfo,
"amount" to 100000L,
"currency" to "THB"
)

ApiClient.post("/charges/googlepay", params) { result ->
when (result) {
is Result.Success -> showSuccess()
is Result.Error -> handleError(result.exception)
}
}
}
}

Google Pay Utility Class

object PaymentsUtil {

private const val GATEWAY_TOKENIZATION_NAME = "omise"

fun getIsReadyToPayRequest(): JSONObject {
return JSONObject().apply {
put("apiVersion", 2)
put("apiVersionMinor", 0)
put("allowedPaymentMethods", JSONArray().put(getBaseCardPaymentMethod()))
}
}

fun getPaymentDataRequest(price: String, currency: String): JSONObject {
return JSONObject().apply {
put("apiVersion", 2)
put("apiVersionMinor", 0)
put("allowedPaymentMethods", JSONArray().put(getCardPaymentMethod()))
put("transactionInfo", getTransactionInfo(price, currency))
put("merchantInfo", getMerchantInfo())
}
}

private fun getBaseCardPaymentMethod(): JSONObject {
return JSONObject().apply {
put("type", "CARD")
put("parameters", JSONObject().apply {
put("allowedAuthMethods", JSONArray().put("PAN_ONLY").put("CRYPTOGRAM_3DS"))
put("allowedCardNetworks", JSONArray()
.put("AMEX")
.put("DISCOVER")
.put("MASTERCARD")
.put("VISA"))
})
}
}

private fun getCardPaymentMethod(): JSONObject {
return getBaseCardPaymentMethod().apply {
put("tokenizationSpecification", JSONObject().apply {
put("type", "PAYMENT_GATEWAY")
put("parameters", JSONObject().apply {
put("gateway", GATEWAY_TOKENIZATION_NAME)
put("gatewayMerchantId", "your_merchant_id")
})
})
}
}

private fun getTransactionInfo(price: String, currency: String): JSONObject {
return JSONObject().apply {
put("totalPrice", price)
put("totalPriceStatus", "FINAL")
put("currencyCode", currency)
}
}

private fun getMerchantInfo(): JSONObject {
return JSONObject().apply {
put("merchantName", "Your App Name")
}
}

fun createPaymentsClient(activity: Activity): PaymentsClient {
val walletOptions = Wallet.WalletOptions.Builder()
.setEnvironment(WalletConstants.ENVIRONMENT_TEST) // Use PRODUCTION for live
.build()

return Wallet.getPaymentsClient(activity, walletOptions)
}
}

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

การจัดการข้อผิดพลาดที่ครอบคลุม

sealed class PaymentError : Exception() {
data class TokenizationError(override val message: String) : PaymentError()
data class ChargeError(override val message: String) : PaymentError()
object AuthenticationFailed : PaymentError()
data class NetworkError(override val cause: Throwable?) : PaymentError()
object InvalidCard : PaymentError()
object InsufficientFunds : PaymentError()
object CardDeclined : PaymentError()
}

class PaymentErrorHandler(private val context: Context) {

fun handle(error: Throwable) {
val message = when (error) {
is PaymentError.TokenizationError ->
"Failed to process card: ${error.message}"
is PaymentError.ChargeError ->
error.message
is PaymentError.AuthenticationFailed ->
"Payment authentication failed"
is PaymentError.NetworkError ->
"Network error. Please check your connection."
is PaymentError.InvalidCard ->
"Invalid card information"
is PaymentError.InsufficientFunds ->
"Insufficient funds on card"
is PaymentError.CardDeclined ->
"Card declined by bank"
else ->
"An error occurred: ${error.message}"
}

showError(message)
logError(error)
}

fun showError(message: String) {
MaterialAlertDialogBuilder(context)
.setTitle("Payment Error")
.setMessage(message)
.setPositiveButton("OK", null)
.show()
}

fun logError(error: Throwable) {
// Log to your analytics service
FirebaseCrashlytics.getInstance().recordException(error)
}
}

กรณีการใช้งานทั่วไป

การชำระเงินสมาชิกซื้อ

class SubscriptionPayment(private val context: Context) {

fun processSubscription(
plan: String,
customerId: String,
amount: Long
) {
val params = mapOf(
"customer" to customerId,
"amount" to amount,
"currency" to "THB",
"description" to "Monthly $plan subscription",
"metadata" to mapOf(
"plan" to plan,
"type" to "subscription"
)
)

ApiClient.post("/charges", params) { result ->
when (result) {
is Result.Success -> {
if (result.data.status == "successful") {
createRecurringSchedule(customerId, plan, amount)
}
}
is Result.Error -> handleError(result.exception)
}
}
}

fun createRecurringSchedule(
customerId: String,
plan: String,
amount: Long
) {
val params = mapOf(
"customer" to customerId,
"amount" to amount,
"period" to "month",
"description" to "$plan subscription"
)

ApiClient.post("/schedules", params) { result ->
handleScheduleCreation(result)
}
}
}

Split Payment

fun processSplitPayment(
totalAmount: Long,
splits: List<PaymentSplit>
) {
val charges = splits.map { split ->
createCharge(
amount = split.amount,
recipient = split.recipientId,
description = split.description
)
}

// Process all charges
Observable.fromIterable(charges)
.flatMap { charge -> processChargeAsync(charge) }
.toList()
.subscribe(
{ results -> handleSplitPaymentComplete(results) },
{ error -> handleError(error) }
)
}

แนวทางที่ดีที่สุด

ความปลอดภัย

  1. ไม่เคยเก็บข้อมูลที่ละเอียดอ่อน

    // ✅ Good - Use tokens
    val token = createToken(cardData)
    sendToBackend(token.id)

    // ❌ Bad - Don't store card data
    sharedPrefs.edit().putString("card_number", cardNumber).apply()
  2. ตรวจสอบความถูกต้องของอินพุต

    fun validateCard(number: String, cvv: String): Boolean {
    if (!CardValidator.isValidCardNumber(number)) {
    return false
    }

    if (!CardValidator.isValidCvv(cvv)) {
    return false
    }

    return true
    }
  3. ใช้ ProGuard/R8

    # In proguard-rules.pro
    -keep class co.omise.android.models.** { *; }
    -keep class co.omise.android.** { *; }

ประสิทธิภาพ

  1. ใช้ Coroutines

    suspend fun createPayment(amount: Long) = withContext(Dispatchers.IO) {
    val token = omiseClient.sendSuspend(cardParam)
    val charge = apiClient.createCharge(token.id, amount)
    charge
    }
  2. วิธีการชำระเงินแคช

    private val cardCache = mutableMapOf<String, List<Card>>()

    fun loadCards(customerId: String, forceRefresh: Boolean = false) {
    if (!forceRefresh && cardCache.containsKey(customerId)) {
    displayCards(cardCache[customerId]!!)
    return
    }

    fetchCardsFromServer(customerId)
    }

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

ปัญหาทั่วไป

Token Creation Fails

// Check public key
val client = OmiseClient("pkey_test_123")

// Validate card details
Log.d("Payment", "Card valid: ${CardValidator.isValidCardNumber(number)}")
Log.d("Payment", "CVV valid: ${CardValidator.isValidCvv(cvv)}")

3D Secure Not Working

// Verify deep link in manifest
// Check return URI matches
// Test with 3DS test card: 4000000000003063

if (charge.authorizeUri != null) {
Log.d("Payment", "Auth URI: ${charge.authorizeUri}")
launch3DSecure(charge.authorizeUri!!)
}

Google Pay Not Available

paymentsClient.isReadyToPay(request).addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d("GooglePay", "Available")
} else {
Log.e("GooglePay", "Not available: ${task.exception}")
}
}

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

คำถามทั่วไป

Q: ฉันต้องการการปฏิบัติตามข้อกำหนด PCI สำหรับแอป Android หรือไม่?

A: ไม่ Omise Android SDK tokenizes ข้อมูลบัตรฝั่งไคลเอนต์ ซึ่งช่วยลดข้อกำหนด PCI อย่างมาก อย่างไรก็ตาม ปฏิบัติตามแนวปฏิบัติความปลอดภัยที่ดีที่สุด

Q: ฉันสามารถยอมรับการชำระเงินออฟไลน์ได้หรือไม่?

A: การสนับสนุนบางส่วน คุณสามารถเก็บข้อมูลบัตรออฟไลน์และ tokenize ในภายหลัง แต่การสร้างการเรียกเก็บเงินต้องใช้การเชื่อมต่ออินเทอร์เน็ต

Q: เวอร์ชัน Android ที่รองรับคืออะไร?

A: Omise Android SDK รองรับ Android 5.0 (API level 21) ขึ้นไป สำหรับผลลัพธ์ที่ดีที่สุด ให้กำหนดเป้าหมาย Android 8.0 (API 26) ขึ้นไป

Q: ฉันจะทดสอบการชำระเงินได้อย่างไร?

A: ใช้หมายเลขบัตรทดสอบ (4242 4242 4242 4242) พร้อมวันหมดอายุในอนาคตใดก็ได้ และ CVV 3 หลักใดก็ได้ ใช้คีย์สาธารณะทดสอบของคุณ (pkey_test_xxx)

Q: ฉันสามารถปรับแต่ง UI การชำระเงินได้หรือไม่?

A: ใช่ ใช้ส่วนประกอบที่สร้างไว้ร่วมกับการปรับแต่ง หรือสร้าง UI ของคุณเองโดยใช้ SDK สำหรับ tokenization เท่านั้น

Q: โทเค็นสามารถนำกลับมาใช้ได้หรือไม่?

A: ไม่ โทเค็นหมดอายุหลังจากใช้งานเพื่อสร้างการเรียกเก็บเงินหรือแนบกับลูกค้า

คำถามการใช้งาน

Q: ฉันควรใช้ Kotlin หรือ Java?

A: เราแนะนำ Kotlin สำหรับความปลอดภัยค่า null ที่ดีขึ้นและการพัฒนา Android สมัยใหม่ แต่ SDK รองรับ Java เต็มรูปแบบ

Q: ฉันสามารถใช้ Jetpack Compose ได้หรือไม่?

A: ใช่ SDK เข้ากันได้กับ Compose ใช้ interop หรือสร้างส่วนประกอบ Compose ที่กำหนดเองรอบ SDK

Q: ฉันจะจัดการหมดเวลา 3D Secure ได้อย่างไร?

A: ใช้การตรวจจับหมดเวลาในตัวจำหน่ายการเชื่อมโยง Deep Link ของคุณและให้ตัวเลือกการลองใหม่แก่ผู้ใช้

Q: ฉันสามารถทดสอบ 3D Secure ในแซนด์บ็อกซ์ได้หรือไม่?

A: ใช่ ใช้บัตรทดสอบ 4000000000003063 เพื่อทริก 3D Secure ในโหมดทดสอบ

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

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