3. 支付渠道抽象层设计
为什么需要支付渠道抽象层
前两例分别把支付宝和微信支付接入了项目,但业务代码里如果直接出现 PayTask.payV2()、IWXAPI.sendReq() 这类调用,每加一个渠道就要改一遍下单页、订单页和回调处理。抽象层的目标是把"发起支付"和"处理结果"收敛成两个稳定接口,让业务层只依赖接口,不依赖任何渠道 SDK。
接口与工厂的职责划分
抽象层由三部分组成:统一支付接口(业务层唯一可见的契约)、渠道实现(每个渠道一个类,内部封装各自 SDK)、渠道工厂(根据渠道标识返回对应实现)。业务层拿到的是 IPayChannel,而不是 AlipayChannel 或 WxPayChannel。
接口定义
接口只暴露两个方法:发起支付和处理结果。请求和结果都用统一的数据对象承载,避免业务层接触渠道特有的字段名。
// PayRequest.kt —— 统一支付请求
data class PayRequest(
val orderId: String, // 商户订单号
val amountFen: Long, // 金额,单位:分
val subject: String, // 商品标题
val channelParams: Map<String, String> // 渠道特有参数(如微信 prepay_id、支付宝 orderInfo)
)
// PayResult.kt —— 统一支付结果
data class PayResult(
val orderId: String,
val status: PayStatus, // SUCCESS / FAIL / CANCEL / PENDING
val rawCode: String?, // 渠道原始返回码,便于排查
val message: String?
)
enum class PayStatus { SUCCESS, FAIL, CANCEL, PENDING }
// IPayChannel.kt —— 统一支付接口
interface IPayChannel {
/** 渠道标识,如 "alipay"、"wxpay" */
val channelType: String
/** 发起支付,activity 用于调起渠道收银台 */
fun pay(activity: Activity, request: PayRequest)
/** 将渠道回调结果映射为统一 PayResult */
fun handleResult(raw: Any): PayResult
}
渠道实现
每个渠道实现类只做两件事:把 PayRequest 转成自己 SDK 需要的参数,把 SDK 回调转成 PayResult。以下以支付宝为例,微信实现结构完全对称。
// AlipayChannel.kt
class AlipayChannel : IPayChannel {
override val channelType = "alipay"
override fun pay(activity: Activity, request: PayRequest) {
// 支付宝 App 支付需要服务端签好名的 orderInfo 字符串
val orderInfo = request.channelParams["orderInfo"]
?: throw IllegalArgumentException("alipay 缺少 orderInfo")
PayTask(activity).payV2(orderInfo, true)
}
override fun handleResult(raw: Any): PayResult {
// raw 为支付宝同步返回的 Map<String, String>
@Suppress("UNCHECKED_CAST")
val map = raw as Map<String, String>
val resultStatus = map["resultStatus"]
val memo = map["memo"]
val status = when (resultStatus) {
"9000" -> PayStatus.SUCCESS
"6001" -> PayStatus.CANCEL
"8000", "6004" -> PayStatus.PENDING
else -> PayStatus.FAIL
}
return PayResult(
orderId = map["out_trade_no"] ?: "",
status = status,
rawCode = resultStatus,
message = memo
)
}
}
支付宝的
resultStatus=9000 只代表客户端支付动作成功,最终交易状态仍需服务端异步通知确认。抽象层把 PENDING 单独列出,就是为了让业务层知道"客户端结果不等于最终结果"。渠道工厂
工厂用 Map 注册渠道,新增渠道只需加一行注册代码,业务层完全无感。
// PayChannelFactory.kt
object PayChannelFactory {
private val registry = mutableMapOf<String, IPayChannel>()
init {
register(AlipayChannel())
register(WxPayChannel())
}
fun register(channel: IPayChannel) {
registry[channel.channelType] = channel
}
fun getChannel(type: String): IPayChannel =
registry[type] ?: throw IllegalArgumentException("未注册的支付渠道: $type")
}
业务层调用方式
业务层只依赖 IPayChannel 和 PayChannelFactory,切换渠道只是换一个字符串。
// 下单页发起支付
val channel = PayChannelFactory.getChannel("alipay")
val request = PayRequest(
orderId = "ORDER_20240101_001",
amountFen = 1990L,
subject = "会员月卡",
channelParams = mapOf("orderInfo" to orderInfoFromServer)
)
channel.pay(this, request)
// 支付回调页处理结果
val result = channel.handleResult(rawCallbackData)
when (result.status) {
PayStatus.SUCCESS -> showToast("支付成功,等待服务端确认")
PayStatus.CANCEL -> showToast("已取消支付")
PayStatus.PENDING -> showToast("支付处理中,请稍后查看订单")
PayStatus.FAIL -> showToast("支付失败:${result.message}")
}
验证抽象层是否生效
用以下三个检查点确认业务层没有泄漏渠道依赖:
- 在业务层文件中搜索
import com.alipay和import com.tencent.mm,结果应为零。 - 把
PayChannelFactory.getChannel("alipay")改成"wxpay",业务层其余代码不需要任何修改即可编译通过。 - 新增一个模拟渠道
MockChannel(channelType = "mock",pay()直接回调成功),注册后业务层用"mock"即可跑通全流程,无需真实 SDK。
工厂的
init 块在类加载时执行,如果渠道实现类的构造中做了耗时操作(如初始化 SDK),会拖慢首次调用。建议渠道实现保持无状态、轻量构造,SDK 初始化延迟到 pay() 内部按需执行。