PAYATHON 2026

两种订单类型如何共用 payment 和 card details 实体

支付小周

结论

两种订单可以共用同一组 Payment 和支付方式实体,但支付记录不应关联到 ORDER_TYPE。

ORDER_TYPE 表示订单类别,比如“商品订单”或“重复处方订单”。付款针对的是某一笔具体订单,因此更合适的设计是:

  1. 建立统一的 Order 主实体;
  2. 使用 order_type_id 区分订单类型;
  3. 通过 order_id 将 Payment 关联到具体订单;
  4. 将银行卡信息建模为可复用的 PaymentMethod,供付款记录引用。

这种设计既避免了实体重复,也能明确记录每次付款对应的订单。

推荐的实体关系

可以采用下面的结构:

OrderType 1 ─── N Order 1 ─── N Payment N ─── 1 PaymentMethod
                         │
                         ├── ProductOrderDetails
                         │
                         └── PrescriptionOrderDetails

各实体分别负责:

  • OrderType:定义订单类别;
  • Order:保存所有订单共有的数据;
  • ProductOrderDetails:保存商品订单特有的数据;
  • PrescriptionOrderDetails:保存处方订单特有的数据;
  • Payment:记录付款行为、金额和状态;
  • PaymentMethod:保存支付服务商标记化后的支付方式信息。

OrderType 是分类数据,Order 才是实际的业务交易。付款必须关联交易实例,而不能只关联订单分类。

假设系统中有 100 笔商品订单,它们都属于同一个 OrderType。如果 Payment 直接关联 OrderType,数据库就无法确定某笔付款属于其中哪一笔订单。

表结构示例

以下以关系型数据库为例。由于 Order 在某些数据库中可能是保留字,示例使用 customer_order 作为表名。

CREATE TABLE order_type (
    order_type_id INTEGER PRIMARY KEY,
    type_code VARCHAR(30) NOT NULL UNIQUE,
    type_name VARCHAR(100) NOT NULL
);

CREATE TABLE customer_order (
    order_id BIGINT PRIMARY KEY,
    customer_id BIGINT NOT NULL,
    order_type_id INTEGER NOT NULL,
    order_status VARCHAR(30) NOT NULL,
    ordered_at TIMESTAMP NOT NULL,
    total_amount DECIMAL(12, 2) NOT NULL,
    currency CHAR(3) NOT NULL,
    FOREIGN KEY (order_type_id)
        REFERENCES order_type(order_type_id)
);

订单类型表可以存放固定的分类数据:

INSERT INTO order_type (order_type_id, type_code, type_name)
VALUES
    (1, 'PRODUCT', '商品订单'),
    (2, 'REPEAT_PRESCRIPTION', '重复处方订单');

两类订单的付款记录可以放在同一张表中:

CREATE TABLE payment (
    payment_id BIGINT PRIMARY KEY,
    order_id BIGINT NOT NULL,
    payment_method_id BIGINT,
    amount DECIMAL(12, 2) NOT NULL,
    currency CHAR(3) NOT NULL,
    payment_status VARCHAR(30) NOT NULL,
    provider_reference VARCHAR(255),
    paid_at TIMESTAMP NULL,
    FOREIGN KEY (order_id)
        REFERENCES customer_order(order_id)
);

这里更适合使用 Order 1:N Payment,而不是简单的 1:1。即使当前规定一张订单只能付款一次,以后也可能遇到:

  • 付款失败后重新支付;
  • 分次付款;
  • 部分退款;
  • 更换银行卡后再次支付;
  • 一张订单产生授权、扣款和退款等多笔支付交易。

如果业务能够明确保证每张订单只有一条付款记录,可以给 payment.order_id 增加唯一约束:

ALTER TABLE payment
ADD CONSTRAINT uq_payment_order UNIQUE (order_id);

不过在实际的支付系统中,保留一对多关系通常更便于扩展。

处理两种订单的专有字段

如果两种订单的大部分字段相同,可以只使用一张 customer_order 表,再通过 order_type_id 区分类型。

如果处方订单需要保存处方编号、开方医生或配药状态,而商品订单需要记录配送方式等专有字段,可以增加一对一的扩展表:

CREATE TABLE product_order_details (
    order_id BIGINT PRIMARY KEY,
    delivery_method VARCHAR(50),
    FOREIGN KEY (order_id)
        REFERENCES customer_order(order_id)
);

CREATE TABLE prescription_order_details (
    order_id BIGINT PRIMARY KEY,
    prescription_id BIGINT NOT NULL,
    dispensing_status VARCHAR(30) NOT NULL,
    FOREIGN KEY (order_id)
        REFERENCES customer_order(order_id)
);

通用数据和支付关系只需定义一次,各类订单的专有数据则存入对应的扩展表。

应用层或数据库约束还需要保证:

  • PRODUCT 类型的订单只能有 product_order_details;
  • REPEAT_PRESCRIPTION 类型的订单只能有 prescription_order_details;
  • 同一订单不能同时出现在两个扩展表中。

普通外键通常无法完整表达这类跨表约束,因此可能需要使用触发器、应用层校验,或者采用支持相应约束能力的数据库设计。

银行卡信息如何设计

不建议使用普通的 card_details 表保存完整银行卡资料,也不要保存:

  • 完整银行卡号;
  • CVV 或 CVC;
  • 未加密的敏感认证数据。

更安全的方式是让合规的支付服务商保存真实卡片信息,数据库只保留支付令牌以及用于展示的非敏感信息:

CREATE TABLE payment_method (
    payment_method_id BIGINT PRIMARY KEY,
    customer_id BIGINT NOT NULL,
    provider VARCHAR(50) NOT NULL,
    provider_token VARCHAR(255) NOT NULL UNIQUE,
    method_type VARCHAR(30) NOT NULL,
    card_brand VARCHAR(30),
    card_last4 CHAR(4),
    expiry_month SMALLINT,
    expiry_year SMALLINT,
    is_active BOOLEAN NOT NULL DEFAULT TRUE
);

然后为 payment 到 payment_method 的关系补充外键:

ALTER TABLE payment
ADD CONSTRAINT fk_payment_method
FOREIGN KEY (payment_method_id)
REFERENCES payment_method(payment_method_id);

采用这种模型后:

  • 一个客户可以保存多个支付方式;
  • 同一个支付方式可以用于多个订单;
  • 每笔付款仍然准确关联到具体订单;
  • 删除或停用支付方式不会导致历史付款记录丢失。

如果系统不支持保存银行卡,Payment 也可以只记录支付服务商返回的交易引用,无需建立持久化的 PaymentMethod。

还需要区分“支付方式”和“支付交易”

这两个概念不应放在同一个实体中:

  • PaymentMethod 表示“用什么支付”,例如某张 Visa 卡对应的支付令牌;
  • Payment 表示“发生了哪次支付”,其中包括金额、状态和交易编号。

同一张卡可以支付多张订单,一张订单也可能产生多次支付尝试,所以二者通常不是一对一关系。

注意事项

金额应使用 DECIMAL 或数据库提供的精确数值类型,不要使用 FLOAT。还应同时保存 ISO 4217 货币代码,例如 CNY、GBP。

付款状态应根据实际业务需要设置,例如:

PENDING
AUTHORIZED
CAPTURED
FAILED
CANCELLED
REFUNDED
PARTIALLY_REFUNDED

如果需要完整审计授权、扣款和退款过程,可以增加 payment_transaction 表,不要反复覆盖 payment 中原有的数据。

设计时应明确各实体的职责:订单类型用于分类,订单承载交易,付款关联具体订单,支付方式由付款引用。这样,两种订单就可以共用同一套清晰且安全的支付模型。

备注:内容仅供参考。