礼品卡 API
礼品卡购买、验证与核销接口(用户端)。此为可插拔功能模块。
路由前缀:/gift-cards
源码:apps/backend/src/routes/gift-cards.ts
认证
所有接口需要 JWT 认证:
Authorization: Bearer <JWT>端点
GET /gift-cards/products
获取可购买的礼品卡商品列表。
响应 200 OK:
json
{
"success": true,
"data": [
{
"sku": "gift-100",
"credits": 100,
"priceCents": 1000,
"currency": "CNY",
"title": "100 积分礼品卡",
"validityDays": 365
}
]
}| 字段 | 类型 | 说明 |
|---|---|---|
sku | string | 商品唯一标识(购买时回传) |
credits | number | 礼品卡面值积分数 |
priceCents | number | 售价(分) |
currency | string | 币种,如 CNY |
title | string | 商品标题 |
validityDays | number | 兑换有效期(天) |
错误码:
| HTTP | 错误 | 说明 |
|---|---|---|
| 500 | INTERNAL_ERROR / 操作失败,请稍后重试 | 服务端错误 |
POST /gift-cards/purchase
创建礼品卡订单并发起微信 Native 支付,返回支付二维码与订单信息。
请求体:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
sku | string | 是 | 商品标识(来自 GET /gift-cards/products) |
teamId | string | 是 | 下单团队 ID(用户须为该团队成员) |
returnUrl | string | 否 | 支付完成后的跳转地址 |
响应 200 OK:
json
{
"success": true,
"data": {
"orderId": "uuid",
"orderNo": "GC202606220001",
"status": "pending",
"amount": 1000,
"currency": "CNY",
"product": {
"sku": "gift-100",
"credits": 100,
"priceCents": 1000,
"currency": "CNY",
"title": "100 积分礼品卡",
"validityDays": 365
},
"providerOrderId": "wx-prepay-id",
"payUrl": "weixin://wxpay/bizpayurl?pr=xxxx",
"qrCode": "weixin://wxpay/bizpayurl?pr=xxxx",
"qrCodeUrl": "https://.../qrcode.png",
"checkoutToken": "token",
"expiresAt": "2026-06-22T12:00:00.000Z",
"orderExpiresAt": "2026-06-22T12:30:00.000Z"
}
}| 字段 | 类型 | 说明 |
|---|---|---|
orderId | string | 订单 ID |
orderNo | string | 订单号 |
status | string | 订单状态(创建后为 pending) |
amount | number | 支付金额(分) |
currency | string | 币种 |
product | object | 商品信息(结构同 GET /gift-cards/products 单项) |
providerOrderId | string | null | 支付渠道侧订单/预支付 ID |
payUrl | string | null | 微信支付拉起链接 |
qrCode | string | null | 支付二维码内容(weixin:// 协议串) |
qrCodeUrl | string | null | 支付二维码图片 URL |
checkoutToken | string | null | 收银台令牌 |
expiresAt | string | null | 支付链接过期时间(ISO 8601) |
orderExpiresAt | string | 订单过期时间(ISO 8601) |
支付方式固定为微信(
provider: wechat,Native 扫码支付)。微信 Native 支付主要返回providerOrderId/qrCode/expiresAt,上表中标string | null的其余字段在当前渠道下可能为null或在响应里省略。
错误码:
| HTTP | 错误 code | 说明 |
|---|---|---|
| 400 | MISSING_SKU | 缺少 sku 参数 |
| 400 | MISSING_TEAM_ID | 缺少 teamId 参数 |
| 401 | UNAUTHORIZED | 未认证 |
| 403 | NOT_TEAM_MEMBER | 用户不属于该团队 |
| 4xx | 支付域错误码 | 支付网关返回的业务错误(HTTP 状态由错误本身决定) |
| 500 | INTERNAL_ERROR | 服务端错误 |
GET /gift-cards/mine
获取本人购买的礼品卡列表(仅 source_type = purchase 的卡,按创建时间倒序)。
响应 200 OK:
json
{
"success": true,
"data": [
{
"id": "uuid",
"code": "GIFT-XXXX-XXXX",
"codeLast4": "XXXX",
"credits": 100,
"status": "active",
"expiresAt": "2027-06-22T00:00:00.000Z",
"orderId": "uuid",
"createdAt": "2026-06-22T08:00:00.000Z",
"redeemedAt": null
}
]
}| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 礼品卡 ID |
code | string | null | 完整卡码;已核销(status = redeemed)时返回 null |
codeLast4 | string | 卡码后 4 位,始终返回(便于已核销卡的识别) |
credits | number | 面值积分数(由毫积分换算) |
status | string | 卡状态,如 active / redeemed / expired |
expiresAt | string | null | 过期时间(ISO 8601) |
orderId | string | null | 关联订单 ID |
createdAt | string | 创建时间(ISO 8601) |
redeemedAt | string | null | 核销时间(未核销为 null) |
错误码:
| HTTP | 错误 code | 说明 |
|---|---|---|
| 401 | UNAUTHORIZED | 未认证 |
| 500 | INTERNAL_ERROR | 服务端错误 |
验证礼品卡码
GET /gift-cards/validate/:code验证礼品卡码是否有效,返回礼品卡信息(不消耗)。
路径参数:
| 参数 | 说明 |
|---|---|
code | 礼品卡码 |
响应 200 OK(有效):
json
{
"valid": true,
"credits": 100,
"expiresAt": "2026-06-01T00:00:00.000Z"
}响应 200 OK(无效):
json
{
"valid": false,
"error": "礼品卡已被使用"
}错误码:
| HTTP | 错误 | 说明 |
|---|---|---|
| 400 | 请输入礼品卡码 | 未提供 code |
| 500 | 验证失败 | 服务端错误 |
核销礼品卡
POST /gift-cards/redeem核销礼品卡,将积分充值到用户所在团队。
原子性与幂等(2026-06-10,issue #692):核销在数据库单事务 RPC
redeem_gift_card_atomic内完成 「锁卡行 → 状态翻转 → 幂等流水(幂等键gift-card-<卡id>)→ 余额增量」。并发兑换同一张卡只会成功一次, 输家收到礼品卡已被使用;重复请求不会重复发放积分;过期卡实时拒绝并自动置为expired。
请求体:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | 礼品卡码 |
teamId | string | 否 | 指定充值到的团队 ID(需为该团队成员或 owner) |
响应 200 OK:
json
{
"success": true,
"creditsGranted": 100,
"message": "成功兑换 100 积分"
}错误码:
| HTTP | 错误 | 说明 |
|---|---|---|
| 400 | 请输入礼品卡码 | 未提供 code |
| 400 | 核销失败信息 | 礼品卡已使用、已过期等 |
| 401 | Unauthorized | 未认证 |
| 403 | 无权为该团队兑换礼品卡 | 指定了 teamId 但无权限 |
| 500 | 核销失败 | 服务端错误 |