@@ -24,6 +24,7 @@ use lightning::events::{
2424} ;
2525use lightning:: ln:: channelmanager:: { PaymentId , TrustedChannelFeatures } ;
2626use lightning:: ln:: types:: ChannelId ;
27+ use lightning:: offers:: offer:: Offer ;
2728use lightning:: routing:: gossip:: NodeId ;
2829use lightning:: sign:: EntropySource ;
2930use lightning:: util:: config:: { ChannelConfigOverrides , ChannelConfigUpdate } ;
@@ -36,8 +37,9 @@ use lightning_types::payment::{PaymentHash, PaymentPreimage};
3637
3738use crate :: config:: { may_announce_channel, Config , PEER_RECONNECTION_INTERVAL } ;
3839use crate :: connection:: ConnectionManager ;
39- use crate :: data_store:: DataStoreUpdateResult ;
40+ use crate :: data_store:: { DataStoreUpdateResult , StorableObject } ;
4041use crate :: fee_estimator:: ConfirmationTarget ;
42+ use crate :: ffi:: Bolt12Invoice ;
4143#[ cfg( feature = "uniffi" ) ]
4244use crate :: ffi:: PaidBolt12Invoice ;
4345use crate :: io:: {
@@ -48,6 +50,7 @@ use crate::liquidity::LiquiditySource;
4850use crate :: logger:: { log_debug, log_error, log_info, log_trace, LdkLogger , Logger } ;
4951use crate :: payment:: asynchronous:: om_mailbox:: OnionMessageMailbox ;
5052use crate :: payment:: asynchronous:: static_invoice_store:: StaticInvoiceStore ;
53+ use crate :: payment:: recurrence:: RecurrenceStatus ;
5154use crate :: payment:: store:: {
5255 PaymentDetails , PaymentDetailsUpdate , PaymentDirection , PaymentKind , PaymentStatus ,
5356} ;
@@ -694,6 +697,82 @@ where
694697 } )
695698 }
696699
700+ /// Processes a successful payment event for an outbound recurrence.
701+ async fn advance_recurrence_on_payment_sent (
702+ & self , payment_id : PaymentId , invoice : & Bolt12Invoice ,
703+ ) -> Result < ( ) , ReplayEvent > {
704+ let mut matches = self
705+ . recurrence_store
706+ . list_filter ( |details| details. active_payment_id == Some ( payment_id) )
707+ . await
708+ . into_iter ( ) ;
709+
710+ let Some ( mut details) = matches. next ( ) else {
711+ // Ordinary payments do not have recurrence state and need no extra handling.
712+ return Ok ( ( ) ) ;
713+ } ;
714+
715+ debug_assert ! (
716+ matches. next( ) . is_none( ) ,
717+ "multiple recurrences have the same active payment ID?"
718+ ) ;
719+
720+ if details. last_successful_payment_id == Some ( payment_id) {
721+ // Event replay after a completed transition must be idempotent.
722+ return Ok ( ( ) ) ;
723+ }
724+
725+ let offer = Offer :: try_from ( details. original_offer . clone ( ) ) . map_err ( |_| ReplayEvent ( ) ) ?;
726+
727+ if invoice. offer_id ( ) != Some ( offer. id ( ) ) {
728+ debug_assert ! ( false , "invoice offer ID does not match recurrence offer ID" ) ;
729+ return Ok ( ( ) ) ;
730+ }
731+
732+ let Some ( invoice_recurrence) = invoice. invoice_recurrence ( ) else {
733+ debug_assert ! ( false , "Invoice that should be corresponding to recurrence according to our recurrence store doesn't?" ) ;
734+ return Ok ( ( ) ) ;
735+ } ;
736+
737+ if let Some ( recurrence_basetime) = details. basetime {
738+ if recurrence_basetime != invoice_recurrence. recurrence_basetime ( ) {
739+ debug_assert ! ( false , "Recurrence basetime info mismatch" ) ;
740+ return Ok ( ( ) ) ;
741+ }
742+ }
743+
744+ details. paid_count = details. paid_count . saturating_add ( 1 ) ;
745+ details. basetime . get_or_insert ( invoice_recurrence. recurrence_basetime ( ) ) ;
746+ details. opaque_state =
747+ invoice_recurrence. recurrence_next_state ( ) . map ( |state| state. to_vec ( ) ) ;
748+ details. last_successful_payment_id = Some ( payment_id) ;
749+ details. active_payment_id = None ;
750+
751+ let recurrence = offer. offer_recurrence ( ) . ok_or ( ReplayEvent ( ) ) ?;
752+ let period_index = recurrence
753+ . period_index ( details. paid_count as u32 , details. initial_start )
754+ . map_err ( |_| ReplayEvent ( ) ) ?;
755+
756+ if recurrence. recurrence_limit . map ( |limit| period_index >= limit. 0 ) . unwrap_or ( false ) {
757+ details. status = RecurrenceStatus :: Completed ;
758+ }
759+
760+ match self . recurrence_store . update ( details. to_update ( ) ) . await {
761+ Ok ( DataStoreUpdateResult :: Updated | DataStoreUpdateResult :: Unchanged ) => Ok ( ( ) ) ,
762+ Ok ( DataStoreUpdateResult :: NotFound ) => {
763+ log_error ! (
764+ self . logger,
765+ "Recurrence disappeared before its payment update was stored"
766+ ) ;
767+ Err ( ReplayEvent ( ) )
768+ } ,
769+ Err ( e) => {
770+ log_error ! ( self . logger, "Failed to store updated recurrence: {}" , e) ;
771+ Err ( ReplayEvent ( ) )
772+ } ,
773+ }
774+ }
775+
697776 async fn resolve_inbound_payment_id (
698777 & self , event_payment_id : Option < PaymentId > , payment_hash : & PaymentHash ,
699778 ) -> Result < ( PaymentId , Option < PaymentDetails > ) , ReplayEvent > {
@@ -1413,6 +1492,16 @@ where
14131492 ..PaymentDetailsUpdate :: new ( payment_id)
14141493 } ;
14151494
1495+ if let Some ( invoice) = bolt12_invoice
1496+ . as_ref ( )
1497+ . and_then ( |paid_invoice| paid_invoice. bolt12_invoice ( ) )
1498+ {
1499+ if let Err ( e) = self . advance_recurrence_on_payment_sent ( payment_id, invoice. as_ref ( ) ) . await {
1500+ log_error ! ( self . logger, "Failed to advance recurrence: replaying event" ) ;
1501+ return Err ( e) ;
1502+ }
1503+ }
1504+
14161505 match self . payment_store . update ( update) . await {
14171506 Ok ( _) => { } ,
14181507 Err ( e) => {
0 commit comments