多支付方式安全选型的设计方案探讨
支付方式动态适配与网关匹配方案
背景需求
我们需要动态支持多种支付方式(信用卡、PayPal、Google Pay等),且可切换某一支付方式的实现网关。比如PayPal支付当前用Stripe SDK实现,未来可切换为PayPal SDK。
初始策略模式思路
原本打算用策略模式,通过不同网关SDK实现对应支付服务,示例代码如下:
class PayPalModel : IPaymentModel class PayPalGateway(val someSdk: SDK) : IPaymentService<PayPalModel> { override suspend fun authorizeFunds(model: PayPalModel) { // someSdk 可以是 Stripe SDK、PayPal SDK 等 } }
调用时注入服务列表,通过PaymentAPI统一处理:
val gateways = listOf( CreditGateway(), PayPalGateway() ) val api = PaymentAPI(gateways) api.authorizeFunds(PayPalModel(...))
PaymentAPI内部通过模型匹配对应的服务:
private suspend fun findService(model: IPaymentModel): IPaymentService<IPaymentModel>? { for (service in paymentServices) { if (service.appliesTo(model)) { return service as IPaymentService<IPaymentModel> } } return null }
实际场景的矛盾
但实际客户端下单时,仅向服务器传递两个字段:
- 选中的支付方式类型
- 从Stripe/Square等SDK获取的授权ID/令牌
此时如果用统一的PaymentRequest代替各支付模型,会丢失类型安全,导致无法通过模型匹配服务:
class PayPalGateway : IPaymentService { override suspend fun authorizeFunds(model: PaymentRequest) { // 无法通过模型类型区分服务 } } class CreditGateway : IPaymentService<PaymentRequest> { override suspend fun authorizeFunds(model: PaymentRequest) { // 同样无法区分 } }
原有的findService函数会失效,因为没有了模型类型的区分度。
解决方案
1. 给支付服务绑定支付方式类型标识
给每个支付服务添加支付方式类型属性,同时让客户端请求携带该类型,直接通过类型匹配服务:
首先定义支付方式枚举:
enum class PaymentMethodType { CREDIT_CARD, PAYPAL, GOOGLE_PAY }
修改支付服务接口,添加类型标识:
interface IPaymentService { val methodType: PaymentMethodType suspend fun authorizeFunds(token: String) }
实现具体网关:
class PayPalStripeGateway(val stripeSdk: StripeSDK) : IPaymentService { override val methodType = PaymentMethodType.PAYPAL override suspend fun authorizeFunds(token: String) { stripeSdk.chargePayPalToken(token) } } class PayPalNativeGateway(val paypalSdk: PayPalSDK) : IPaymentService { override val methodType = PaymentMethodType.PAYPAL override suspend fun authorizeFunds(token: String) { paypalSdk.authorizePayment(token) } } class CreditCardGateway(val stripeSdk: StripeSDK) : IPaymentService { override val methodType = PaymentMethodType.CREDIT_CARD override suspend fun authorizeFunds(token: String) { stripeSdk.chargeCreditCardToken(token) } }
修改PaymentAPI的匹配逻辑:
class PaymentAPI(private val paymentServices: List<IPaymentService>) { suspend fun authorizeFunds(methodType: PaymentMethodType, token: String) { val service = paymentServices.firstOrNull { it.methodType == methodType } ?: throw IllegalArgumentException("Unsupported payment method: $methodType") service.authorizeFunds(token) } }
2. 结合依赖注入实现网关切换
如果需要切换同一支付方式的网关(比如PayPal从Stripe切换到Native SDK),只需在注入服务列表时替换对应的实现即可,无需修改业务逻辑:
// 用Stripe实现PayPal val gateways = listOf( CreditCardGateway(stripeSdk), PayPalStripeGateway(stripeSdk) ) // 切换为Native PayPal SDK val gateways = listOf( CreditCardGateway(stripeSdk), PayPalNativeGateway(paypalSdk) )
3. 保留类型安全的进阶方案(可选)
如果仍想保留模型的类型区分,可以将PaymentRequest设计为带类型标记的密封类:
sealed class PaymentRequest(val methodType: PaymentMethodType) { data class CreditCardRequest(val token: String) : PaymentRequest(PaymentMethodType.CREDIT_CARD) data class PayPalRequest(val token: String) : PaymentRequest(PaymentMethodType.PAYPAL) data class GooglePayRequest(val token: String) : PaymentRequest(PaymentMethodType.GOOGLE_PAY) }
修改服务接口:
interface IPaymentService<T : PaymentRequest> { val methodType: PaymentMethodType suspend fun authorizeFunds(request: T) }
实现服务时绑定具体请求类型:
class PayPalGateway(val sdk: SDK) : IPaymentService<PaymentRequest.PayPalRequest> { override val methodType = PaymentMethodType.PAYPAL override suspend fun authorizeFunds(request: PaymentRequest.PayPalRequest) { sdk.processToken(request.token) } }
PaymentAPI的匹配逻辑可以同时用类型和methodType双重校验:
class PaymentAPI(private val paymentServices: List<IPaymentService<*>>) { @Suppress("UNCHECKED_CAST") suspend fun authorizeFunds(request: PaymentRequest) { val service = paymentServices.firstOrNull { it.methodType == request.methodType && it.javaClass.genericInterfaces[0].let { iface -> (iface as ParameterizedType).actualTypeArguments[0] == request.javaClass } } as? IPaymentService<PaymentRequest> ?: throw IllegalArgumentException("Unsupported payment request: ${request.methodType}") service.authorizeFunds(request) } }
这种方式既保留了类型安全,又能通过客户端传递的支付方式类型(映射到密封类)匹配服务。
内容的提问来源于stack exchange,提问作者BVtp
相关产品推荐
相关产品推荐

