Corda支付场景下更新Invoice State和Payment State的Flow最佳实践咨询
Corda 支付场景状态更新Flow最佳实践解答
结论
最佳实践为方案2:仅创建单个updatePaymentAndInvoiceFlow,在同一Flow内同时更新Payment State和所有关联Invoice State
方案选型理由
为什么不推荐方案1
- 存在数据一致性风险:方案1拆分为多个独立Flow执行,一旦中间环节出现异常(如网络中断、某节点拒绝签名),会出现Payment State已标记为
PAID但部分关联Invoice State仍为未支付的状态不一致问题,严重违反业务逻辑。 - 额外性能损耗:多轮Flow会触发多次签名协商、公证校验流程,节点IO和通信开销远高于单次交易。
- 语义不匹配:「支付完成后批量更新所有关联发票状态」是一个不可拆分的原子业务操作,拆分为多个Flow会提升代码维护成本。
方案2的优势
- 原子性保障:Corda交易天生具备ACID特性,单次交易内的所有状态更新要么全部生效要么全部回滚,从底层避免数据不一致问题。
- 执行效率更高:仅需一次签名收集、一次公证校验,流程耗时更短。
- 可维护性更强:单个Flow对应完整业务逻辑,代码语义清晰,后续迭代调整成本更低。
核心实现示例
// 定义交易命令,标记本次为支付+发票批量更新操作 class UpdatePaymentAndInvoiceCommand : TypeOnlyCommandData() /** * 发起方Flow:入参为付款批次ID */ @InitiatingFlow @StartableByRPC class UpdatePaymentAndInvoiceFlow(private val paymentBatchId: String) : FlowLogic<SignedTransaction>() { override fun call(): SignedTransaction { val notary = serviceHub.networkMapCache.notaryIdentities.first() // 1. 查找对应付款批次的Payment State输入 val paymentStateAndRef = serviceHub.vaultService.queryBy<PaymentState>() .states.single { it.state.data.batchId == paymentBatchId } val inputPayment = paymentStateAndRef.state.data check(inputPayment.status != "PAID") { "该付款批次已完成支付" } // 2. 查找该批次关联的所有Invoice State输入 val invoiceStateAndRefs = serviceHub.vaultService.queryBy<InvoiceState>() .states.filter { it.state.data.relatedPaymentBatchId == paymentBatchId } check(invoiceStateAndRefs.isNotEmpty()) { "该付款批次未关联任何发票" } // 3. 构建更新后的输出状态 val paidPayment = inputPayment.copy(status = "PAID") val paidInvoices = invoiceStateAndRefs.map { it.state.data.copy(status = "PAID") } // 4. 组装交易 val txBuilder = TransactionBuilder(notary) .addInputState(paymentStateAndRef) .apply { invoiceStateAndRefs.forEach { addInputState(it) } } .addOutputState(paidPayment, PaymentContract.ID) .apply { paidInvoices.forEach { addOutputState(it, InvoiceContract.ID) } } .addCommand( UpdatePaymentAndInvoiceCommand(), // 收集所有需要签名的参与方公钥,按需调整 (paidPayment.participants + paidInvoices.flatMap { it.participants }).distinct().map { it.owningKey } ) // 5. 标准交易流程:验证->自签->收集对方签名->公证->最终落地 txBuilder.verify(serviceHub) val selfSignedTx = serviceHub.signInitialTransaction(txBuilder) val otherSingers = (paidPayment.participants + paidInvoices.flatMap { it.participants }).distinct() - ourIdentity val sessions = otherSingers.map { initiateFlow(it) } val fullySignedTx = subFlow(CollectSignaturesFlow(selfSignedTx, sessions)) return subFlow(FinalityFlow(fullySignedTx, sessions)) } } /** * 响应方Flow */ @InitiatedBy(UpdatePaymentAndInvoiceFlow::class) class UpdatePaymentAndInvoiceResponderFlow(private val counterpartySession: FlowSession) : FlowLogic<SignedTransaction>() { override fun call(): SignedTransaction { val signedTx = subFlow(object : SignTransactionFlow(counterpartySession) { override fun checkTransaction(stx: SignedTransaction) = requireThat { // 自定义交易校验规则,按需扩展 val outputPayment = stx.coreTransaction.outputsOfType<PaymentState>().single() val outputInvoices = stx.coreTransaction.outputsOfType<InvoiceState>() "Payment状态必须更新为PAID" using (outputPayment.status == "PAID") "所有关联发票状态必须更新为PAID" using (outputInvoices.all { it.status == "PAID" }) "发票关联付款批次匹配" using (outputInvoices.all { it.relatedPaymentBatchId == outputPayment.batchId }) } }) return subFlow(ReceiveFinalityFlow(counterpartySession, expectedTxId = signedTx.id)) } }
内容的提问来源于stack exchange,提问作者Ariya Kluankloi
相关产品推荐
相关产品推荐

