@@ -611,28 +611,71 @@ where
611611 } )
612612 }
613613
614+ fn create_refund_builder_intern < ES : Deref , PF , I > (
615+ & self , entropy_source : ES , make_paths : PF , amount_msats : u64 , absolute_expiry : Duration ,
616+ payment_id : PaymentId ,
617+ ) -> Result < RefundBuilder < secp256k1:: All > , Bolt12SemanticError >
618+ where
619+ ES :: Target : EntropySource ,
620+ PF : FnOnce (
621+ PublicKey ,
622+ MessageContext ,
623+ & secp256k1:: Secp256k1 < secp256k1:: All > ,
624+ ) -> Result < I , Bolt12SemanticError > ,
625+ I : IntoIterator < Item = BlindedMessagePath > ,
626+ {
627+ let node_id = self . get_our_node_id ( ) ;
628+ let expanded_key = & self . inbound_payment_key ;
629+ let entropy = & * entropy_source;
630+ let secp_ctx = & self . secp_ctx ;
631+
632+ let nonce = Nonce :: from_entropy_source ( entropy) ;
633+ let context = MessageContext :: Offers ( OffersContext :: OutboundPayment {
634+ payment_id,
635+ nonce,
636+ hmac : None ,
637+ } ) ;
638+
639+ // Create the base builder with common properties
640+ let mut builder = RefundBuilder :: deriving_signing_pubkey (
641+ node_id,
642+ expanded_key,
643+ nonce,
644+ secp_ctx,
645+ amount_msats,
646+ payment_id,
647+ ) ?
648+ . chain_hash ( self . chain_hash )
649+ . absolute_expiry ( absolute_expiry) ;
650+
651+ for path in make_paths ( node_id, context, secp_ctx) ? {
652+ builder = builder. path ( path) ;
653+ }
654+
655+ Ok ( builder. into ( ) )
656+ }
657+
614658 /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the
615659 /// [`OffersMessageFlow`], and any corresponding [`Bolt12Invoice`] received for the refund
616660 /// can be verified using [`Self::verify_bolt12_invoice`].
617661 ///
662+ /// # Privacy
663+ ///
664+ /// Uses the [`OffersMessageFlow`]'s [`MessageRouter`] to construct a [`BlindedMessagePath`]
665+ /// for the offer. See those docs for privacy implications.
666+ ///
618667 /// The builder will have the provided expiration set. Any changes to the expiration on the
619668 /// returned builder will not be honored by [`OffersMessageFlow`]. For non-`std`, the highest seen
620669 /// block time minus two hours is used for the current time when determining if the refund has
621670 /// expired.
622671 ///
623- /// To refund can be revoked by the user prior to receiving the invoice.
672+ /// The refund can be revoked by the user prior to receiving the invoice.
624673 /// If abandoned, or if an invoice is not received before expiration, the payment will fail
625674 /// with an [`Event::PaymentFailed`].
626675 ///
627676 /// If `max_total_routing_fee_msat` is not specified, the default from
628677 /// [`RouteParameters::from_payment_params_and_value`] is applied.
629678 ///
630- /// # Privacy
631- ///
632- /// Uses [`MessageRouter`] to construct a [`BlindedMessagePath`] for the refund based on the given
633- /// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for
634- /// privacy implications.
635- ///
636679 /// Also uses a derived payer id in the refund for payer privacy.
637680 ///
638681 /// # Errors
@@ -651,32 +694,62 @@ where
651694 where
652695 ES :: Target : EntropySource ,
653696 {
654- let node_id = self . get_our_node_id ( ) ;
655- let expanded_key = & self . inbound_payment_key ;
656- let entropy = & * entropy_source;
657- let secp_ctx = & self . secp_ctx ;
658-
659- let nonce = Nonce :: from_entropy_source ( entropy) ;
660- let context = OffersContext :: OutboundPayment { payment_id, nonce, hmac : None } ;
661-
662- let path = self
663- . create_blinded_paths_using_absolute_expiry ( context, Some ( absolute_expiry) , peers)
664- . and_then ( |paths| paths. into_iter ( ) . next ( ) . ok_or ( ( ) ) )
665- . map_err ( |_| Bolt12SemanticError :: MissingPaths ) ?;
666-
667- let builder = RefundBuilder :: deriving_signing_pubkey (
668- node_id,
669- expanded_key,
670- nonce,
671- secp_ctx,
697+ self . create_refund_builder_intern (
698+ & * entropy_source,
699+ |_, context, _| {
700+ self . create_blinded_paths ( peers, context)
701+ . map ( |paths| paths. into_iter ( ) . take ( 1 ) )
702+ . map_err ( |_| Bolt12SemanticError :: MissingPaths )
703+ } ,
672704 amount_msats,
705+ absolute_expiry,
673706 payment_id,
674- ) ?
675- . chain_hash ( self . chain_hash )
676- . absolute_expiry ( absolute_expiry)
677- . path ( path) ;
707+ )
708+ }
678709
679- Ok ( builder)
710+ /// Same as [`Self::create_refund_builder`] but allows specifying a custom [`MessageRouter`]
711+ /// instead of using the one provided via the [`OffersMessageFlow`] parameterization.
712+ ///
713+ /// This gives users full control over how the [`BlindedMessagePath`] is constructed,
714+ /// including the option to omit it entirely.
715+ ///
716+ /// See [`Self::create_refund_builder`] for:
717+ /// - how the resulting [`Refund`] is recognized by [`OffersMessageFlow`] and verified via [`Self::verify_bolt12_invoice`],
718+ /// - refund expiration handling,
719+ /// - rules around revocation and [`Event::PaymentFailed`] behavior,
720+ /// - and defaulting logic for `max_total_routing_fee_msat`.
721+ ///
722+ /// # Errors
723+ ///
724+ /// In addition to the errors documented in [`Self::create_refund_builder`], this method will
725+ /// return an error if the provided [`MessageRouter`] fails to construct a valid
726+ /// [`BlindedMessagePath`] for the refund.
727+ ///
728+ /// [`Refund`]: crate::offers::refund::Refund
729+ /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath
730+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
731+ /// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
732+ /// [`RouteParameters::from_payment_params_and_value`]: crate::routing::router::RouteParameters::from_payment_params_and_value
733+ pub fn create_refund_builder_using_router < ES : Deref , ME : Deref > (
734+ & self , router : ME , entropy_source : ES , amount_msats : u64 , absolute_expiry : Duration ,
735+ payment_id : PaymentId , peers : Vec < MessageForwardNode > ,
736+ ) -> Result < RefundBuilder < secp256k1:: All > , Bolt12SemanticError >
737+ where
738+ ME :: Target : MessageRouter ,
739+ ES :: Target : EntropySource ,
740+ {
741+ self . create_refund_builder_intern (
742+ & * entropy_source,
743+ |node_id, context, secp_ctx| {
744+ router
745+ . create_blinded_paths ( node_id, context, peers, secp_ctx)
746+ . map ( |paths| paths. into_iter ( ) . take ( 1 ) )
747+ . map_err ( |_| Bolt12SemanticError :: MissingPaths )
748+ } ,
749+ amount_msats,
750+ absolute_expiry,
751+ payment_id,
752+ )
680753 }
681754
682755 /// Creates an [`InvoiceRequestBuilder`] such that the [`InvoiceRequest`] it builds is recognized
0 commit comments