使用serverless调用支付宝支付时如何生成outerOrderNo?
结论
outerOrderNo 是商户侧订单号,应由业务系统自行生成。通常既没有必要,也不应通过支付宝 API 提前获取。
支付宝受理支付请求后,会生成平台交易号,例如 trade_no。outerOrderNo 则用于标识商户自己的订单,二者不能混用。
为什么需要自行生成
商户订单号要在发起支付前生成,用于关联业务订单、支付请求和支付结果。支付宝也会通过该字段识别交易,因此它还用于幂等控制和问题排查。
outerOrderNo 通常需要满足以下要求:
- 在商户系统内保持唯一。
- 创建订单时生成,不要在每次调用支付接口时重新生成。
- 同一订单重试支付时,订单号保持不变。
- 只使用支付接口允许的字符,并遵守字段长度限制。
- 不要直接使用手机号、身份证号等敏感信息。
不同 Serverless 支付能力、SDK 或接口版本对字段长度和字符的限制可能不同,请以对应的接口文档为准。
推荐处理流程
- 用户提交购买请求。
- 业务服务创建本地订单。
- 服务端生成唯一的
outerOrderNo。 - 保存订单号和订单状态。
- 调用支付宝支付能力,并传入该订单号。
- 支付完成后,根据回调或主动查询结果更新本地订单。
订单号不要在客户端生成。客户端提交的编号可能被篡改,也可能因重复点击发生冲突。
Node.js 示例
以下代码使用“时间戳 + 随机值”生成订单号:
import crypto from 'node:crypto';
function generateOuterOrderNo() {
const timestamp = Date.now().toString();
const randomPart = crypto.randomBytes(8).toString('hex');
return `PAY${timestamp}${randomPart}`;
}
const outerOrderNo = generateOuterOrderNo();
console.log(outerOrderNo);
订单号应在创建订单时生成并保存:
async function createOrder(orderRepository, userId, amount) {
const outerOrderNo = generateOuterOrderNo();
const order = {
outerOrderNo,
userId,
amount,
status: 'PENDING'
};
await orderRepository.insert(order);
return order;
}
调用支付能力时,直接使用数据库中保存的订单号:
const order = await orderRepository.findById(orderId);
const paymentParams = {
outerOrderNo: order.outerOrderNo,
totalAmount: order.amount,
subject: '商品订单'
};
// 将 paymentParams 传给实际使用的支付宝 Serverless 支付接口
示例中的 totalAmount 和 subject 只用于说明参数之间的关系。实际参数名称请以当前使用的支付宝 Serverless API 定义为准。
高并发场景的生成方式
只使用时间戳,无法保证订单号在并发环境中绝对唯一。生产系统可以采用以下方式:
- 使用 UUID,并根据接口限制去掉连字符。
- 组合数据库自增 ID 和业务前缀。
- 使用 Snowflake ID 等分布式 ID。
- 组合时间戳、实例标识和随机数。
无论采用哪种方式,都建议为数据库中的 outerOrderNo 字段建立唯一索引,在存储层约束唯一性。
注意事项
支付重试不要更换订单号
首次调用超时并不等于支付失败,此时不要立即生成新的 outerOrderNo。应先查询原订单状态,再决定重试还是关闭订单,否则可能导致重复支付,或让同一个业务订单对应多笔交易。
不要使用纯随机短字符串
随着订单量增加,短随机字符串更容易发生碰撞。即使理论上的碰撞概率不高,也应配合数据库唯一索引使用,并在冲突时重新生成。
区分商户订单号和支付宝交易号
outerOrderNo:由商户系统生成,在发起支付前已经存在。trade_no:由支付宝受理交易后生成,通常可以从支付响应、异步通知或查询结果中获得。
正确的流程是先在自己的 Serverless 服务中创建业务订单并生成 outerOrderNo,再调用支付宝支付接口,而不是调用支付宝 API 预先申请商户订单号。
备注:内容仅供参考。
