|
15 | 15 | //! * For parsing use `str::parse::<Invoice>(&self)` (see the docs of `impl FromStr for Invoice`)
|
16 | 16 | //! * For constructing invoices use the `InvoiceBuilder`
|
17 | 17 | //! * For serializing invoices use the `Display`/`ToString` traits
|
| 18 | +pub mod payment; |
18 | 19 | pub mod utils;
|
19 | 20 |
|
20 | 21 | extern crate bech32;
|
21 | 22 | extern crate bitcoin_hashes;
|
22 |
| -extern crate lightning; |
| 23 | +#[macro_use] extern crate lightning; |
23 | 24 | extern crate num_traits;
|
24 | 25 | extern crate secp256k1;
|
25 | 26 |
|
@@ -1187,6 +1188,19 @@ impl Invoice {
|
1187 | 1188 | .unwrap_or(Duration::from_secs(DEFAULT_EXPIRY_TIME))
|
1188 | 1189 | }
|
1189 | 1190 |
|
| 1191 | + /// Returns whether the invoice has expired. |
| 1192 | + pub fn is_expired(&self) -> bool { |
| 1193 | + Self::is_expired_from_epoch(self.timestamp(), self.expiry_time()) |
| 1194 | + } |
| 1195 | + |
| 1196 | + /// Returns whether the expiry time from the given epoch has passed. |
| 1197 | + pub(crate) fn is_expired_from_epoch(epoch: &SystemTime, expiry_time: Duration) -> bool { |
| 1198 | + match epoch.elapsed() { |
| 1199 | + Ok(elapsed) => elapsed > expiry_time, |
| 1200 | + Err(_) => false, |
| 1201 | + } |
| 1202 | + } |
| 1203 | + |
1190 | 1204 | /// Returns the invoice's `min_final_cltv_expiry` time, if present, otherwise
|
1191 | 1205 | /// [`DEFAULT_MIN_FINAL_CLTV_EXPIRY`].
|
1192 | 1206 | pub fn min_final_cltv_expiry(&self) -> u64 {
|
@@ -1219,8 +1233,13 @@ impl Invoice {
|
1219 | 1233 | self.signed_invoice.currency()
|
1220 | 1234 | }
|
1221 | 1235 |
|
| 1236 | + /// Returns the amount if specified in the invoice as millisatoshis. |
| 1237 | + pub fn amount_milli_satoshis(&self) -> Option<u64> { |
| 1238 | + self.signed_invoice.amount_pico_btc().map(|v| v / 10) |
| 1239 | + } |
| 1240 | + |
1222 | 1241 | /// Returns the amount if specified in the invoice as pico <currency>.
|
1223 |
| - pub fn amount_pico_btc(&self) -> Option<u64> { |
| 1242 | + fn amount_pico_btc(&self) -> Option<u64> { |
1224 | 1243 | self.signed_invoice.amount_pico_btc()
|
1225 | 1244 | }
|
1226 | 1245 | }
|
@@ -1867,6 +1886,7 @@ mod test {
|
1867 | 1886 | assert!(invoice.check_signature().is_ok());
|
1868 | 1887 | assert_eq!(invoice.tagged_fields().count(), 10);
|
1869 | 1888 |
|
| 1889 | + assert_eq!(invoice.amount_milli_satoshis(), Some(123)); |
1870 | 1890 | assert_eq!(invoice.amount_pico_btc(), Some(1230));
|
1871 | 1891 | assert_eq!(invoice.currency(), Currency::BitcoinTestnet);
|
1872 | 1892 | assert_eq!(
|
@@ -1913,5 +1933,33 @@ mod test {
|
1913 | 1933 |
|
1914 | 1934 | assert_eq!(invoice.min_final_cltv_expiry(), DEFAULT_MIN_FINAL_CLTV_EXPIRY);
|
1915 | 1935 | assert_eq!(invoice.expiry_time(), Duration::from_secs(DEFAULT_EXPIRY_TIME));
|
| 1936 | + assert!(!invoice.is_expired()); |
| 1937 | + } |
| 1938 | + |
| 1939 | + #[test] |
| 1940 | + fn test_expiration() { |
| 1941 | + use ::*; |
| 1942 | + use secp256k1::Secp256k1; |
| 1943 | + use secp256k1::key::SecretKey; |
| 1944 | + |
| 1945 | + let timestamp = SystemTime::now() |
| 1946 | + .checked_sub(Duration::from_secs(DEFAULT_EXPIRY_TIME * 2)) |
| 1947 | + .unwrap(); |
| 1948 | + let signed_invoice = InvoiceBuilder::new(Currency::Bitcoin) |
| 1949 | + .description("Test".into()) |
| 1950 | + .payment_hash(sha256::Hash::from_slice(&[0;32][..]).unwrap()) |
| 1951 | + .payment_secret(PaymentSecret([0; 32])) |
| 1952 | + .timestamp(timestamp) |
| 1953 | + .build_raw() |
| 1954 | + .unwrap() |
| 1955 | + .sign::<_, ()>(|hash| { |
| 1956 | + let privkey = SecretKey::from_slice(&[41; 32]).unwrap(); |
| 1957 | + let secp_ctx = Secp256k1::new(); |
| 1958 | + Ok(secp_ctx.sign_recoverable(hash, &privkey)) |
| 1959 | + }) |
| 1960 | + .unwrap(); |
| 1961 | + let invoice = Invoice::from_signed(signed_invoice).unwrap(); |
| 1962 | + |
| 1963 | + assert!(invoice.is_expired()); |
1916 | 1964 | }
|
1917 | 1965 | }
|
0 commit comments