IUPBCP 登录、账户与账单 Clean-room 协议包

IUPBCP 登录、账户与账单 Clean-room 协议包

本目录是一套 clean-room、厂商中立(vendor-neutral) 的开发契约,用于演示印度 UPI 与 Bharat Connect 风格的登录、账户、账单、充值和售后聚合。它不是 Paytm 私有协议,也不是 Paytm 或任何机构的生产 API;示例不包含真实端点、真实密钥、真实签名、私有签名算法或任何认证/风控绕过方式。本文中的标识符、凭据和证明都只能指向隔离的本地 Mock。

协议包不代表已取得 NPCI、PSP、TPAP、Bharat Connect、合作银行或监管机构的接入资格,也不能据此处理真实资金。

文件职责

文件职责
openapi.yaml同步 HTTP API 的路径、operationId、安全方案、请求/响应、错误与 JSON Schema 真源
asyncapi.yaml聚合状态事件、事件 envelope、channel 与消费者接口真源
state-machines.yaml聚合初态、终态、合法迁移、守卫与父子支付映射
examples/requests.http覆盖全部 86 个 operationId 的安全 REST Client 示例及变量传递
scripts/validate_contracts.rb跨文件引用、状态、安全字段与示例的静态验证

Quick start

以下命令只面向本地开发。不同工具对 OpenAPI 3.1、JSON Schema 2020-12 和 AsyncAPI 3.0 的支持不同,应在团队里固定工具版本并把生成差异纳入评审。

  1. 先做仓库内语义校验:

    ruby protocols/paytm-cleanroom/scripts/validate_contracts.rb
    
  2. 用支持 OpenAPI 3.1 的 linter 校验 openapi.yaml,用支持 AsyncAPI 3.0 的 linter 校验 asyncapi.yaml。例如可在已固定依赖的项目中执行:

    npx redocly lint protocols/paytm-cleanroom/openapi.yaml
    npx asyncapi validate protocols/paytm-cleanroom/asyncapi.yaml
    
  3. 从 OpenAPI 3.1 生成类型化客户端或服务端接口;生成物不要反向覆盖契约:

    openapi-generator-cli generate -i protocols/paytm-cleanroom/openapi.yaml -g typescript-fetch -o build/generated/client
    openapi-generator-cli generate -i protocols/paytm-cleanroom/openapi.yaml -g spring -o build/generated/server
    
  4. 从 AsyncAPI 3.0 生成事件消费者骨架,再按 aggregateVersion 规则实现 inbox:

    asyncapi generate fromTemplate protocols/paytm-cleanroom/asyncapi.yaml <pinned-template> -o build/generated/events
    
  5. 启动由 schema 驱动的本地 Mock,并把 baseUrl 配置为该 Mock 的 /v1 地址。Mock 必须无真实支付能力,绝不能连接真实资金轨、银行、PSP 或账单网络。schema mock 只用于契约测试,不可用于资金验证。

  6. 在 REST Client 中打开 examples/requests.http,配置文档保留值和本地文件变量,按章节执行。不要把示例凭据写入源码、日志或测试快照。

内置 validator 使用 File.read 配合 YAML.safe_load(..., aliases: true)
已在 Ruby 2.6.10 / Psych 3.1 上验证。CI 仍应同时运行当前受支持的
Ruby 版本;任何兼容性失败都不得通过跳过 OpenAPI、AsyncAPI 或状态机校验来规避。

推荐实现顺序

  1. 固定公共类型、错误 envelope、请求追踪、Idempotency-Key、ETag/If-Match 和鉴权中间件。
  2. 实现身份、设备登记、标准 OTP、注册与会话;随后实现 step-up、换号、恢复和账户关闭。
  3. 实现客户、consent、KYC、证据上传、银行发现/绑定、VPA 和余额授权。
  4. 实现账单方目录、runtime schema、账单快照、quote、充值目录与 quote。
  5. 实现 UPI AuthorizationSession、唯一子 PaymentOrder、父级 Bill/Recharge saga、receipt、退款和争议。
  6. 最后接入 outbox/inbox、AsyncAPI 消费者、对账、审计、可观测性、灾备和全链路契约测试。

每一步都先实现 state-machines.yaml 的迁移守卫和拒绝路径,再开放对应 HTTP 写操作。

服务边界、聚合与存储

建议按 Identity/Device、Customer/KYC、Account/VPA、Bill/Recharge、UPI Orchestrator、Refund/Dispute、Reconciliation 与 Audit 分界。每个聚合独占自己的状态和版本;跨聚合只传不透明 ID、冻结快照或事件,不共享可写表。

  • Identity 持有 enrollment、OTPChallenge、Session、step-up、换号和恢复;OTP 原文只在验证边界短暂处理。
  • Account 持有 Customer、Consent、KycCase、BankAccountLink 与 VPA;银行账号只存结构化掩码显示。
  • Bill/Recharge 持有业务快照、quote、父 saga、履约/结算/退款维度,但不能写 UPI 表、创建网络尝试或接触银行授权秘密。
  • UPI Orchestrator 独占 AuthorizationSession 与 PaymentOrder,并以 (aggregateId, aggregateVersion) 发布事件。
  • Evidence 存元数据、内容摘要、扫描状态与受控对象引用;普通 bearer 域和 recovery 域必须物理或逻辑隔离,不能跨域复用 uploadId。
  • Reconciliation 读取不可变流水、外部确认和 outbox,不把超时直接写成失败。

存储层建议使用聚合版本的条件更新、事务 outbox、消费者 inbox、不可变业务快照和受限审计索引。不要以手机号、VPA、账号或 recovery ID 作为可枚举主键或 bearer credential。

登录、注册与会话 ETag

登录顺序是 device enrollment → OTP challenge → 标准 OTP verification → registration 或 session。OTP challenge 响应永不回传验证码;登录验证只返回一次性 authenticationGrant。注册和既有客户创建会话是互斥分支,同一 grant 不得消费两次。

SessionBundle.sessionEtagGET /sessions/current 的 ETag 只用于 refresh/撤销当前会话;GET /sessions/{sessionId} 的 ETag 只用于该命名会话;GET /sessions 的 collection ETag 只用于 revoke-all。不要混用这三类版本。

refresh 仍是公开鉴权交换,但必须同时提供原 refresh credential、设备证明、幂等键和当前会话 ETag。凭据轮换后立即使旧值失效;日志只记录凭据指纹或安全分类,不记录值。

账户维护、银行绑定、VPA 与余额

客户偏好 PATCH、VPA PATCH、银行绑定 action、KYC action、账户关闭 action、step-up 验证和换号 proof attachment 都要求使用刚读取的 ETag。换号需分别验证旧侧和新侧标准 OTP,并逐次采用响应中的新 ETag;cooling-off 不是可跳过的客户端计时器。

银行流程是:银行目录 → 账户发现 POST/GET 轮询 → ACCOUNT_BINDING AuthorizationSession POST/GET → 使用一次性的 authorizationSessionRef 创建账户绑定 → 读取掩码账户 → 创建/读取/更新 VPA。发现中的 UNKNOWN 不是“无账户”。

余额流程必须先创建并轮询 BALANCE_INQUIRY AuthorizationSession,再 POST balance inquiry,并用 GET /upi/balance-inquiries/{balanceInquiryId} 轮询。只有 SUCCEEDED 才读取 balance;余额来源是发行银行的短期视图,不是本地账本余额。

AuthorizationSession 在 ACTION_REQUIRED 时只返回启动受批准组件(approved component)的短期 launch credential。公共 API 绝不接收、记录或转发 UPI PIN;UPI PIN 只能在经批准的银行/PSP 组件内采集和处理,也不能出现在日志、事件、证据或 Mock fixture。

账单、充值与唯一子 Payment saga

先读取 biller,再读取 /billers/{billerId}/parameter-schema。Biller 参数完全由返回的 runtime schema(运行时 schema) 驱动;customerRef 只是示例字段,客户端不得为所有 biller 硬编码它,也不得提交运行时 schema 未声明的字段。

BillFetch 成功后产生不可变、会过期的 BillSnapshot。quote 必须绑定该快照与 funding instrument;BillOrder 又必须绑定同一快照和 quote。金额全部是 INR minor-unit 十进制字符串,例如 {"amountMinor":"100","currency":"INR"},禁止浮点金额、主单位推断或其他币种。

BillOrder 和 RechargeOrder 都先进入 PAYMENT_PROVISIONING,此时 paymentId 必须为 null 且不得发起资金网络调用。轮询至 AUTH_REQUIRED 后,父聚合恰好出现一个不可变 child PaymentOrder。客户端按父级 nextAction 创建/轮询 AuthorizationSession,然后 GET child 取得 ETag,再以 SUBMIT 和授权引用修改这个 child。不得创建替代 paymentId;父服务只消费 child 状态事件。

充值同样执行 operator → plan → subscriber validation → quote → order → provisioning poll → authorization → child GET/SUBMIT → order/receipt poll。subscriber validation 返回的是短期不透明 token,不应长期存储原始订户标识。

Receipt 是账单/充值聚合的投影,不是银行对账单,也不证明尚未完成的 fulfilment 或 settlement 已成功。展示层必须同时显示 payment、fulfilment、settlement 和 refund 状态。

证据、退款、争议与恢复

普通 KYC/争议证据链是 create upload → PUT .../content → completion → 各自 GET 轮询到 READY → attach;recovery 域也必须在 completion 后用同一 recovery request 下的专用 GET 轮询到 READY 才能 submit。普通 create body 必须显式携带 target: ResourceRef:KYC 使用 KYC_CASE 与后续路径中的同一 kycCaseId,争议使用 DISPUTE 与后续路径中的同一 disputeId。创建时冻结 purpose、target、media type、字节数、主体、租户、凭据域与 creator context;消费证据时服务端以存储的冻结绑定校验 READY、purpose、target 与调用上下文,不能信任客户端重报。PUT 的 Content-Digest 和 completion 的 contentDigest 必须完全相同。

退款是独立聚合:以原始业务资源、业务幂等引用、INR 金额和原因创建,随后轮询 Refund。争议是独立 case:create → GET → 普通域证据上传/扫描 → evidence attach → 可选 escalation。退款成功不能重写原 PaymentOrder 的历史事实。

恢复严格 non-enumeration:请求是否命中客户都返回统一公开投影。完整顺序是新设备 enroll → recovery request → public GET → recovery challenge → 取得 otpChallengeId → 调用标准 OTP verification 得到 verificationId → attach recovery verification → 交换 recovery grant → restricted recovery session。

若要求证据,必须使用专用 recovery evidence 域:create → PUT content → completion → submit。所有步骤都绑定同一 recovery request、session、主体、租户和 recovery credential audience,不能调用普通 /evidence-uploads 代替。最后仅在状态允许时执行 CONFIRM_RESTORE;公开 GET、recovery ID 或 challenge ID 本身都不是授权。

幂等、并发、精确重放与对账

所有写请求都带调用方生成的 Idempotency-Key,其作用域是 method + canonical path。相同键只允许精确重放:规范化 body、目标资源、认证主体、媒体类型、文件字节和关键头必须相同,服务返回原结果;同键不同表示必须冲突。对同一业务意图不要在超时后换新键制造第二个支付。

要求 If-Match 的写操作只能使用紧邻目标资源的最新 ETag;412/STATE_CONFLICT 后重新 GET、重新评估状态,再决定是否重试。绝不能盲目覆盖或把父聚合 ETag 用于 child PaymentOrder。

PENDINGUNKNOWNNEEDS_RECONCILIATION 都是对账中的不确定状态,不是失败、成功、可安全重付或可安全履约信号。保持同一 operation/child,按 nextStatusCheckAt 或退避策略轮询,并把最终判定交给 reconciliation。只有 safeToCreateNewOperation:true 且业务策略明确允许时,才可创建新操作。

事件、错误、重试、审计与隐私

错误处理、重试、审计、隐私必须作为同一条端到端安全链路设计和验收。

事件消费必须幂等:inbox 以 (event.id, consumer) 去重,以 (aggregateId, aggregateVersion) 拒绝旧事件、重复事件和乱序覆盖。父 Bill/Recharge 只接受唯一 child 的版本单调事件;outbox 发布和聚合提交同事务。

错误处理以 error.code/category/retryable/safeToCreateNewOperation/correlationId 为准,HTTP 状态只作传输分类。仅对 retryable:true 做带抖动的指数退避;不确定资金结果即使网络可重试,也不能创建替代业务操作。保留服务端 X-Request-Id/Trace-Id 以便审计,但不要把凭据、OTP、设备证明、手机号、VPA、账号或证据内容放进日志、指标标签和异常消息。

审计记录应追加写、最小化、按角色隔离并设置留存/删除策略;隐私实现需支持目的限制、数据最小化、访问审批、加密、删除/法定保留冲突处理和可追溯导出。应用日志与审计日志不要共用宽权限存储。

测试矩阵

维度必测内容
契约YAML 解析、本地$ref、86 个 operationId 唯一且示例全覆盖、请求/响应 schema、OpenAPI/AsyncAPI lint
安全public/bearer/recoveryAuth 隔离;OTP 作用域;敏感字段、真实端点、完整账号和凭据泄漏扫描
幂等首次写、精确重放、同键异 body/主体/路径/文件、过期上传、并发重复提交
并发正确/陈旧/跨资源 ETag;换号双侧、KYC、closure、payment、dispute 与 recovery 状态冲突
Sagaprovisioning 唯一 child、授权过期、提交前取消、提交中断、父子乱序、无替代支付
不确定性PENDING、UNKNOWN、NEEDS_RECONCILIATION、对账恢复、权威失败证据、退款补偿
Evidence普通域/recovery 域隔离、摘要/长度/media type 一致、恶意文件、扫描未 READY 禁止 attach
事件event.id 去重、aggregateVersion 单调、乱序/重复/缺口、outbox/inbox 崩溃恢复
隐私与运行non-enumeration、日志脱敏、权限、限流、审计留存、备份恢复与灾备演练

如何查阅 86 个操作

openapi.yamloperationId 是稳定索引;路径和 method 才是实际调用地址。列出全部 86 个操作:

ruby -ryaml -e 'o=YAML.safe_load(File.read("protocols/paytm-cleanroom/openapi.yaml"), [], [], true); o["paths"].each { |p, i| i.each { |m, op| puts "#{op["operationId"]}\t#{m.upcase}\t#{p}" if op.is_a?(Hash) && op["operationId"] } }'

examples/requests.http 中搜索 # @operationId <名称> 可定位安全请求。一个 operationId 可能出现多次,以展示不同状态分支或前序变量;覆盖统计按唯一 operationId 计算。

生产接入前置条件

本协议只给出通用合规提示,不构成法律意见,也不声称任何主体已获资质。生产前至少需要:

生产控制面应同时覆盖 HSM、数据保护、风控、审计、灾备,且均需形成可验证证据。

  • 由合格法务与合规团队确认适用的 RBI/NPCI、支付、KYC/AML、Bharat Connect、消费者保护、争议/退款、数据本地化与数据保护要求;
  • 与 NPCI、PSP、TPAP、Bharat Connect 参与方及合作银行分别完成适用的准入、合同、认证、联调、沙盒和生产审批;
  • 建立机构级身份、证书生命周期、密钥托管与轮换、HSM、双人控制、职责分离和紧急吊销;
  • 完成数据分类、加密、数据保护影响评估、目的限制、保留删除、跨境/本地化评估与主体权利流程;
  • 上线交易风控、欺诈/账户接管防控、AML/制裁筛查、限额、人工复核、投诉与事件响应;
  • 通过代码、基础设施、供应链、渗透、移动端、API、审计和监管要求的独立评估;
  • 验证容量、可用性、对账、不可变审计、监控告警、备份恢复、RTO/RPO、灾备切换与定期演练。

上述前置条件必须由真实合作方和适用主管机构确认;本地 Mock、代码生成成功或契约测试通过均不能替代资质、法务、认证和生产安全审批。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小宝哥Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值