首页
/ 突破平台壁垒:Compose-Multiplatform支付功能跨端集成指南

突破平台壁垒:Compose-Multiplatform支付功能跨端集成指南

2026-02-04 04:25:45作者:江焘钦

你是否还在为多平台应用的支付功能实现而烦恼?既要适配Android的支付宝、微信支付,又要对接iOS的内购系统,不同平台的开发语言和API差异让代码维护成本飙升。本文将带你一步掌握如何在Compose-Multiplatform项目中优雅实现统一的支付逻辑,让跨平台支付集成不再复杂。读完本文你将获得:多平台支付架构设计方案、平台特定代码隔离实践、支付状态管理最佳实践以及完整的集成步骤示例。

多平台支付架构设计

Compose-Multiplatform的核心优势在于使用Kotlin单一代码库开发跨平台应用,但支付功能涉及各平台原生能力,因此需要采用分层抽象+平台实现的架构模式。

架构设计图

graph TD
    A[共享UI层] -->|调用| B[支付抽象接口]
    B --> C[Android支付实现]
    B --> D[iOS支付实现]
    C --> E[支付宝SDK]
    C --> F[微信支付SDK]
    D --> G[Apple内购StoreKit]
    B --> H[支付状态管理]
    H --> I[共享数据模型]

这种架构通过定义统一接口隔离平台差异,共享UI层只需调用抽象接口,无需关心具体平台实现。支付状态通过ViewModel在各平台间保持同步,确保用户体验一致。

支付抽象接口定义

在共享模块中定义支付相关的抽象接口和数据模型,是实现跨平台支付的基础。以下是核心代码实现:

// commonMain/src/PaymentService.kt
interface PaymentService {
    suspend fun createOrder(orderInfo: OrderInfo): Result<OrderResult>
    suspend fun queryOrderStatus(orderId: String): Result<OrderStatus>
    fun handlePaymentResult(data: PlatformSpecificData): Boolean
}

// 订单数据模型
data class OrderInfo(
    val productId: String,
    val amount: Double,
    val currency: String = "CNY",
    val subject: String,
    val extra: Map<String, String> = emptyMap()
)

data class OrderResult(
    val orderId: String,
    val platformOrderId: String,
    val payUrl: String? = null,
    val rawData: String? = null
)

enum class OrderStatus {
    PENDING, PAID, FAILED, REFUNDED
}

// 平台特定数据封装
expect class PlatformSpecificData

// 支付异常定义
sealed class PaymentException(message: String) : Exception(message) {
    class NetworkError(message: String) : PaymentException(message)
    class InvalidParams(message: String) : PaymentException(message)
    class PlatformError(message: String, val code: Int) : PaymentException(message)
}

这段代码定义了支付服务的核心能力:创建订单、查询状态和处理支付结果。使用expect关键字声明平台特定数据类,在各平台模块中提供实际实现。

平台特定实现

Android平台实现

Android平台需要集成支付宝和微信支付SDK,在androidMain中实现支付接口:

// androidMain/src/AndroidPaymentService.kt
actual class PlatformSpecificData(
    val intent: Intent? = null,
    val uri: Uri? = null,
    val data: Bundle? = null
)

class AndroidPaymentService : PaymentService {
    private val alipaySDK = AlipaySDK()
    private val wechatSDK = WechatPaySDK()
    
    override suspend fun createOrder(orderInfo: OrderInfo): Result<OrderResult> {
        return try {
            when (orderInfo.paymentMethod) {
                PaymentMethod.ALIPAY -> createAlipayOrder(orderInfo)
                PaymentMethod.WECHAT -> createWechatOrder(orderInfo)
                else -> Result.failure(UnsupportedOperationException("支付方式不支持"))
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    private suspend fun createAlipayOrder(orderInfo: OrderInfo): Result<OrderResult> {
        // 调用支付宝SDK创建订单
        val orderString = alipaySDK.createOrder(orderInfo)
        return Result.success(OrderResult(
            orderId = orderInfo.productId,
            platformOrderId = UUID.randomUUID().toString(),
            rawData = orderString
        ))
    }
    
    // 微信支付实现...
    
    override fun handlePaymentResult(data: PlatformSpecificData): Boolean {
        // 处理支付宝/微信返回的支付结果
        data.intent?.let { intent ->
            return alipaySDK.handlePaymentResult(intent) || wechatSDK.handleIntent(intent)
        }
        return false
    }
    
    // 其他方法实现...
}

Android平台实现需要处理Activity跳转和结果回调,建议在MainActivity中接收支付结果并转发给支付服务:

// MainActivity.kt
class MainActivity : ComponentActivity() {
    private lateinit var paymentService: PaymentService
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        paymentService = AndroidPaymentService()
        setContent {
            AppTheme {
                // 提供支付服务给组合树
                CompositionLocalProvider(
                    LocalPaymentService provides paymentService
                ) {
                    MainScreen()
                }
            }
        }
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == PAYMENT_REQUEST_CODE) {
            paymentService.handlePaymentResult(PlatformSpecificData(data))
        }
    }
    
    companion object {
        const val PAYMENT_REQUEST_CODE = 1001
    }
}

// 提供CompositionLocal以便UI访问支付服务
val LocalPaymentService = compositionLocalOf<PaymentService> {
    error("PaymentService not provided")
}

iOS平台实现

iOS平台主要集成Apple内购(StoreKit),以下是核心实现代码:

// iosMain/src/PaymentService.kt
import StoreKit.SKPaymentQueue
import StoreKit.SKProductsRequest

actual class PlatformSpecificData(
    val transaction: SKPaymentTransaction? = null,
    val url: NSURL? = null
)

class IOSPaymentService : PaymentService {
    private val paymentQueue = SKPaymentQueue.defaultQueue()
    private var productsRequest: SKProductsRequest? = null
    private val orderCache = mutableMapOf<String, OrderInfo>()
    
    init {
        paymentQueue.addTransactionObserver(PaymentTransactionObserver { transaction ->
            handleTransaction(transaction)
        })
    }
    
    override suspend fun createOrder(orderInfo: OrderInfo): Result<OrderResult> {
        return suspendCancellableCoroutine { continuation ->
            // 查询产品信息
            val productIdentifiers = NSSet(arrayOf(orderInfo.productId))
            productsRequest = SKProductsRequest(productIdentifiers = productIdentifiers).apply {
                delegate = object : SKProductsRequestDelegateProtocol {
                    override fun productsRequest(request: SKProductsRequest, didReceive response: SKProductsResponse) {
                        val product = response.products.firstOrNull()
                        if (product == null) {
                            continuation.resume(Result.failure(PaymentException.InvalidParams("Product not found")))
                            return
                        }
                        
                        // 创建支付请求
                        val payment = SKPayment(product: product)
                        orderCache[orderInfo.productId] = orderInfo
                        paymentQueue.addPayment(payment)
                        continuation.resume(Result.success(OrderResult(
                            orderId = orderInfo.productId,
                            platformOrderId = product.productIdentifier
                        )))
                    }
                    
                    override fun request(request: SKRequest, didFailWithError error: NSError) {
                        continuation.resume(Result.failure(
                            PaymentException.PlatformError(error.localizedDescription, error.code)
                        ))
                    }
                }
                start()
            }
        }
    }
    
    private fun handleTransaction(transaction: SKPaymentTransaction) {
        when (transaction.transactionState) {
            SKPaymentTransactionState.Purchased -> {
                // 处理购买成功
                val orderInfo = orderCache.remove(transaction.payment.productIdentifier)
                // 验证购买凭证并通知服务器
                verifyReceipt(transaction)
                paymentQueue.finishTransaction(transaction)
            }
            SKPaymentTransactionState.Failed -> {
                // 处理购买失败
                paymentQueue.finishTransaction(transaction)
            }
            // 处理其他状态...
        }
    }
    
    private fun verifyReceipt(transaction: SKPaymentTransaction) {
        // 实现凭证验证逻辑
    }
    
    // 其他方法实现...
}

iOS支付实现利用StoreKit框架处理内购流程,需要注意:

  1. 产品ID必须在App Store Connect中预先配置
  2. 支付结果通过SKPaymentTransactionObserver接收
  3. 购买凭证需要在服务端验证,防止客户端篡改
  4. 测试需要使用Sandbox测试账号

共享支付UI组件

Compose-Multiplatform允许我们创建跨平台的支付UI组件,以下是一个支付按钮和订单列表的实现:

// commonMain/src/PaymentUI.kt
@Composable
fun PaymentButton(
    product: Product,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    isLoading: Boolean = false
) {
    Button(
        onClick = onClick,
        modifier = modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp),
        enabled = !isLoading
    ) {
        if (isLoading) {
            CircularProgressIndicator(
                modifier = Modifier.size(24.dp),
                color = MaterialTheme.colors.onPrimary,
                strokeWidth = 2.dp
            )
            Spacer(modifier = Modifier.size(8.dp))
        }
        Text(
            text = "支付 ¥${product.price}",
            style = MaterialTheme.typography.button
        )
    }
}

@Composable
fun OrderHistoryList(
    orders: List<Order>,
    onOrderClick: (Order) -> Unit,
    modifier: Modifier = Modifier
) {
    LazyColumn(modifier = modifier.fillMaxSize()) {
        items(orders) { order ->
            OrderItem(
                order = order,
                onClick = { onOrderClick(order) }
            )
            Divider()
        }
    }
}

@Composable
private fun OrderItem(order: Order, onClick: () -> Unit) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .clickable { onClick() }
            .padding(16.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Column {
            Text(
                text = order.subject,
                style = MaterialTheme.typography.subtitle1,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis
            )
            Spacer(modifier = Modifier.size(4.dp))
            Text(
                text = "订单号: ${order.orderId}",
                style = MaterialTheme.typography.caption,
                color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
            )
        }
        Column(horizontalAlignment = Alignment.End) {
            Text(
                text = ${order.amount}",
                style = MaterialTheme.typography.subtitle1,
                fontWeight = FontWeight.Bold
            )
            OrderStatusBadge(status = order.status)
        }
    }
}

@Composable
private fun OrderStatusBadge(status: OrderStatus) {
    val (color, text) = when (status) {
        OrderStatus.PENDING -> Pair(Color(0xFFFFC107), "待支付")
        OrderStatus.PAID -> Pair(Color(0xFF4CAF50), "已支付")
        OrderStatus.FAILED -> Pair(Color(0xFFF44336), "支付失败")
        OrderStatus.REFUNDED -> Pair(Color(0xFF9E9E9E), "已退款")
    }
    Box(
        modifier = Modifier
            .background(color = color, shape = RoundedCornerShape(12.dp))
            .padding(horizontal = 8.dp, vertical = 2.dp)
    ) {
        Text(
            text = text,
            style = MaterialTheme.typography.caption.copy(color = Color.White),
            fontSize = 12.sp
        )
    }
}

这些UI组件在各平台上保持一致的外观和交互,通过MaterialTheme确保符合平台设计规范。支付按钮包含加载状态,订单列表展示历史交易记录和状态。

支付状态管理

使用ViewModel管理支付相关状态,确保状态在配置变更和平台间保持一致:

// commonMain/src/PaymentViewModel.kt
class PaymentViewModel(
    private val paymentService: PaymentService,
    private val orderRepository: OrderRepository
) : ViewModel() {
    private val _uiState = MutableStateFlow<PaymentUiState>(PaymentUiState.Idle)
    val uiState: StateFlow<PaymentUiState> = _uiState.asStateFlow()
    
    private val _orders = MutableStateFlow<List<Order>>(emptyList())
    val orders: StateFlow<List<Order>> = _orders.asStateFlow()
    
    init {
        loadOrderHistory()
    }
    
    fun loadOrderHistory() {
        viewModelScope.launch {
            _uiState.value = PaymentUiState.Loading
            try {
                val orders = orderRepository.getOrderHistory()
                _orders.value = orders
                _uiState.value = PaymentUiState.Success
            } catch (e: Exception) {
                _uiState.value = PaymentUiState.Error(e.message ?: "加载订单失败")
            }
        }
    }
    
    fun createPayment(orderInfo: OrderInfo) {
        viewModelScope.launch {
            _uiState.value = PaymentUiState.Loading
            try {
                val result = paymentService.createOrder(orderInfo)
                result.onSuccess { orderResult ->
                    // 保存订单信息
                    orderRepository.saveOrder(
                        Order(
                            orderId = orderResult.orderId,
                            platformOrderId = orderResult.platformOrderId,
                            amount = orderInfo.amount,
                            subject = orderInfo.subject,
                            status = OrderStatus.PENDING,
                            createTime = System.currentTimeMillis()
                        )
                    )
                    _uiState.value = PaymentUiState.PaymentCreated(orderResult)
                    // 启动支付流程
                    launchPayment(orderResult)
                }.onFailure { e ->
                    _uiState.value = PaymentUiState.Error(e.message ?: "创建订单失败")
                }
            } catch (e: PaymentException) {
                _uiState.value = PaymentUiState.Error(e.message ?: "支付处理异常")
            }
        }
    }
    
    private suspend fun launchPayment(orderResult: OrderResult) {
        // 根据平台特性启动支付
    }
    
    fun handlePaymentResult(data: PlatformSpecificData) {
        viewModelScope.launch {
            val success = paymentService.handlePaymentResult(data)
            if (success) {
                _uiState.value = PaymentUiState.PaymentSuccess
                loadOrderHistory() // 刷新订单列表
            } else {
                _uiState.value = PaymentUiState.PaymentFailed("支付失败,请重试")
            }
        }
    }
}

// UI状态密封类
sealed class PaymentUiState {
    object Idle : PaymentUiState()
    object Loading : PaymentUiState()
    object Success : PaymentUiState()
    data class Error(val message: String) : PaymentUiState()
    data class PaymentCreated(val orderResult: OrderResult) : PaymentUiState()
    object PaymentSuccess : PaymentUiState()
    data class PaymentFailed(val message: String) : PaymentUiState()
}

平台特定配置与依赖

Android平台配置

在Android模块的build.gradle.kts中添加支付SDK依赖:

// androidApp/build.gradle.kts
dependencies {
    // 支付宝SDK
    implementation("com.alipay.sdk:alipaysdk-android:+")
    // 微信支付SDK
    implementation("com.tencent.mm.opensdk:wechat-sdk-android:+")
    // 其他依赖...
}

AndroidManifest.xml中添加必要权限和配置:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application>
    <!-- 支付宝Activity配置 -->
    <activity
        android:name="com.alipay.sdk.app.H5PayActivity"
        android:configChanges="orientation|keyboardHidden|navigation|screenSize"
        android:exported="false"
        android:screenOrientation="behind"
        android:windowSoftInputMode="adjustResize|stateHidden" />
        
    <!-- 微信支付回调Activity -->
    <activity
        android:name=".wxapi.WXPayEntryActivity"
        android:exported="true"
        android:launchMode="singleTop" />
</application>

iOS平台配置

在iOS模块的Podfile中添加StoreKit依赖:

# iosApp/Podfile
target 'iosApp' do
  use_frameworks!
  pod 'StoreKit'
end

Info.plist中添加内购相关配置:

<key>SKProductsUsageDescription</key>
<string>需要访问App Store以处理应用内购买</string>

支付流程测试与调试

支付功能的测试需要特别注意各平台的特殊性:

Android测试要点

  1. 使用支付宝和微信支付的沙箱环境
  2. 确保签名配置正确,否则无法调起支付
  3. 测试不同支付结果的回调处理
  4. 网络异常场景的错误处理

iOS测试要点

  1. 使用TestFlight或沙箱测试账号
  2. 在Xcode中配置正确的签名和App ID
  3. 产品ID必须与App Store Connect中配置一致
  4. 测试交易完成后的凭证验证流程

测试工具推荐

  • Android Studio的App Inspection工具监控网络请求
  • Xcode的Network和Debug导航器调试StoreKit交互
  • Charles或Fiddler抓包分析支付请求和响应

完整支付流程示例

以下是在共享UI中集成支付功能的完整示例:

@Composable
fun ProductDetailScreen(
    productId: String,
    viewModel: ProductViewModel = viewModel(),
    paymentViewModel: PaymentViewModel = viewModel(),
    navController: NavController
) {
    val productState by viewModel.productState.collectAsState()
    val paymentState by paymentViewModel.uiState.collectAsState()
    val context = LocalContext.current
    
    LaunchedEffect(productId) {
        viewModel.loadProduct(productId)
    }
    
    LaunchedEffect(paymentState) {
        when (paymentState) {
            is PaymentUiState.PaymentSuccess -> {
                showToast("支付成功!")
                navController.popBackStack()
            }
            is PaymentUiState.Error -> {
                showToast((paymentState as PaymentUiState.Error).message)
            }
            is PaymentUiState.PaymentFailed -> {
                showToast((paymentState as PaymentUiState.PaymentFailed).message)
            }
            else -> {}
        }
    }
    
    Scaffold(
        topBar = { TopAppBar(title = { Text("商品详情") }) }
    ) { padding ->
        when (productState) {
            is ProductUiState.Loading -> {
                Center(Modifier.fillMaxSize()) {
                    CircularProgressIndicator()
                }
            }
            is ProductUiState.Error -> {
                Center(Modifier.fillMaxSize()) {
                    Text("加载失败,请重试")
                }
            }
            is ProductUiState.Success -> {
                val product = (productState as ProductUiState.Success).product
                Column(
                    modifier = Modifier
                        .fillMaxSize()
                        .padding(padding)
                ) {
                    // 商品图片
                    ProductImage(imageUrl = product.imageUrl)
                    
                    // 商品信息
                    ProductInfoSection(product = product)
                    
                    Spacer(modifier = Modifier.weight(1f))
                    
                    // 支付按钮
                    PaymentButton(
                        product = product,
                        isLoading = paymentState is PaymentUiState.Loading,
                        onClick = {
                            paymentViewModel.createPayment(
                                OrderInfo(
                                    productId = product.id,
                                    amount = product.price,
                                    subject = product.name,
                                    extra = mapOf("productType" to product.type)
                                )
                            )
                        }
                    )
                    
                    Spacer(modifier = Modifier.size(16.dp))
                }
            }
        }
    }
}

总结与最佳实践

Compose-Multiplatform实现跨平台支付功能的核心在于抽象接口+平台实现的架构设计,关键最佳实践包括:

  1. 接口隔离平台差异:通过定义抽象接口,使共享UI层与平台实现解耦
  2. 状态集中管理:使用ViewModel统一管理支付状态,确保跨平台一致性
  3. 数据模型共享:核心数据模型在commonMain中定义,避免平台间数据转换
  4. 平台特性适配:充分利用expect/actual机制处理平台特定逻辑
  5. 错误统一处理:定义共享的异常体系,简化错误处理流程
  6. 安全最佳实践:敏感操作(如签名)放在平台模块,避免在共享代码中暴露密钥

通过本文介绍的方法,你可以在Compose-Multiplatform项目中高效实现跨平台支付功能,显著减少重复开发工作,同时保持各平台的原生用户体验。

相关资源

希望本文能帮助你顺利实现Compose-Multiplatform应用的支付功能。如有任何问题或建议,欢迎在项目的GitHub仓库提交issue交流讨论。

提示:支付功能涉及敏感的财务数据,务必在服务端进行订单验证和金额计算,客户端仅负责发起支付和展示结果,以确保交易安全。

登录后查看全文
热门项目推荐
相关项目推荐