本文根据过去一年持续迭代的支付卡平台整理。为保护业务与合作方信息,项目名称、渠道名称、域名、内部接口和部署标识均已泛化。
过去一年,我持续负责一套支付卡与跨境资金服务的需求分析、方案设计、Go API、Laravel 管理后台、渠道接入、数据库迁移、测试和运行维护。项目最早承接的是发卡能力,随后增加卡片管理、资金操作、交易记录、跨境汇款、异步事件和运营工具,逐步形成一套供多个业务应用共同使用的支付卡平台。
发卡只发生一次,卡片进入使用阶段后,持卡人、账户、交易、限额、认证和清算会长期变化。多个业务应用接入后,认证信息、数据归属和回调目标需要彼此独立;外部渠道增加后,同一业务动作又会出现不同的协议、字段和状态。平台要吸收这些差异,对下游保留稳定的业务语义。
发卡只是卡片生命周期的起点
卡业务从持卡人建档开始。平台保存统一身份,再维护内部持卡人与外部渠道身份的对应关系。业务应用只依赖平台的持卡人模型,渠道账户结构和字段差异留在网关内部。
卡片创建后进入持续管理阶段,包括状态调整、交易限额、认证信息、续卡、封存、授权与清算记录。卡片和账户分开建模,一张账户可以关联多张卡,共享账户和独立储值账户也能使用同一组领域对象。
资金服务覆盖余额、入金、出金、账户流水和商户资金查询。跨境汇款在此基础上增加收款人校验、汇率、手续费、申请和状态查询。渠道可能使用不同的金额单位、币种代码和请求结构,下游调用方式保持不变。
异步事件构成另一半业务流程。交易、认证、清算和账户状态通过 Webhook 返回,平台完成验签、解密、标准化和持久化,再根据业务归属发送给对应应用。运营后台提供查询、审计、回调重试和模拟能力,使实时 API 与人工处理共享同一组业务事实。
一次发卡请求如何穿过系统
发卡会同时连接产品配置、应用身份、持卡人、外部渠道、卡账户和卡片记录。一条完整请求的处理顺序如下:
网关先完成应用认证,并把应用身份写入请求上下文。领域服务根据卡产品选择渠道能力,判断持卡人和卡账户能否复用。共享账户沿用当前有效账户,储值账户则为新卡建立独立账户。渠道适配层接收统一请求模型,处理日期、币种、身份资料和产品参数的协议转换。
外部渠道返回结果后,系统写入渠道身份、卡账户、卡片和操作记录。通用表保存跨渠道都需要的字段,渠道专属表保存特有业务信息。需要同时保持一致的本地记录由数据库事务统一提交。
业务应用后续查询卡片、操作资金或接收事件时,继续使用平台内部标识。外部渠道标识只存在于适配和数据映射范围内,不会替代平台主标识。
领域模型不跟着渠道接口变化
业务应用、持卡人、渠道关系、卡账户、卡片和交易构成平台最主要的对象。它们描述平台自己的业务语义,不照搬某个渠道的字段结构。
平台内部标识负责连接领域对象,渠道标识负责外部映射,应用归属决定数据范围。三类标识不能混用。渠道返回的编号可能随接入方式变化,平台内部标识需要在查询、运营和审计中保持稳定。
卡片与账户分开后,资金操作可以先解析账户,再选择对应渠道;卡片只承担支付、状态和认证相关职责。共享账户允许多张卡关联同一资金账户,独立储值账户则让资金跟随单独账户。
通用表和渠道专属表也有不同职责。通用表支持跨渠道查询、应用隔离和统一后台;专属表保存授权、清算、账户快照、风控配置和原始报文等扩展信息。运营人员可以从统一对象进入,再查看对应的渠道明细。
Go API 与 Laravel 后台共享业务事实
系统由 Go API、PostgreSQL、Redis 和 Laravel 管理后台组成。API 承接业务应用请求、外部渠道调用和 Webhook,管理后台负责数据库迁移、运营查询、权限、审计与人工操作。
Go 代码按 Router、Handler、Service、渠道 Client、Repository 和 Model 分层。Router 组织路由与中间件,Handler 处理 HTTP 输入和输出,Service 承载业务规则,Client 处理外部协议,Repository 维护数据访问。Service 不依赖 HTTP 请求对象,渠道 Client 也不感知下游应用的接口格式。
Laravel 后台使用 Filament 和 Livewire 组织数据表格、详情、筛选、权限和操作入口。实时支付请求由 API 独立承载,后台与 API 使用同一套领域数据。数据库结构、运营页面和审计工具可以沿着业务变化演进,不必进入实时请求代码。
后台迁移到 Filament 后,我还把 Dcat Admin 中常用的查询体验整理为独立的 Filament Dcat Filters 组件。Scope、Range、SelectTable、组合查询、筛选状态持久化和 URL 同步等能力可以在不同 Resource 中复用,复杂业务列表不必重复实现同类筛选控件。
PostgreSQL 保存业务主数据,Redis 保存应用配置缓存、渠道令牌和幂等结果。一次请求依次经过日志、签名认证和幂等处理,Handler 把 HTTP 输入转换为领域请求,Service 根据应用身份执行归属判断和业务规则。
应用归属贯穿请求、数据和回调
多个业务应用共享网关时,每个应用都拥有独立的认证密钥、启停状态和 Webhook 配置。应用归属贯穿持卡人渠道关系、卡账户、卡片、交易、资金操作和汇款记录,查询与操作始终限制在当前应用范围内。
渠道关系由持卡人、渠道和业务应用共同确定。平台为每组关系生成独立外部引用,使不同应用在同一渠道中保留各自的数据关系。PostgreSQL 部分唯一索引约束当前有效关系,历史记录继续保留,用于追踪此前的卡片、账户和事件。
归属规则分布在四个位置:认证层确定当前应用,Service 判断业务对象归属,Repository 查询附带应用范围,Webhook 按应用配置选择目标。应用配置也使用独立缓存键,某个应用更新密钥、状态或回调地址时,可以单独刷新。
Webhook 路由遵循同一组规则。交易事件通过卡片归属找到目标应用,其他事件通过持卡人、账户和应用关系确定目标。认证、查询、写入和异步发送都使用相同的应用边界,避免只在 API 入口隔离数据,后台任务却失去范围。
两种渠道接入方式可以并存
多数卡与资金渠道共享发卡、卡状态、限额、认证、余额和资金操作等能力。平台为这些动作定义统一接口,通过适配器转换内部模型与渠道模型。注册表根据卡产品和渠道配置选择实现,业务服务只依赖统一能力。
适配器的输入和输出都是平台模型。发卡请求包含持卡人、产品和操作信息,卡片结果包含平台需要的渠道身份、账户、状态和到期信息。字段命名、日期格式、币种代码、金额单位和状态表达都在适配器内部转换。
有些渠道在认证协议、账户语义、回调格式和业务能力上形成完整且独立的模型。此时继续扩大通用接口会引入大量条件分支,系统会为它保留独立 API 版本和渠道服务,同时复用应用认证、幂等、数据库、日志和管理后台。
两种方式按业务相似度选择。能力接近的渠道复用领域服务,差异完整的渠道使用独立版本,再把通用数据写回平台模型。渠道原始数据作为快照保存在专属记录或事件日志中,日常查询依赖稳定的领域字段。
金额、时间和状态统一在边界转换
金额在领域服务和数据库中使用最小货币单位的整数表示,渠道边界根据币种规则完成单位转换与舍入,展示层只负责格式化。汇率、手续费和交易本金分别保存,查询、统计和导出使用同一组金额与币种规则。
渠道时间先解析为带时区的时间对象,再转换为 UTC 保存。交易日期、创建时间、完成时间和回调时间各自记录,API 返回统一格式,后台再按界面需要展示。
状态枚举负责渠道状态与平台状态之间的映射。渠道专属状态保留完整细节,平台状态用于统一查询和业务判断。状态名称、颜色、筛选项和审计规则都从枚举读取,卡片、交易、资金和汇款在 API 与后台中使用同一表达。
金额、时区和状态转换集中在公共转换层与领域服务中,渠道 Client 只处理协议格式。单元测试覆盖金额转换、币种映射、时间解析和状态转换,新渠道需要通过同一组边界测试。
认证、加密与幂等各自处理一类风险
业务应用使用独立密钥进行 HMAC-SHA512 请求签名,请求体、时间戳和随机值共同参与计算。网关完成签名校验后,将应用身份写入请求上下文,后续服务直接使用该身份执行归属判断。
外部渠道使用各自的安全协议。系统支持 RSA 签名与验签、RSA 分段加密和 AES 对称加密。响应通过验签后才进入业务处理,卡号、账户号、身份资料和认证信息在日志中按字段类型显示掩码或摘要。
幂等中间件只处理会改变状态的请求,并在 Redis 中保存成功结果。缓存范围由业务应用、请求类型和幂等键共同确定,请求体指纹用于确认同一个幂等键始终对应同一份业务内容。相同请求再次到达时,网关核对指纹并返回已有结果;不同应用可以独立使用同一个幂等键。
认证确认调用方和请求内容,加密保护渠道报文,幂等限制业务请求的处理次数。三部分分开实现,再按渠道和业务动作组合,避免一个中间件同时承担身份、保密和业务状态职责。
Webhook 先持久化,再异步发送
Webhook 从接收原始报文开始。平台先按渠道协议验签和解密,再解析事件类型,转换为统一事件,保存业务数据与处理状态,最后发送给所属业务应用。
入站事件使用渠道消息标识判断重复。原始报文、解密结果、签名状态、业务类型和处理时间保存在事件记录中,授权、清算和通知类事件再更新对应的卡片、交易或持卡人数据。事件先持久化,下游发送随后异步执行。
统一事件只保留卡片、交易、金额、商户、认证和时间等通用信息。渠道字段调整由转换层吸收,业务应用面对稳定结构。回调记录连接原始报文、标准化结果和发送状态,后台可以按卡片、交易、事件类型和处理状态查询。
下游发送前会规范化 JSON,再使用应用独立密钥计算签名。发送服务设置请求超时和递增间隔重试,每个应用可以配置多个事件目标,交易、认证和其他通知按用途选择地址。人工重试沿用原事件和目标配置,模拟功能则从已有业务数据生成标准事件,用于联调和验收。
运营后台是架构的一部分
管理后台覆盖持卡人、渠道关系、卡账户、卡片、交易、限额、资金、汇款、Webhook 和审计数据。Filament Resource 提供统一列表、详情、筛选和操作入口,枚举负责状态名称与颜色,多语言资源保持界面表达一致。
访问控制采用 RBAC,关键账号支持双因子认证。数据审计按关系、归属、状态和历史四类执行:确认领域对象能够互相定位,业务记录属于有效应用,数据库状态来自统一枚举,历史对象与当前记录保持预期关系。
审计命令返回每类检查数量,后台将结果转换为报告并缓存。应用配置更新后可以刷新对应缓存,失败的 Webhook 可以重新发送,测试事件可以按业务数据生成。运营和技术支持使用领域模型处理问题,不需要直接理解每个外部渠道的原始结构。
数据库演进由 Laravel Migration 统一管理。结构迁移保持可逆,数据回填通过支持预览、分批和重复执行的命令完成;模型、资源和审计查询跟随 Schema 一起更新。
可观测性围绕业务对象组织
结构化日志使用应用、持卡人、账户、卡片、交易和事件作为查询维度。一次操作从业务请求、领域处理、渠道调用到结果回写沿用相同业务标识,技术支持可以从任一对象定位完整过程。
渠道调用分阶段记录请求摘要、响应摘要和本地处理结果。HTTP Header 会过滤认证信息,请求体中的卡号、账户号、身份资料、认证信息和图片数据按类型显示掩码或长度摘要,原始敏感信息不会进入普通日志。
服务端与外部 Client 分别设置读取、写入和调用超时。健康检查观察进程与依赖状态,定时任务刷新渠道账户快照并执行运行检查。日志、健康检查和后台审计分别覆盖实时请求、服务运行与业务数据。
测试跟着系统边界走
Go 项目的测试覆盖工具、加密、Repository、Service、Handler、Middleware 和集成流程。Laravel 项目覆盖模型、枚举、数据审计、后台资源、权限和管理 API。接口文档描述下游契约,联调脚本覆盖卡片、资金、汇款和 Webhook。
金额、币种、时区、签名和加密适合纯函数测试;Repository 测试确认查询与事务行为;Service 测试通过 Mock 渠道验证业务编排;Handler 与 Middleware 测试验证 HTTP 输入、认证上下文和幂等响应。表格驱动测试用于覆盖同一规则的多组输入。
集成测试从路由发起完整请求,连接应用配置、数据库、业务服务和渠道测试环境。发卡、卡片生命周期、资金、汇款和 Webhook 各有独立流程,测试数据按环境隔离。后台测试覆盖页面、资源、权限、缓存和审计命令,PostgreSQL 专属迁移另做针对性验证。
接口文档、Mock 和依赖注入代码由生成工具维护。预提交检查验证生成结果与源码一致,使接口实现、测试替身和对接文档继续使用同一份定义。
一年迭代留下的架构取舍
支付卡平台的稳定性来自几条明确边界:平台标识不让位于渠道标识,卡片和账户分开建模,应用归属贯穿请求、查询和事件,渠道差异停在适配层,Webhook 先形成可查询记录再发送给下游。
这些选择也带来成本。通用模型和渠道专属数据需要同时维护;应用范围必须进入每一层查询和后台任务;独立版本渠道会增加接口与测试数量;事件持久化、重试和人工操作需要完整的状态设计。边界越清楚,需要维护的契约也越多。
后续增加业务应用、卡产品或外部渠道时,我会先确认领域对象与数据归属,再判断复用通用适配器还是建立独立版本,随后补齐事件、运营入口、审计和测试。这个顺序比从渠道接口反推数据库结构更稳定,也更容易判断一次接入是否达到完成条件。
延伸阅读
支付与账户平台的一年:业务边界、账本与异步架构:从账户、账本、状态机、Consumer 和跨语言契约继续展开资金系统实现。
金融科技工程手册:补充资金表示、幂等、对账、控制与测试等通用工程方法。
跨境支付:支付网关与技术方案:从支付网关、路由、多币种清算和运营支撑理解更大的系统边界。
参考资料
数据与请求边界:
框架与实现: