【业务场景架构实战】2. 对聚合支付 SDK 的封装

天下事在局外呐喊议论,总是无益,必须躬身入局,挺膺负责,方有成事之可冀。

业务场景

已知有一个 聚合支付 SDK ------ 可以把它理解成,封装了支付宝、微信等支付渠道 SDK,提供统一的完成支付和结果通知接口。

当前业务上新增支付需求,需要把聚合支付 SDK 集成到当前的项目里,从而赋予项目内业务模块以支付的能力,例如集成到运动课程模块,就可以支持下单购买专业运动课程;集成到睡眠模块,就可以支持购买专业睡眠报告分析等等。

设计目标

这是在项目里首次引入支付能力,希望不仅可以实现当前的产品需求(即在运动课程里添加购买功能),还能够支撑未来更多模块使用支付功能。

设计目标梳理如下:

  • 安全性:由于涉及用户财产,设计支付相关模块时首当其冲的就是安全性。
  • 通用性:应用类的任意模块,都可以使用封装后的支付能力。
  • 可测试性:能够自由切换底层实现,例如,APP开发过程中提供 mock 的下单、支付接口,以便在启动前后端联调前,就可以验证前端 UI 逻辑。
  • 伸缩性:未来聚合支付 SDK 进行升级时,对已接入的业务不产生影响。
  • 隐藏实现细节:上层业务无需关心底层支付流程细节。
  • 与 Jetpack 无缝集成 :采用 StateFlow 模式,利用 Hilt 管理对象生命周期。

设计思路

将上述设计目标分门别类后,思考对应的技术选型。

目标 技术选型 说明
安全性 独立 module + 网络传输加密 将支付模块作为独立模块,未来可以抽出 aar 集成,也便于混淆控制。下单、查询订单状态等接口,进行二次加密从而提升安全等级
通用性,伸缩性,隐藏实现细节 Activity-ViewModel-Repository-DataSource 的分层设计 PaySdkClient(聚合支付 SDK)作为 DataSource 对象,只对 Repository 可见
可测试性 PayRepository 声明为接口,并提供 PayRepositoryImplFakePayRepository 两个实现 APP 开发阶段使用 FakePayRepository,联调阶段切换为 PayRepositoryImpl
与 Jetpack 无缝集成 Hilt + Flow 使用 Hilt 进行单例作用域管理,使用 Flow 实现支付状态优雅流转

架构图

  • Domain 层可选,当业务场景复杂度高、有复用的需求时,会将其封装出 Domain 层的 UseCase
  • ViewModel 既可以引用 UseCase,也可以引用 Repository
  • Repository 层的每个模块,提供了真实实现和 Fake 实现,后者可用于开发阶段自测、自动化测试。
  • 层与层之间单向依赖,通过 Hilt 进行实例管理和注入。

时序图

  • 两层订单号设计
    • 业务订单号:由业务(例如健身课程)后台生成,是业务系统内标识,描述用户买了哪门课、多少钱等。
    • 支付交易号:由支付公司生成,在支付渠道内唯一定位这笔支付。
    • 两层订单号要做绑定关系,方便对账查账。
  • 支付后台 ---支付结果通知--> 业务后台
    • 回调:支付后台在用户完成支付后,会向商户预先配置的回调地址(notify URL)发送一条 HTTP POST 请求,里面包含订单号、支付结果、签名等信息。
    • 校验:健身课程后台收到后需要做校验(验签、防重放)并更新订单状态。
    • 重试:如果商户后台返回失败或超时,支付后台会按策略重试几次(指数退避、定时重发等)。
  • 业务后台 ---订单最新状态--> 前端APP页面
    • 常见做法(轮询):APP 拿到支付 SDK 支付结果后,立即查询一次健身课程后台。为了等到最终一致性,会做2~3次短时间轮询,每次间隔数秒。
    • 推送通知:后台通过移动推送告知 APP 订单状态变更,不保证实时与可靠,一般用作辅助手段。
    • 长轮询:前端发起长连接,等后台主动推送订单状态更新。实时性更好,但后台需要具备推送能力,对网络环境要求高。
sequenceDiagram Note left of APP健身课程页: 1.用户发起支付 APP健身课程页->>APP健身课程页: 发起支付 activate APP健身课程页 Note left of APP健身课程页: 2.创建订单 APP健身课程页->>健身课程后台: 请求订单号 activate 健身课程后台 健身课程后台-->>APP健身课程页: 业务订单号 deactivate 健身课程后台 Note left of APP健身课程页: 3.SDK收银 APP健身课程页->>支付SDK: 拉起收银台 activate 支付SDK 支付SDK->>支付后台: 统一下单接口 activate 支付后台 支付后台-->>支付SDK: 支付交易号 deactivate 支付后台 支付SDK->>支付SDK: 完成支付 Note left of APP健身课程页: 4.返回支付结果 支付SDK -->>APP健身课程页: 支付结果 deactivate 支付SDK Note left of APP健身课程页: 5.支付后台落账 支付SDK->>支付后台: 发送订单号&支付结果 activate 支付后台 支付后台->>支付后台: 标记该订单已支付 支付后台 -->> 健身课程后台: 通知支付结果(通过提前注册的回调地址) deactivate 支付后台 Note left of APP健身课程页: 6.确认订单状态 APP健身课程页->>健身课程后台: 查询订单最新状态(同步or轮询) activate 健身课程后台 健身课程后台-->>APP健身课程页: 已支付订单 deactivate 健身课程后台 deactivate APP健身课程页 Note left of APP健身课程页: 7.刷新界面 APP健身课程页->>APP健身课程页: 刷新页面

开发细节

是分层使用不同的数据类,还是把同一个数据类跨层传输

分层使用不同的数据 Bean

  • 关注点分离:数据层-贴近外部协议,领域层-关注业务语义,展示层-关注界面状态。
  • 解耦:底层数据类型发生变化时,分层设计可以缓冲这种变化,降低对上层页面逻辑影响。反之亦然。

共用一个 Bean

  • 短期省事,但长期可能会让层次之间耦合、边界模糊。

跨层之间 Bean 转换

使用 Mapper 扩展函数,进行 底层->上层 的转换。

kotlin 复制代码
// DTO -> Domain
fun OrderDto.toDomain(): Order {
    val status = when (statusCode) {
        0 -> OrderStatus.CREATED
        1 -> OrderStatus.PAID
        else -> OrderStatus.FAILED
    }
    return Order(
        id = orderId,
        price = amount,
        status = status
    )
}

// Domain -> UI
fun Order.toUiModel(): OrderUiModel {
    val statusText = when (status) {
        OrderStatus.CREATED -> "待支付"
        OrderStatus.PAID -> "已支付"
        OrderStatus.FAILED -> "支付失败"
    }
    return OrderUiModel(
        displayId = "订单号: $id",
        priceLabel = "¥${price / 100.0}", // 转换为金额字符串
        statusText = statusText
    )
}

ViewModel 里面则通过 FLow.map 进行转换。

kotlin 复制代码
class OrderViewModel(
    private val repository: OrderRepository
) : ViewModel() {

    val orderUiState: StateFlow<OrderUiModel?> =
        repository.getOrder() // Flow<OrderDto>
            .map { dto -> dto.toDomain() }
            .map { domain -> domain.toUiModel() }
            .stateIn(viewModelScope, SharingStarted.Eagerly, null)
}
相关推荐
可触的未来,发芽的智生2 小时前
追根索源-神经网络的灾难性遗忘原因
人工智能·神经网络·算法·机器学习·架构
linux修理工3 小时前
n1 ARMbian部署Grafana
arm开发·架构·grafana
axban7 小时前
QT M/V架构开发实战:QFileSystemModel介绍
开发语言·qt·架构
是罐装可乐7 小时前
深入理解 Vue3 Router:三种路由模式的工作原理与实战应用
架构·vue·路由·history·hash·ssr·router
爱睡觉的圈圈9 小时前
分布式IP代理集群架构与智能调度系统
分布式·tcp/ip·架构
APItesterCris12 小时前
构建分布式京东商品数据采集系统:基于 API 的微服务实现方案
分布式·微服务·架构
程序员在线炒粉8元1份顺丰包邮送可乐13 小时前
Docker 部署生产环境可用的 MySQL 主从架构
mysql·docker·架构
Angelyb15 小时前
微服务保护和分布式事务
java·微服务·架构
失散1316 小时前
分布式专题——10.1 ShardingSphere介绍
java·分布式·架构·shardingsphere·分库分表