【从0-1业务中台】中台支付系统革命实践

1. 引言

在业务沉淀、企业急需转型的浪潮中,构建一个强大、灵活的支付中台系统对于企业的战略发展至关重要。本文详细介绍支付中台服务的数据库设计和功能概要设计,目的是为了实现一个可扩展、高效且安全的支付中台。

2. 设计目标和原则

2.1. 设计目标

2.1.1. 核心目标

● 多应用、主体、渠道接入:支持多个应用,多个公司主体、多个渠道的模式接入。

● 线上兼容与扩展:线上支付渠道实现兼容扩展和逐步的无缝切换。

2.1.2. 高效和稳定性

● 性能优化:确保系统能够处理高峰时段的大量交易。

● 系统稳定性:保障系统在各种负载下的稳定运行。

2.1.3. 安全性与合规性

● 数据加密:对敏感支付数据进行加密处理。

● 安全合规性:确保支付系统符合行业安全标准。

2.2. 设计原则

2.2.1. 数据一致性

● 分布式事务:采用分布式事务处理,确保数据的一致性和完整性。

● 数据对账:定期订单对账与一致性校验。

2.2.2. 用户体验

● 接口易用性:提供清晰易懂的API,优化用户接入体验。

● 流畅的交易流程:确保支付过程简洁流畅,减少用户操作步骤。

3. 数据表结构设计

3.1. 公司主体表

【功能描述】

存储接入公司主体的基本信息。

【表结构】

字段名 字段说明 数据类型 长度 空否
id 主键 bigint 20 N
subject_id 主体ID bigint 20 N
subject_name 主体名称 varchar 200 N
subject_code 营业执照号码 varchar 64
subject_country_code 主体所属国家代码 varchar 64
... ... ... ... ...

【索引】

索引名 引用字段 索引说明 索引类型
subject_id_index subject_id 主体ID唯一索引 Unique

【字段说明】

● 主体ID:为每个主体分配一个ID。

● 主体所属国家代码:默认值CN,具体可参考国家代码标准。

3.2. 支付方式表

【功能描述】

存储所有对接支付方式。

【表结构】

字段名 字段说明 数据类型 长度 空否
id 主键 bigint 20 N
mode_name 支付方式名称 varchar 200 N
mode_type 支付方式类型 int 3 N
mode_code 支付方式代码 varchar 64 N
... ... ... ... ...

【字段说明】

● 支付方式类型:1:谷歌支付 2:苹果支付 3:Paypel 4:PayerMax...

● 支付方式代码:可以用来做订单ID前缀,谷歌支付:gg 苹果:ap Paypel: pp ...

3.3. 应用-公司主体关系表

【功能描述】

存储应用和公司主体多对多关系信息。

【表结构】

字段名 字段说明 数据类型 长度 空否
id 主键 bigint 20 N
app_id 应用ID bigint 20 N
subject_id 主体ID bigint 20 N

3.4. 应用-支付方式配置表

【功能描述】

存储所有对接应用对应支付方式的支付配置。

【表结构】

字段名 字段说明 数据类型 长度 空否
id 主键 bigint 20 N
app_id 应用ID bigint 20 N
mode_id 支付方式ID bigint 20 N
api_key api对接key varchar 200 N
api_secret api对接密钥 varchar 2000 N
env_type 环境类型 tinyint 1 N
pay_callback_url 支付通知回调URL varchar 200 N
pay_result_url 支付结果URL varchar 200
extra_param 扩展参数 varchar 2000
... ... ... ... ...

【字段说明】

● api对接key:应用对应在支付平台中对应的api key,有时可能是叫clientId或者商户号,在中台中统一抽象叫做api_key

● api对接密钥:应用对应在支付平台中对应的api 密钥,有时可能是叫clientId或者商户号,在中台中统一抽象叫做api_key

● 环境类型:1:测试/沙盒环境 2:生产环境

● 支付通知回调URL:第三方平台通知支付订单结果的回调URL

● 支付结果URL:当用户完成支付后,客户会被跳转到这个URL(有些自带的则无需填写这个字段)

● 扩展参数:有一些支付平台可能不止是key和密钥验证的,可以将对应的参数json数据放于此字段中

【索引】

索引名 引用字段 索引说明 索引类型
app_mode_env_index app_id,mode_id,env_type 应用ID方式ID唯一索引 Unique

3.5. 支付渠道表

【功能描述】

存储所有对接的支付渠道信息。

【表结构】

字段名 字段说明 数据类型 长度 空否
id 主键 bigint 20 N
mode_id 支付方式ID bigint 20 N
channel_name 渠道名称 varchar 200 N
channel_code 渠道code 渠道特定代码 32
... ... ... ... ...

【字段说明】

● 支付方式ID:对应支付方式表主键

● 渠道code:对应渠道提供/自定义code,具体可参考对应支付渠道的文档中渠道code说明

3.6. 主体支付方式关系表

【功能描述】

存储主体对应支付方式信息

【表结构】

字段名 字段说明 数据类型 长度 空否
id 主键 bigint 20 N
subject_id 主体ID bigint 20 N
mode_id 支付方式ID bigint 20 N

【字段说明】

● 主体ID:存储company_subject_info表的subject_id字段

● 支付方式ID:存储pay_mode_info表的主键

3.7. 支付订单表

【功能描述】

存储主体对应支付订单信息

【表结构】

字段名 字段说明 数据类型 长度 空否
id 主键 bigint 20 N
order_id 中台订单ID varchar 64 N
business_order_id 业务订单ID varchar 64 N
platform_order_id 支付平台订单ID varchar 64
app_id 应用ID bigint 20 N
channel_id 渠道ID bigint 20 N
mode_id 支付方式ID bigint 20 N
subject_id 主体ID bigint 20 N
currency_type 支付货币类型 varchar 32 N
currency_amount 支付货币金额 decimal 保留两位小数 N
pay_credential 支付凭证 varchar 3000
create_time 订单创建时间 datetime N
pay_time 订单支付时间 datetime
order_state 订单状态 int 2 N
extra_param 扩展参数 varchar 200
... ... ... ... ...

【索引】

索引名 引用字段 索引说明 索引类型
app_id_index app_id 应用ID索引 Normal
order_id_index order_id 中台订单ID唯一索引 Unique
business_id_index business_order_id 业务订单ID唯一索引 Unique

【字段说明】

● 中台订单ID:生成规则参考功能设计说明书

● 业务订单ID:业务调用侧传过来的订单ID

● 支付平台订单ID:三方支付平台的订单ID

● 支付货币类型:参考标准国际货币单位表

● 支付凭证:客户端请求中台支付时携带的支付凭证(如果有的则保存,有的可能是单个字段,有的可能是JSON字段)

● 订单状态:0:订单待支付 1:订单已支付 -1:支付取消...

4. 功能概要设计

4.1. 支付策略设计

功能描述

对于支付应该采用抽象、策略模式进行相应的业务处理和流程设计

支付上下文、工厂模式定义
csharp 复制代码
class PaymentContext{
    
    PaymentCnotext getInstance(){
        ...
    }
    
   PaymentStrategy getPaymentStrategy(int payMode,int payChannelType){
   ....
    }
}
抽象、策略类定义

● 策略接口

scss 复制代码
interface PaymentStrategy {
    //支付流程执行
    PaymentResultVo excutePay(PaymentDetailsDto dto);
    //支付回调
    PaymentCallbackResultVo callback(PaymentCallbackDto dto);
    //查询订单
    PaymentDetailsVo searchOrder(PaymentDetailsDto dto);
    //验证订单
    ...
}

● 抽象策略父类

java 复制代码
abstract AbstractPaymentStrategy implements PaymentStrategy {
    ....
}
支付状态定义
scss 复制代码
//与数据库订单表order_state字段一致
enum PayOrderStatusType {


    /**
     * 支付异常
     */
    STATE_PAY_ERROR(-2, "支付异常"),

    /**
     * 重复订校验凭据
     */
    STATE_REPEAT_VERIFY(-3, "重复订校验凭据"),

    /**
     * 订单已存在
     */
    STATE_ORDER_EXIST(-4, "订单已存在"),

    /**
     * 未找到该笔订单
     */
    STATE_ORDER_NOT_FOUND(-5, "未找到订单"),

    /**
     * 待支付
     */
    STATUS_WAIT_PAID(0, "待支付"),

    /**
     * 支付成功
     */
    STATE_PAY_SUCCESS(1, "支付成功"),
    /**
     * 支付取消
     */
    STATUS_CANCEL_PAID(-1, "支付取消");

    ...
}
实体对象定义

● 支付信息DTO

arduino 复制代码
class PaymentDetailsDto{
    //业务订单ID
    String orderId;
    //支付货币类型
    String currencyType;
    //支付货币金额
    String currenctAmount;
    //付款方式    int payModeType;
    //支付渠道
    int payChannelType;
    ...
}

● 支付创建结果

arduino 复制代码
class PaymentDetailsVo{

     /**
     * 第三方支付下单地址
     */
    String redirectUrl;

    /**
     * 创建订单是否成功
     */
    boolean success;

    /**
     * 创建订单支付平台提示消息
     */
    String message;    
    /**
     * 中台订单ID
     */    String centerPlatformOrderId;
     /**
     * 支付平台订单ID
     */   
     String platformOrderId;
}

● 支付回调DTO

arduino 复制代码
class PaymentCallbackDto{
   //此dto需要定义公共参数,然后再根据具体类型进行具体DTO对象的强转
   
   //公共参数如下
   
   //状态
   String state;
   //第三方订单ID
   String orderId;
   
   //参数随着支付渠道进一步接入根据不同渠道的回调参数进行进一步的抽象
   ...
}

● 支付回调结果

arduino 复制代码
class PaymentCallbackResultVo{
   
    /**
     * 支付状态
     *
     * @see PayOrderStatusType#getType()
     * @see PayOrderStatusType#getDesc()
     */
     int state;
    /**
     * 重定向地址
     */
    String redirectUrl;
    //支付货币类型
    String currencyType;
    //支付货币金额
    String currenctAmount;

}

● 订单详情DTO

arduino 复制代码
class OrderDetailsDto{
    //支付货币类型
    String currencyType;
    //支付货币金额
    String currenctAmount;
    //付款方式    int payModeType;
    ...
}

● 订单详情VO

arduino 复制代码
class OrderDetailsVo{
    //支付货币类型
    String currencyType;
    //支付货币金额
    String currenctAmount;
    /**
     * 支付状态
     *
     * @see PayOrderStatusType#getType()
     * @see PayOrderStatusType#getDesc()
     */
    private int state;
    ...
}
操作步骤
  1. 定义支付上下文类 PaymentContext,添加 getInstance 和getPaymentStrategy方法根据支付方式,支付渠道类型创建并获取PaymentStrategy 返回对应的支付策略

  2. 定义支付策略接口 PaymentStrategy。

  3. 策略接口中定义策略方法如 excutePay(PaymentDetailsDto dto)、callback(PaymentCallbackDto dto)、searchOrder(OrderDetailsDto dto)、verifyOrder(OrderDetailsDto dto) 等。

  4. 定义一个抽象父类 AbstractPaymentStrategy,实现PaymentStrategy中的所有策略接口。

  5. 具体策略实现,每个支付策略类继承抽象父类,类内部包含特定于支付方式的逻辑和配置,例如API调用、数据加密、网络通信等。

  6. 如果策略类需要外部资源(如数据库连接、网络客户端),应通过抽象父类依赖注入提供这些资源,而不是在类内部直接创建。

  7. 第三方接口Api对接时应该要建立对应的Api库和模块,使用OkHttp远程调用,方便扩展性与提高http线程利用率。

错误处理和日志记录

● 异常处理:

在策略类中捕获和处理可能的异常,如网络错误、API故障。

应对不同类型的异常采取不同的处理策略,比如重试、回滚、用户通知等。

● 日志记录:

在关键步骤记录详细日志,包括请求数据、执行结果、异常信息等。

使用统一的日志格式和级别管理,便于后续分析和故障排查。

数据合规性处理

● 数据保护:

对敏感信息(如信用卡信息)进行加密处理。

确保所有网络通信都通过安全的协议(如HTTPS)进行。

● 合规性:

遵守行业安全标准,如PCI DSS(针对信用卡交易)。

定期进行安全审计和漏洞扫描。

● 插件式策略管理:

设计系统以支持"插拔式"策略,新的支付方式可以作为插件加入而不影响现有系统。

使用工厂模式或依赖注入框架来管理策略对象的创建和生命周期。

4.2. 订单创建流程设计

功能描述

订单创建分为两个阶段,首先是在中台系统创建订单,随后调用第三方服务接口创建对应的第三方订单。

操作步骤
  1. 用户在客户端界面选择或添加需要购买的商品或服务。

  2. 用户提交订单,客户端系统将订单信息(货币类型、货币数量、应用ID与主体ID)封装成JSON格式,并通过POST请求发送至后端服务。

  3. 后端服务接收请求后,首先验证用户的身份和操作权限,同时检查订单信息的完整性和正确性。

  4. 验证通过后,后端服务在中台系统中创建订单,生成中台订单ID,并记录订单信息。

  5. 后端服务调用第三方接口,传递必要的订单信息,以创建第三方订单。

  6. 后端服务将订单创建的结果(包括中台和第三方订单的信息)封装成JSON格式响应给前端。

  7. 前端接收到响应后,根据结果给用户反馈订单创建状态与成功或失败的提示。

中台订单ID生成规则

为方便区分主体、支付方式、渠道的订单,将采用可读性较强的订单命名方式:

支付方式代码+主体ID+渠道ID++生成订单时分秒+当天订单数量(6位补位)

例如:Paypal支付(pp)、A主体(主体 ID:110224)、支付宝渠道(15)、生成订单时间(2023年11月10日 15点20分30秒)、当天订单数量(第210个)

则订单号按规则生成为:pp1102241520231110152030000210,提现订单在订单ID前面加上wd,例如 wdpp1102241520231110152030000210

接口设计

创建订单接口

● 请求方法: POST

● URL: /payment/pay/create-order

● 请求头:

less 复制代码
Content-Type: application/json
`Authorization: Bearer <access_token>`

请求示例:

json 复制代码
{
  "appId": "long,应用ID"
  "businessOrderId": "string,业务订单ID",
  "currencyType": "string,支付货币类型",
  "currenctAmount": "BigDecimal,支付货币金额",
  "subChannelCode": "string,子渠道",
  "channel":"string,支付渠道",
}

响应示例:

css 复制代码
{
    "code": 200,
    "message": "创建订单成功",
    "data": {
        "success": "boolean,创建订单请求是否成功",
        "redirectUrl": "string,第三方支付下单地址",
        "message" :"string,创建订单支付平台提示消息",
        "centerPlatformOrderId": "string,中台订单ID",
        "platformOrderId": "string,支付平台订单ID"
    }
}

性能考虑

● 订单ID的验证应该缓存以减少数据库查询。

4.3. 渠道对接流程设计

功能描述

实现服务端对多种支付渠道API调用的对接策略,以创建、查询、验证支付订单。

操作步骤
  1. 接收支付请求: 服务端接收来自客户端的支付请求,该请求包含必要的订单信息,如订单ID、支付金额、货币类型等。

  2. 验证支付信息: 服务端验证支付请求的完整性和合法性,包括用户身份验证、订单金额检查等。

  3. 选择支付渠道: 根据用户选择或应用逻辑,服务端确定要使用的支付渠道(如Stripe、PayPal等)。

  4. 构建支付请求: 服务端根据选定的支付渠道API规范,构建支付请求。这通常涉及组装API请求所需的数据和参数。

  5. 发送支付请求到支付渠道: 服务端向选定的支付渠道发送API请求。这通常是一个HTTP请求,包含必要的认证信息和支付参数。

  6. 处理支付渠道响应: 服务端接收并处理来自支付渠道的响应。这可能包括支付确认、错误处理、异常捕获等。

  7. 记录交易信息:无论成功与否,服务端都应记录此次交易的详细信息,以便于后续的审计和问题解决。

  8. 返回支付结果: 服务端将支付结果(成功或失败及相关信息)封装成响应,发送回客户端。

API调用接口设计

● 请求方法 POST

● URL 对应渠道相应的API地址

● 请求头

less 复制代码
//根据相应渠道对接文档要求设置,以下为示例
Content-Type: application/json

请求示例

json 复制代码
//根据相应渠道进行设置,以下为参数示例,具体以渠道对接文档为准
{
  "oderId": 123456,
  "amount": 100.00,
  "currency": "USD",
  "payment_channel": "stripe"
}

响应示例

json 复制代码
//根据相应渠道进行封装对象,以下为返回示例,具体以渠道对接文档为准
{
  "success": true,
  "message": "支付订单创建成功",
  "order_id": "ord_123456789",
  "transaction_id": "tr_987654321"
}

性能考虑

● 优化API调用流程,与支付渠道的通信是高效和稳定的。

● 实现强大的错误处理机制,以便在API调用失败时快速响应。

数据加密

● 确保所有敏感信息,如API密钥和用户支付信息,在传输和存储时都得到加密。

4.4. 订单回调流程设计

通过实现一个统一的回调接口,我们可以简化支付渠道回调的处理,同时保持代码的整洁和可维护性。

定义统一的回调接口

● 定义统一回调路径和方法:定义一个通用的API回调端点,如 /payment/payment/callback,使用 POST 方法接收回调。

● 请求体抽象:因为不同支付渠道的回调格式可能不同,可以定义一个通用的请求体结构,例如使用JSON格式,包含一些通用字段(如transaction_id, amount, status)和一个details字段,后者可以是一个包含渠道特定信息的JSON对象。

解析验证回调数据

● 数据解析:在服务端解析接收到的JSON数据。例如在Java语言中,可以使用Spring Boot框架的@RequestBody注解来自动将请求体映射到一个Java对象。

● 签名验证:对于需要验证签名的支付渠道,实现一个通用的验证方法,并在接收到回调时进行验证,以确保回调的真实性。

处理回调逻辑

● 业务逻辑处理:根据回调数据中的信息(如支付状态、订单号等),执行相应的业务逻辑,如更新订单状态、通知用户等。

● 特定渠道支持:对于特定渠道的特殊处理,可以使用策略模式,为每个支付渠道实现一个处理策略,并在回调接口中根据渠道类型选择相应的策略执行。

返回响应

● 统一响应格式:对于所有的支付渠道,返回一个统一格式的响应。如果支付渠道需要特定的响应格式,可以在相应的策略实现中进行转换。

● 根据应用配置的回调与通知URL进行回调:对于所有的应用,都提前在中台中配置了回调URL或支付结果URL,当中台收到三方回调时,进行逻辑处理完毕后,会对配置的URL进行回调操作。

回调示例处理代码
less 复制代码
@RestController
@RequestMapping("/pay")
public class PaymentCallbackController {
    @PostMapping("/callback")
    public ResponseEntity<?> handlePaymentCallback(@RequestBody PaymentCallbackRequest callbackRequest) {
        // 验证回调签名(如果需要)
        // 解析和处理回调数据
        // 返回统一格式的响应
        // 发送给应用在中台中配置的回调URL
    }
}
回调业务平台

● 请求方法: POST

● URL: 中台配置的回调URL

● 请求头:

bash 复制代码
Content-Type: application/json

请求体示例:

json 复制代码
{
  "businessOrderId": "string,业务订单ID",
  "centerPlatformOrderId": "string,中台订单ID",
  "platformOderId": "string,三方订单ID",
  "state": "订单状态,参观订单状态定义",
  "details": {
    // 渠道特定信息字段...
  }
}
注意事项

● 安全性:确保对回调数行的验证析,防止欺诈和注入攻击。

● 兼容性:考虑到不同支道有不同据格式和要求,需要灵活处理这些差异。

● 测试:对每种支付渠道调充分试,确保在不同情况下都能正确处理。

4.5. 数据指标监控设计

4.5.1. 充值交易成功率

监控策略
  1. 实时跟踪所有充值交易,计算成功交易订单的百分比(成功交易订单/ 对应时间/周期/渠道下的订单数量)。

  2. 根据不同支付渠道进行分析,列出每一个渠道的交易成功率。

  3. 所有渠道的总交易成功率也需要汇总。

告警策略
  1. 交易成功率低于指定阙值(定时任务15分钟扫描一次),例如20%(阙值可根据不同渠道动态配置调整),则触发告警

  2. 同一告警类型一小时内静默期(即同样的告警 一小时只会触发一次提醒)

  3. 每一天的凌晨9点定时告警提醒(不管有没有达到告警阙值)都需要触发一次最近24小时内的交易成功率告警提醒

4.5.2. 充值异常率

监控策略
  1. 实时跟踪所有充值交易,计算充值异常率百分比(创建订单异常、交易成功回调时异常、回调业务方异常)/ 对应时间/周期/渠道下的订单。

  2. 根据不同支付渠道进行分析,列出每一个渠道的充值异常率。

  3. 所有渠道的总充值异常率也需要汇总。

告警策略
  1. 充值异常率高于指定阙值(定时任务15分钟扫描一次),例如5%(阙值可根据不同渠道动态配置调整),则触发告警

  2. 同一告警类型一小时内静默期(即同样的告警 一小时只会触发一次提醒)

  3. 每一天的凌晨9点定时告警提醒(不管有没有达到告警阙值)都需要触发一次最近24小时内充值异常率告警提醒

4.5.3. 充值环比数据

监控策略
  1. 定期(每日)比较充值数据,分析环比增长或下降趋势,环比的数据为:充值成功率、充值异常率、充值总金额。

  2. 根据不同支付渠道进行分析,列出每一个渠道的环比数据。

  3. 所有渠道的总充值环比数据也需要汇总。

告警策略
  1. 环比数据下降超过指定阙值(3小时扫描一次,例如今日最近6个小时时间段对比昨日这个时间段),环比下降10%(阙值可根据不同渠道动态配置调整),则触发告警

  2. 同一告警类型6小时内静默期(即同样的告警 6小时内只会触发一次提醒)

  3. 每一天的凌晨9点定时告警提醒(不管有没有达到告警阙值)都需要触发一次环比数据告警提醒

相关推荐
yuanbenshidiaos41 分钟前
c++---------数据类型
java·jvm·c++
向宇it44 分钟前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
Lojarro1 小时前
【Spring】Spring框架之-AOP
java·mysql·spring
莫名其妙小饼干1 小时前
网上球鞋竞拍系统|Java|SSM|VUE| 前后端分离
java·开发语言·maven·mssql
isolusion1 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp2 小时前
Spring-AOP
java·后端·spring·spring-aop
Oneforlove_twoforjob2 小时前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
TodoCoder2 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
向宇it2 小时前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
小蜗牛慢慢爬行2 小时前
Hibernate、JPA、Spring DATA JPA、Hibernate 代理和架构
java·架构·hibernate