@@ -27,7 +27,6 @@ pub use lightning::events::{ClosureReason, PaymentFailureReason};
2727pub use lightning:: ln:: types:: ChannelId ;
2828pub use lightning:: offers:: invoice:: Bolt12Invoice ;
2929pub use lightning:: offers:: offer:: OfferId ;
30- pub use lightning:: offers:: refund:: Refund ;
3130pub use lightning:: routing:: gossip:: { NodeAlias , NodeId , RoutingFees } ;
3231pub use lightning:: util:: string:: UntrustedString ;
3332
@@ -58,6 +57,7 @@ use bitcoin::hashes::Hash;
5857use bitcoin:: secp256k1:: PublicKey ;
5958use lightning:: ln:: channelmanager:: PaymentId ;
6059use lightning:: offers:: offer:: { Amount as LdkAmount , Offer as LdkOffer } ;
60+ use lightning:: offers:: refund:: Refund as LdkRefund ;
6161use lightning:: util:: ser:: Writeable ;
6262use lightning_invoice:: { Bolt11Invoice as LdkBolt11Invoice , Bolt11InvoiceDescriptionRef } ;
6363
@@ -272,15 +272,117 @@ impl AsRef<LdkOffer> for Offer {
272272 }
273273}
274274
275- impl UniffiCustomTypeConverter for Refund {
276- type Builtin = String ;
275+ /// A `Refund` is a request to send an [`Bolt12Invoice`] without a preceding [`Offer`].
276+ ///
277+ /// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to
278+ /// recoup their funds. A refund may be used more generally as an "offer for money", such as with a
279+ /// bitcoin ATM.
280+ ///
281+ /// [`Bolt12Invoice`]: lightning::offers::invoice::Bolt12Invoice
282+ /// [`Offer`]: lightning::offers::offer::Offer
283+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
284+ pub struct Refund {
285+ pub inner : LdkRefund ,
286+ }
277287
278- fn into_custom ( val : Self :: Builtin ) -> uniffi:: Result < Self > {
279- Refund :: from_str ( & val) . map_err ( |_| Error :: InvalidRefund . into ( ) )
288+ impl Refund {
289+ pub fn from_str ( refund_str : & str ) -> Result < Self , Error > {
290+ refund_str. parse ( )
280291 }
281292
282- fn from_custom ( obj : Self ) -> Self :: Builtin {
283- obj. to_string ( )
293+ /// A complete description of the purpose of the refund.
294+ ///
295+ /// Intended to be displayed to the user but with the caveat that it has not been verified in any way.
296+ pub fn description ( & self ) -> String {
297+ self . inner . description ( ) . to_string ( )
298+ }
299+
300+ /// Seconds since the Unix epoch when an invoice should no longer be sent.
301+ ///
302+ /// If `None`, the refund does not expire.
303+ pub fn absolute_expiry_seconds ( & self ) -> Option < u64 > {
304+ self . inner . absolute_expiry ( ) . map ( |duration| duration. as_secs ( ) )
305+ }
306+
307+ /// Whether the refund has expired.
308+ pub fn is_expired ( & self ) -> bool {
309+ self . inner . is_expired ( )
310+ }
311+
312+ /// The issuer of the refund, possibly beginning with `user@domain` or `domain`.
313+ ///
314+ /// Intended to be displayed to the user but with the caveat that it has not been verified in any way.
315+ pub fn issuer ( & self ) -> Option < String > {
316+ self . inner . issuer ( ) . map ( |printable| printable. to_string ( ) )
317+ }
318+
319+ /// An unpredictable series of bytes, typically containing information about the derivation of
320+ /// [`payer_signing_pubkey`].
321+ ///
322+ /// [`payer_signing_pubkey`]: Self::payer_signing_pubkey
323+ pub fn payer_metadata ( & self ) -> Vec < u8 > {
324+ self . inner . payer_metadata ( ) . to_vec ( )
325+ }
326+
327+ /// A chain that the refund is valid for.
328+ pub fn chain ( & self ) -> Option < Network > {
329+ Network :: try_from ( self . inner . chain ( ) ) . ok ( )
330+ }
331+
332+ /// The amount to refund in msats (i.e., the minimum lightning-payable unit for [`chain`]).
333+ ///
334+ /// [`chain`]: Self::chain
335+ pub fn amount_msats ( & self ) -> u64 {
336+ self . inner . amount_msats ( )
337+ }
338+
339+ /// The quantity of an item that refund is for.
340+ pub fn quantity ( & self ) -> Option < u64 > {
341+ self . inner . quantity ( )
342+ }
343+
344+ /// A public node id to send to in the case where there are no [`paths`].
345+ ///
346+ /// Otherwise, a possibly transient pubkey.
347+ ///
348+ /// [`paths`]: lightning::offers::refund::Refund::paths
349+ pub fn payer_signing_pubkey ( & self ) -> PublicKey {
350+ self . inner . payer_signing_pubkey ( )
351+ }
352+
353+ /// Payer provided note to include in the invoice.
354+ pub fn payer_note ( & self ) -> Option < String > {
355+ self . inner . payer_note ( ) . map ( |printable| printable. to_string ( ) )
356+ }
357+ }
358+
359+ impl std:: str:: FromStr for Refund {
360+ type Err = Error ;
361+
362+ fn from_str ( refund_str : & str ) -> Result < Self , Self :: Err > {
363+ refund_str
364+ . parse :: < LdkRefund > ( )
365+ . map ( |refund| Refund { inner : refund } )
366+ . map_err ( |_| Error :: InvalidRefund )
367+ }
368+ }
369+
370+ impl From < LdkRefund > for Refund {
371+ fn from ( refund : LdkRefund ) -> Self {
372+ Refund { inner : refund }
373+ }
374+ }
375+
376+ impl Deref for Refund {
377+ type Target = LdkRefund ;
378+ fn deref ( & self ) -> & Self :: Target {
379+ & self . inner
380+ }
381+ }
382+
383+ impl AsRef < LdkRefund > for Refund {
384+ fn as_ref ( & self ) -> & LdkRefund {
385+ self . deref ( )
284386 }
285387}
286388
@@ -830,9 +932,11 @@ mod tests {
830932 time:: { SystemTime , UNIX_EPOCH } ,
831933 } ;
832934
833- use lightning:: offers:: offer:: { OfferBuilder , Quantity } ;
834-
835935 use super :: * ;
936+ use lightning:: offers:: {
937+ offer:: { OfferBuilder , Quantity } ,
938+ refund:: RefundBuilder ,
939+ } ;
836940
837941 fn create_test_invoice ( ) -> ( LdkBolt11Invoice , Bolt11Invoice ) {
838942 let invoice_string = "lnbc1pn8g249pp5f6ytj32ty90jhvw69enf30hwfgdhyymjewywcmfjevflg6s4z86qdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhdfjjzh2j9f7ye5qqqqryqqqqthqqpysp5mm832athgcal3m7h35sc29j63lmgzvwc5smfjh2es65elc2ns7dq9qrsgqu2xcje2gsnjp0wn97aknyd3h58an7sjj6nhcrm40846jxphv47958c6th76whmec8ttr2wmg6sxwchvxmsc00kqrzqcga6lvsf9jtqgqy5yexa" ;
@@ -871,6 +975,28 @@ mod tests {
871975 ( ldk_offer, wrapped_offer)
872976 }
873977
978+ fn create_test_refund ( ) -> ( LdkRefund , Refund ) {
979+ let payer_key = bitcoin:: secp256k1:: PublicKey :: from_str (
980+ "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619" ,
981+ )
982+ . unwrap ( ) ;
983+
984+ let expiry =
985+ ( SystemTime :: now ( ) + Duration :: from_secs ( 3600 ) ) . duration_since ( UNIX_EPOCH ) . unwrap ( ) ;
986+
987+ let builder = RefundBuilder :: new ( "Test refund" . to_string ( ) . into ( ) , payer_key, 100_000 )
988+ . unwrap ( )
989+ . description ( "Test refund description" . to_string ( ) )
990+ . absolute_expiry ( expiry)
991+ . quantity ( 3 )
992+ . issuer ( "test_issuer" . to_string ( ) ) ;
993+
994+ let ldk_refund = builder. build ( ) . unwrap ( ) ;
995+ let wrapped_refund = Refund :: from ( ldk_refund. clone ( ) ) ;
996+
997+ ( ldk_refund, wrapped_refund)
998+ }
999+
8741000 #[ test]
8751001 fn test_invoice_description_conversion ( ) {
8761002 let hash = "09d08d4865e8af9266f6cc7c0ae23a1d6bf868207cf8f7c5979b9f6ed850dfb0" . to_string ( ) ;
@@ -1087,4 +1213,81 @@ mod tests {
10871213 } ,
10881214 }
10891215 }
1216+
1217+ #[ test]
1218+ fn test_refund_roundtrip ( ) {
1219+ let ( ldk_refund, _) = create_test_refund ( ) ;
1220+
1221+ let refund_str = ldk_refund. to_string ( ) ;
1222+
1223+ let parsed_refund = Refund :: from_str ( & refund_str) ;
1224+ assert ! ( parsed_refund. is_ok( ) , "Failed to parse refund from string!" ) ;
1225+
1226+ let invalid_result = Refund :: from_str ( "invalid_refund_string" ) ;
1227+ assert ! ( invalid_result. is_err( ) ) ;
1228+ assert ! ( matches!( invalid_result. err( ) . unwrap( ) , Error :: InvalidRefund ) ) ;
1229+ }
1230+
1231+ #[ test]
1232+ fn test_refund_properties ( ) {
1233+ let ( ldk_refund, wrapped_refund) = create_test_refund ( ) ;
1234+
1235+ assert_eq ! ( ldk_refund. description( ) . to_string( ) , wrapped_refund. description( ) ) ;
1236+ assert_eq ! ( ldk_refund. amount_msats( ) , wrapped_refund. amount_msats( ) ) ;
1237+ assert_eq ! ( ldk_refund. is_expired( ) , wrapped_refund. is_expired( ) ) ;
1238+
1239+ match ( ldk_refund. absolute_expiry ( ) , wrapped_refund. absolute_expiry_seconds ( ) ) {
1240+ ( Some ( ldk_expiry) , Some ( wrapped_expiry) ) => {
1241+ assert_eq ! ( ldk_expiry. as_secs( ) , wrapped_expiry) ;
1242+ } ,
1243+ ( None , None ) => {
1244+ // Both fields are missing which is expected behaviour when converting
1245+ } ,
1246+ ( Some ( _) , None ) => {
1247+ panic ! ( "LDK refund had an expiry but wrapped refund did not!" ) ;
1248+ } ,
1249+ ( None , Some ( _) ) => {
1250+ panic ! ( "Wrapped refund had an expiry but LDK refund did not!" ) ;
1251+ } ,
1252+ }
1253+
1254+ match ( ldk_refund. quantity ( ) , wrapped_refund. quantity ( ) ) {
1255+ ( Some ( ldk_expiry) , Some ( wrapped_expiry) ) => {
1256+ assert_eq ! ( ldk_expiry, wrapped_expiry) ;
1257+ } ,
1258+ ( None , None ) => {
1259+ // Both fields are missing which is expected behaviour when converting
1260+ } ,
1261+ ( Some ( _) , None ) => {
1262+ panic ! ( "LDK refund had an quantity but wrapped refund did not!" ) ;
1263+ } ,
1264+ ( None , Some ( _) ) => {
1265+ panic ! ( "Wrapped refund had an quantity but LDK refund did not!" ) ;
1266+ } ,
1267+ }
1268+
1269+ match ( ldk_refund. issuer ( ) , wrapped_refund. issuer ( ) ) {
1270+ ( Some ( ldk_issuer) , Some ( wrapped_issuer) ) => {
1271+ assert_eq ! ( ldk_issuer. to_string( ) , wrapped_issuer) ;
1272+ } ,
1273+ ( None , None ) => {
1274+ // Both fields are missing which is expected behaviour when converting
1275+ } ,
1276+ ( Some ( _) , None ) => {
1277+ panic ! ( "LDK refund had an issuer but wrapped refund did not!" ) ;
1278+ } ,
1279+ ( None , Some ( _) ) => {
1280+ panic ! ( "Wrapped refund had an issuer but LDK refund did not!" ) ;
1281+ } ,
1282+ }
1283+
1284+ assert_eq ! ( ldk_refund. payer_metadata( ) . to_vec( ) , wrapped_refund. payer_metadata( ) ) ;
1285+ assert_eq ! ( ldk_refund. payer_signing_pubkey( ) , wrapped_refund. payer_signing_pubkey( ) ) ;
1286+
1287+ if let Ok ( network) = Network :: try_from ( ldk_refund. chain ( ) ) {
1288+ assert_eq ! ( wrapped_refund. chain( ) , Some ( network) ) ;
1289+ }
1290+
1291+ assert_eq ! ( ldk_refund. payer_note( ) . map( |p| p. to_string( ) ) , wrapped_refund. payer_note( ) ) ;
1292+ }
10901293}
0 commit comments