PAYATHON 2026

order number、transaction id 与 invoice no. 有何区别?

支付阿杰

结论

这几个编号表示的是不同概念:

  • Order number 标识客户的一次下单行为。
  • Transaction ID 标识支付系统执行的一次操作,如授权、扣款、退款或撤销。
  • Invoice no. 标识一张财务或税务凭证。
  • 对同一次操作,支付网关、收单机构和银行还可能分别生成自己的 reference ID。

在非常简单的系统里,它们可以暂时使用同一个值,但不建议这样设计。一个订单可能分多次付款,也可能经历支付重试或部分退款。一张发票还可能包含多个订单。出现这些情况后,三个编号就不再是一一对应的关系。

更合理的做法是分别建模、分别生成编号,再通过关联字段把它们连接起来。

三种编号分别代表什么

Order number

Order number 是商户系统中的订单编号,回答的是:

客户购买了什么?

它通常由订单系统生成,用于查询商品、数量、价格、收货信息和订单状态。

例如:

ORD-20260831-000123

一个订单可能对应:

  • 零次支付:订单已经创建,但客户尚未付款;
  • 一次支付:最常见的情况;
  • 多次支付:第一次失败后重试,或使用分期、组合支付;
  • 多次退款:部分退款后再次退款。

所以,order number 与 payment transaction 通常是一对多关系。

Transaction ID

Transaction ID 标识一次具体的支付操作,回答的是:

支付系统执行了哪一次资金操作?

支付不一定只有一个步骤。一个订单可能产生以下记录:

授权 authorization
扣款 capture
支付 payment
撤销 void
退款 refund
拒付 chargeback

每次操作都可能有独立的 transaction ID:

TXN-01J6N8B45JV1A6M3GQZ3H8D2PT

商户、支付网关、收单机构和银行也可能分别生成不同的编号。例如:

merchant_transaction_id
gateway_transaction_id
acquirer_reference_number
bank_reference_number

这些编号属于不同系统,不能假定它们彼此相同,也不能假定所有支付渠道都会提供某个外部编号。

Invoice no.

Invoice no. 是发票或账单编号,回答的是:

哪一张财务凭证记录了这笔应收或销售?

例如:

INV-2026-000087

发票与订单、支付之间的关系由业务规则决定,常见情况包括:

  • 一个订单开一张发票;
  • 一个订单分期开多张发票;
  • 多个订单汇总到一张月结发票;
  • 先开发票,客户之后付款;
  • 支付已经成功,但暂时没有开发票;
  • 原发票作废后重新开具新发票。

因此,不能直接用 transaction ID 代替 invoice no.。

发票编号还可能受到当地会计和税务法规的约束。例如,某些地区要求编号连续且不得重复,发票作废后也要保留相关记录。具体规则需要根据商户注册地和发票类型确认,不能只考虑技术实现是否方便。

还有哪些常见标识

实际支付系统中还经常出现以下字段:

字段含义通常由谁生成
order_id订单数据库主键商户
order_number面向客户的订单编号商户
payment_id商户系统中的一次支付尝试商户
transaction_id一次资金操作的编号商户或支付服务商
gateway_reference支付网关返回的引用编号支付网关
authorization_code发卡方批准授权时返回的代码发卡方或收单方
capture_id扣款操作编号支付服务商
refund_id退款操作编号商户或支付服务商
invoice_id发票数据库主键发票系统
invoice_number面向客户和财务的发票编号发票系统
idempotency_key防止重复提交产生重复操作API 调用方
ARN卡组织链路中的收单参考号,常用于追踪退款等资金流收单机构或卡组织

不同服务商对 payment、charge、transaction 等术语的定义并不完全相同。接入支付服务时,应以服务商的 API 数据模型为准。

哪些编号必须唯一

这些编号通常都需要唯一,但各自的唯一性范围不同。

Order number 的唯一性

建议让 order number 在整个商户订单系统中保持唯一,而不只是对单个客户唯一:

UNIQUE(merchant_id, order_number)

如果系统只服务一个商户,也可以直接设置:

UNIQUE(order_number)

公开编号不要只使用递增数字,否则很容易被枚举,还可能暴露订单量。可以给编号加入随机成分,或者把内部主键和公开编号分开。

Transaction ID 的唯一性

商户生成的 transaction ID 应在自己的支付系统中唯一:

UNIQUE(merchant_id, transaction_id)

支付网关返回的外部 ID,一般只保证在该网关或该商户账户范围内唯一。保存这类 ID 时,应将渠道也纳入唯一约束:

UNIQUE(provider, provider_account_id, provider_transaction_id)

不能假设两个支付服务商一定不会返回格式或值相同的编号。

Invoice no. 的唯一性

invoice no. 至少应在适用的公司主体、账簿或发票序列中唯一。例如:

UNIQUE(legal_entity_id, invoice_series, invoice_number)

编号能否按年重置、是否必须连续,以及作废编号能否再次使用,都取决于当地法规和财务制度。通常不应重复使用已经开具或作废的发票编号。

Idempotency key 的唯一性

idempotency_key 不是业务凭证编号。它用于保证同一个请求只执行一次,并且应在服务端规定的作用域和有效期内保持唯一。

例如,客户端因为网络超时而重新发起支付时,应继续使用同一个 key:

Idempotency-Key: 01J6N8DPS5EGWQX9Y4MJPQAZRK

如果每次重试都生成新 key,服务端可能会把重试识别为新的支付请求,进而造成重复扣款。

推荐的数据关系

不要在订单表里只保存一个 transaction_id。更稳妥的设计是将订单、支付和资金操作拆开:

CREATE TABLE orders (
    id                  BIGINT PRIMARY KEY,
    order_number        VARCHAR(64) NOT NULL UNIQUE,
    customer_id         BIGINT NOT NULL,
    currency            CHAR(3) NOT NULL,
    total_amount_minor  BIGINT NOT NULL,
    status              VARCHAR(32) NOT NULL
);

CREATE TABLE payments (
    id                  BIGINT PRIMARY KEY,
    payment_number      VARCHAR(64) NOT NULL UNIQUE,
    order_id            BIGINT NOT NULL,
    provider            VARCHAR(32) NOT NULL,
    amount_minor        BIGINT NOT NULL,
    currency            CHAR(3) NOT NULL,
    status              VARCHAR(32) NOT NULL,
    FOREIGN KEY (order_id) REFERENCES orders(id)
);

CREATE TABLE payment_transactions (
    id                       BIGINT PRIMARY KEY,
    transaction_id           VARCHAR(64) NOT NULL UNIQUE,
    payment_id               BIGINT NOT NULL,
    transaction_type         VARCHAR(32) NOT NULL,
    amount_minor             BIGINT NOT NULL,
    status                   VARCHAR(32) NOT NULL,
    provider_transaction_id  VARCHAR(128),
    created_at               TIMESTAMP NOT NULL,
    FOREIGN KEY (payment_id) REFERENCES payments(id),
    UNIQUE (provider_transaction_id)
);

CREATE TABLE invoices (
    id                  BIGINT PRIMARY KEY,
    invoice_number      VARCHAR(64) NOT NULL UNIQUE,
    order_id            BIGINT NOT NULL,
    status              VARCHAR(32) NOT NULL,
    issued_at           TIMESTAMP,
    FOREIGN KEY (order_id) REFERENCES orders(id)
);

如果系统接入了多个支付服务商,外部交易编号的唯一约束应包含 provider 和渠道账户,不能只约束 provider_transaction_id。

金额建议使用最小货币单位的整数保存,例如人民币的分或美元的美分:

1999 = CNY 19.99

不要使用二进制浮点数存储货币金额。

一次订单为什么会有多个 Transaction ID

假设客户创建了以下订单:

order_number = ORD-20260831-000123
amount = CNY 500.00

系统可能生成这些支付记录:

TXN-AUTH-001     authorization   CNY 500.00   succeeded
TXN-CAPTURE-001  capture         CNY 500.00   succeeded
TXN-REFUND-001   refund          CNY 100.00   succeeded
TXN-REFUND-002   refund          CNY 400.00   succeeded

这些操作属于同一个订单,但资金方向和业务含义并不相同。如果共用一个编号,回调去重、退款对账和问题排查都会变得困难。

支付失败后重试是另一个常见场景:

PAY-001 / TXN-001  failed
PAY-002 / TXN-002  succeeded

订单仍然只有一个,但每次支付尝试都要单独保留。不能用成功记录覆盖失败记录,否则会丢失审计和风控需要的信息。

API 处理示例

支付请求可以使用商户生成的 payment ID,并通过 idempotency key 避免重复创建:

POST /api/payments
Content-Type: application/json
Idempotency-Key: 01J6N8DPS5EGWQX9Y4MJPQAZRK

{
  "order_number": "ORD-20260831-000123",
  "amount": 50000,
  "currency": "CNY"
}

响应应分别返回不同层次的标识:

{
  "order_number": "ORD-20260831-000123",
  "payment_id": "PAY-01J6N8F7KVH0TPN93A5X4E2RQC",
  "transaction_id": "TXN-01J6N8FARJ4Y1E7X6NW9M2P3DK",
  "status": "processing"
}

收到支付服务商的异步回调后,不能只根据前端跳转结果判断支付是否成功。服务端应:

  1. 验证回调签名;
  2. 按支付服务商和外部 transaction ID 去重;
  3. 核对商户号、订单号、金额和币种;
  4. 以幂等方式更新 transaction、payment 和 order 的状态;
  5. 保存原始回调或必要的审计信息;
  6. 必要时调用支付服务商的查询接口确认状态。

更新状态时可以加入条件,防止重复回调触发重复履约:

UPDATE payments
SET status = 'succeeded'
WHERE payment_number = :payment_number
  AND status IN ('pending', 'processing');

只有第一次成功更新状态的处理流程,才应继续触发发货、充值或开通服务。

支付完成后向客户展示什么

支付结果页应优先展示客户容易理解、客服能够查询的信息:

  • 支付状态,如成功、处理中或失败;
  • order number;
  • 支付金额和币种;
  • 支付时间;
  • 支付方式的非敏感描述,例如 Visa •••• 4242;
  • 收据或发票入口;
  • 客服联系方式;
  • 必要时展示面向客户的 payment reference。

示例:

支付成功

订单号:ORD-20260831-000123
支付金额:CNY 500.00
支付方式:Visa •••• 4242
支付时间:2026-08-31 14:32:18
支付参考号:PAY-01J6N8F7KVH0TPN93A5X4E2RQC

结果页通常不需要显示数据库主键或所有内部 transaction ID。如果客服、银行或支付服务商需要客户提供交易参考号,可以展示一个稳定、不可预测且不含敏感信息的公开编号。

如果支付状态还没有最终确认,应显示“处理中”,不能提前显示“支付成功”:

付款请求已提交,正在确认支付结果。
请勿重复付款,可稍后通过订单号查询状态。

安全和实现注意事项

  • 不要在任何编号中加入卡号、CVV、支付令牌、签名密钥或完整的银行账户信息。
  • 公开编号应不可预测。否则,查询接口必须严格鉴权,防止有人枚举编号并查看他人的订单。
  • 不要把 order number 当作授权凭证。知道订单号并不代表有权查看订单。
  • 内部主键、公开业务编号和外部渠道编号应分开保存。
  • 不要覆盖失败、撤销和退款记录。应使用追加式交易记录,保留完整的资金轨迹。
  • 退款必须关联原支付或扣款记录,不能只关联订单。
  • 订单状态和支付状态应分开。例如,订单可能已经支付但尚未发货,也可能已经取消但退款仍在处理中。
  • 对账时应同时保存金额、币种、渠道、商户账户、发生时间和外部参考号,不能只依赖一个 transaction ID。
  • 支付结果必须以服务端验证后的状态为准,不能信任浏览器传回的 success=true。
  • 日志中的支付工具信息和个人数据需要脱敏,并按适用法规设置保存期限。

核心原则是:订单描述商业行为,交易记录资金操作,发票记录财务凭证。 三者可以相互关联,但生命周期和唯一性范围不同,所以应使用各自独立的标识。

备注:内容仅供参考。