跳到内容

卡券

受众:开发者 / 运维 摘要:Voucher 模块提供中性的卡券模板、发放、锁定和核销底座,供 Mall、Meeting、Marketing 等业务模块复用。

spring-open-core-voucher 是通用卡券底座,不绑定商城优惠券、会议兑换券或营销活动单一语义。业务模块通过 consumerCodeownerTypeownerIdbusinessTypebusinessId 和幂等键隔离自己的使用场景。

模块边界

内容
Maven 模块spring-open-core/spring-open-core-voucher
Java 包com.springopen.core.voucher
配置前缀spring.open.voucher
默认开关spring.open.voucher.enabled=true
表前缀voucher_*

卡券底座只保存卡券事实和审计事实,不保存商城订单状态、会议套餐状态、营销活动规则、支付余额或资金流水。业务模块需要发券、锁券或核销时,应调用卡券底座 contract,并在自己的模块里保存业务订单、活动或权益关系。

当前范围

当前版本已落地模块骨架、迁移、模板目录、发放领取、可用券查询、锁定、核销、冲正、释放、过期 contract、Admin 运维入口、当前用户领取 / 我的卡券 API、App / PC 用户券包页面、Open Platform 开放 API、Marketing 抽奖卡券投放接入、Mall 订单结算样板和 Meeting 商业套餐样板:

能力说明
卡券模板voucher_template 保存消费方、模板编码、名称、面值、折扣、门槛、有效期和状态
适用范围voucher_scope 保存模板适用对象,后续用于商品、栏目、会议套餐等场景
发放批次voucher_batch 保存批量发放计划和执行快照
卡券实例voucher_instance 保存用户或业务对象持有的具体卡券
发放记录voucher_grant_record 保存发放尝试、来源和幂等键
锁定记录voucher_lock 保存下单前的卡券预占事实
核销记录voucher_usage 保存最终核销、冲正和优惠金额
操作日志voucher_operation_log 保存模板、实例、锁定和核销的操作审计

Marketing 抽奖奖项类型 coupon 已接入 VoucherGrantService。运营在抽奖奖项 payload 中填写 voucherTemplateId 后,用户中奖会由 Marketing 用 marketing-lottery:{idempotencyKey}:voucher 作为幂等键发放 Voucher,并把抽奖记录派奖状态更新为 granted

Mall 普通订单已作为首个业务消费样板接入 Voucher。结算预览和下单可传 voucherId,Mall 使用 consumerCode=mallownerType=userownerId=当前用户 校验卡券归属和模板,按 Mall 自己的金额规则计算优惠;下单锁定 Voucher,支付成功确认核销,取消订单释放锁定。Mall 订单只保存 voucherIdvoucherLockIdvoucherUsageId 快照,不复制 Voucher 模板、状态或核销事实。

Meeting 商业套餐购买单已作为第二个业务消费样板接入 Voucher。创建套餐购买单可传 voucherId,Meeting 使用 consumerCode=meetingownerType=userownerId=购买人 校验卡券归属和模板,锁定后按优惠后的应付金额创建 Cashier 订单;支付成功或人工结算后确认核销。Meeting 购买单只保存 voucherIdvoucherLockIdvoucherUsageId、原价和优惠金额快照,不复制 Voucher 模板、状态或核销事实。

Admin 管理

后台入口位于 Admin 端 /#/voucher,菜单 seed 写入 0000_00_00_034100_core_voucher_seed_data.xml,默认挂在根菜单“卡券中心”。后台 API 挂载在 /api/v1/admin/voucher/**

API说明权限
GET /api/v1/admin/voucher/templates分页查询卡券模板voucher:template:view
POST /api/v1/admin/voucher/templates新增卡券模板voucher:template:update
PUT /api/v1/admin/voucher/templates/{id}编辑卡券模板voucher:template:update
POST /api/v1/admin/voucher/templates/{id}/status切换模板状态voucher:template:update
GET /api/v1/admin/voucher/instances查询卡券实例voucher:instance:view
GET /api/v1/admin/voucher/grants查询发放记录voucher:instance:view
GET /api/v1/admin/voucher/locks查询锁定记录voucher:redemption:view
GET /api/v1/admin/voucher/usages查询核销记录voucher:redemption:view

Admin 页面只做模板配置和运维查询,不承载业务优惠试算、营销活动策略或订单状态。业务模块接入时仍应在自己的结算流程里调用 Voucher contract。

用户侧 API

用户侧 API 挂载在 /api/v1/vouchers/**,用于 App / PC 或业务前台展示可领取模板、主动领取卡券和查看当前登录用户持有的卡券。领取和“我的卡券”查询使用当前登录用户作为 ownerType=userownerId=当前用户,前端不需要也不应该传入持有人 ID。

API说明
GET /api/v1/vouchers/templates/claimable?consumerCode=mall查询某个消费方当前启用的可领取模板
POST /api/v1/vouchers/templates/{templateId}/claim当前登录用户领取一张模板卡券
GET /api/v1/vouchers/mine分页查询当前登录用户持有的卡券,可按消费方、模板和状态过滤

领取接口请求体可选传入 idempotencyKeyremark。未传 idempotencyKey 时,Voucher 会按当前用户和模板生成稳定幂等键,避免同一用户重复点击领取按钮造成重复发券。业务端如果有自己的领取流水号,也可以传入稳定 idempotencyKey

App 端会员券包和 PC 端用户中心优惠券页已直接接入通用 Voucher API。页面按 consumerCode 切换商城券和会议券,领取时调用模板领取接口,“我的券包”按当前登录用户查询,不再依赖 Mall 专用优惠券接口。

Open Platform API

开放 API 前缀为 /api/v1/open/vouchers,复用 Open Platform 的 AppKey + AppSecret + HMAC 签名、防重放、scope、限流和调用日志。第三方应用应在自己的服务端完成签名,不要把 AppSecret 放进浏览器、小程序或移动端源码。

API说明Scope
POST /api/v1/open/vouchers/available查询某个消费方、持有人当前可用的卡券open:voucher:available
POST /api/v1/open/vouchers/locks在业务结算前锁定一张可用卡券,返回卡券和锁定记录open:voucher:lock
POST /api/v1/open/vouchers/locks/{lockId}/confirm业务成功后确认核销已锁定卡券open:voucher:redeem
POST /api/v1/open/vouchers/usages/{usageId}/reverse业务退款、撤销或履约失败后冲正已核销卡券open:voucher:redeem
POST /api/v1/open/vouchers/locks/{lockId}/release业务取消、失败或超时时释放卡券锁定open:voucher:lock

available 请求体包含 consumerCodeownerTypeownerId 和可选 atlocks 请求体包含 voucherIdconsumerCodeownerTypeownerIdbusinessTypebusinessIdoriginalAmountdiscountAmountexpiresAtidempotencyKeyremark。确认核销请求体包含 consumerCodeidempotencyKeyremark;冲正请求体包含 consumerCodereason;释放锁定请求体包含 consumerCodereason

Open API 使用锁定响应里的 lock.id 作为后续确认核销和释放锁定的路径参数,使用确认核销响应里的 usage.id 作为后续冲正路径参数。写操作建议传入业务侧稳定 idempotencyKey,例如外部订单号、支付单号或履约流水号。冲正按 usage.id 幂等,重复冲正同一核销记录会返回已冲正结果,不会重复回退模板使用量。Voucher 模块只保存卡券和锁定 / 核销 / 冲正事实;应用、密钥、scope 授权、配额、计量和调用日志仍由 Open Platform 统一处理。

Java Contract

VoucherTemplateService 可用于创建模板、读取模板、按消费方查询启用模板和切换模板状态。

VoucherGrantService 可用于从启用模板发放或领取一张卡券实例,并执行可用券过期扫描:

方法说明
grantVoucher(GrantVoucherCommand)校验模板启用状态、有效期、库存和持有人限额;创建 available 实例、发放记录和操作日志
expireAvailableVouchers(int limit)扫描已到期的 available 实例并标记为 expired,单次最多处理 500 条

发放命令建议传入业务侧稳定 idempotencyKey,例如订单号、活动发放记录号或用户领取流水号。相同 consumerCode + idempotencyKey 已存在成功发放记录时,服务会返回原卡券实例,不重复扣减模板库存。

VoucherRedemptionService 提供业务结算期需要的原子能力:

方法说明
listAvailableVouchers(VoucherAvailabilityQuery)查询某个消费方、持有人在指定时间可用的卡券实例
lockVoucher(LockVoucherCommand)把一张 available 卡券锁定到业务单,校验归属、有效期、金额和幂等键
confirmUsage(ConfirmVoucherUsageCommand)把已锁定卡券确认为 used,生成核销记录并累计模板使用量
reverseUsage(ReverseVoucherUsageCommand)把已成功核销记录冲正为 reversed,恢复卡券可用状态并回退模板已核销数量
releaseLock(ReleaseVoucherLockCommand)业务单取消时释放锁定,把卡券恢复为 available
expireLocks(int limit)扫描超时锁定并释放对应卡券,单次最多处理 500 条

Voucher 不负责决定“哪张券最优惠”。Mall、Meeting 或其他业务模块应在自己的结算流程中查询可用券、选择券,再调用锁定与核销 contract。这样可以保持卡券底座简单,并允许不同业务域使用自己的优惠试算规则。

使用建议

业务模块接入时保持三个原则:

  1. 只把通用卡券事实写入 Voucher:模板、实例、锁定、核销和操作日志。
  2. 业务状态留在业务模块:订单、会议套餐、营销活动、支付结果和资金结算都不复制进 Voucher。
  3. 关键写操作带幂等键:发放、锁定和核销应使用业务侧稳定幂等键;冲正应保存并复用核销响应中的 usage.id,避免重复优惠回退。

更多交易型业务模块可以参考 Mall 样板接入;订阅 / 套餐型业务模块可以参考 Meeting 商业套餐样板接入。新增业务场景时优先复用 consumerCode 扩展模板和用户侧筛选,不要为每个业务域再复制一套优惠券状态机。

Released under the Apache License 2.0.