Skip to content

Commit 42d30d5

Browse files
authored
req to pay before maturity date (#495)
1 parent fe9f29d commit 42d30d5

File tree

16 files changed

+98
-50
lines changed

16 files changed

+98
-50
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# 0.3.10
2+
3+
* Change behaviour of request to pay
4+
* it's now possible to req to pay before the maturity date
5+
* The actual payment expiry still only happens 2 workdays after the end of the maturity date,
6+
or end of the req to pay end of day if that was after the maturity date
7+
* the `request_to_pay_timed_out` flag is set after payment expired, not after the req to pay expired
8+
* The waiting state for payment is only active during the req to pay (while it's blocked)
9+
* Afterwards, the bill is not blocked anymore, can still be rejected to pay and paid
10+
* But recourse is only possible after the payment expired (after maturity date)
11+
* An expired req to pay, which expired before the maturity date does not show up in `past_payments`
12+
113
# 0.3.9
214

315
* Add possibility to use a local regtest esplora setup for payment

crates/bcr-ebill-api/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bcr-ebill-api"
3-
version = "0.3.9"
3+
version = "0.3.10"
44
edition = "2024"
55

66
[lib]

crates/bcr-ebill-api/src/service/bill_service/data_fetching.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::util;
22

33
use super::service::BillService;
44
use super::{Error, Result};
5-
use bcr_ebill_core::bill::validation::get_deadline_base_for_req_to_pay;
5+
use bcr_ebill_core::bill::validation::get_expiration_deadline_base_for_req_to_pay;
66
use bcr_ebill_core::constants::RECOURSE_DEADLINE_SECONDS;
77
use bcr_ebill_core::contact::Contact;
88
use bcr_ebill_core::{
@@ -152,8 +152,10 @@ impl BillService {
152152
time_of_request_to_pay = Some(req_to_pay_block.timestamp);
153153
paid = self.store.is_paid(&bill.id).await?;
154154
rejected_to_pay = chain.block_with_operation_code_exists(BillOpCode::RejectToPay);
155-
let deadline_base =
156-
get_deadline_base_for_req_to_pay(req_to_pay_block.timestamp, &bill.maturity_date)?;
155+
let deadline_base = get_expiration_deadline_base_for_req_to_pay(
156+
req_to_pay_block.timestamp,
157+
&bill.maturity_date,
158+
)?;
157159
if !paid
158160
&& !rejected_to_pay
159161
&& util::date::check_if_deadline_has_passed(
@@ -162,6 +164,7 @@ impl BillService {
162164
PAYMENT_DEADLINE_SECONDS,
163165
)
164166
{
167+
// this is true, if the payment is expired (after maturity date)
165168
request_to_pay_timed_out = true;
166169
}
167170
}
@@ -325,7 +328,14 @@ impl BillService {
325328
// it's paid - we're not waiting anymore
326329
None
327330
} else if request_to_pay_timed_out {
328-
// it timed out, we're not waiting anymore
331+
// payment expired, we're not waiting anymore
332+
None
333+
} else if util::date::check_if_deadline_has_passed(
334+
last_block.timestamp,
335+
current_timestamp,
336+
PAYMENT_DEADLINE_SECONDS,
337+
) {
338+
// the request timed out, we're not waiting anymore, but the payment isn't expired
329339
None
330340
} else {
331341
// we're waiting, collect data
@@ -508,17 +518,26 @@ impl BillService {
508518
let payment = &bill.status.payment;
509519
if payment.requested_to_pay && !payment.paid && !payment.rejected_to_pay {
510520
if let Some(time_of_request_to_pay) = payment.time_of_request_to_pay {
511-
let deadline_base = get_deadline_base_for_req_to_pay(
521+
let deadline_base = get_expiration_deadline_base_for_req_to_pay(
512522
time_of_request_to_pay,
513523
&bill.data.maturity_date,
514524
)?;
525+
// payment has expired (after maturity date)
515526
if util::date::check_if_deadline_has_passed(
516527
deadline_base,
517528
current_timestamp,
518529
PAYMENT_DEADLINE_SECONDS,
519530
) {
520531
invalidate_and_recalculate = true;
521532
}
533+
// req to pay has expired (after maturity date)
534+
if util::date::check_if_deadline_has_passed(
535+
time_of_request_to_pay,
536+
current_timestamp,
537+
PAYMENT_DEADLINE_SECONDS,
538+
) {
539+
invalidate_and_recalculate = true;
540+
}
522541
}
523542
}
524543

crates/bcr-ebill-api/src/service/bill_service/mod.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4244,12 +4244,24 @@ pub mod tests {
42444244
.check_requests_for_expiration(&bill_payment, 1531593929)
42454245
.unwrap()
42464246
);
4247-
// 2 days after req to pay, but not yet 2 days after end of day maturity date
4247+
// 2 days after req to pay, but not yet 2 days after end of day maturity date, req expired
42484248
assert!(
4249-
!service
4249+
service
42504250
.check_requests_for_expiration(&bill_payment, 1531780429)
42514251
.unwrap()
42524252
);
4253+
// after req to pay, and after end of day maturity date, payment expired
4254+
assert!(
4255+
service
4256+
.check_requests_for_expiration(&bill_payment, 1831593928)
4257+
.unwrap()
4258+
);
4259+
// 1 sec after req to pay, not expired at all
4260+
assert!(
4261+
!service
4262+
.check_requests_for_expiration(&bill_payment, 1531593929)
4263+
.unwrap()
4264+
);
42534265

42544266
let mut bill_acceptance = get_baseline_cached_bill(TEST_BILL_ID.to_string());
42554267
bill_acceptance.status.acceptance = BillAcceptanceStatus {

crates/bcr-ebill-api/src/service/bill_service/service.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::persistence::identity::{IdentityChainStoreApi, IdentityStoreApi};
2424
use crate::util::BcrKeys;
2525
use crate::{external, util};
2626
use async_trait::async_trait;
27-
use bcr_ebill_core::bill::validation::get_deadline_base_for_req_to_pay;
27+
use bcr_ebill_core::bill::validation::get_expiration_deadline_base_for_req_to_pay;
2828
use bcr_ebill_core::bill::{
2929
BillIssueData, BillValidateActionData, PastPaymentDataPayment, PastPaymentDataRecourse,
3030
PastPaymentDataSell, PastPaymentResult, PastPaymentStatus,
@@ -724,8 +724,12 @@ impl BillServiceApi for BillService {
724724
.bitcoin_client
725725
.get_mempool_link_for_address(&address_to_pay);
726726

727-
let deadline_base =
728-
get_deadline_base_for_req_to_pay(req_to_pay.timestamp, &bill.maturity_date)?;
727+
// we check for the payment expiration, not the request expiration
728+
// if the request expired, but the payment deadline hasn't, it's not a past payment
729+
let deadline_base = get_expiration_deadline_base_for_req_to_pay(
730+
req_to_pay.timestamp,
731+
&bill.maturity_date,
732+
)?;
729733
let is_expired = util::date::check_if_deadline_has_passed(
730734
deadline_base,
731735
timestamp,
@@ -756,9 +760,7 @@ impl BillServiceApi for BillService {
756760
};
757761
PastPaymentStatus::Rejected(ts)
758762
} else {
759-
PastPaymentStatus::Expired(
760-
req_to_pay.timestamp + PAYMENT_DEADLINE_SECONDS,
761-
)
763+
PastPaymentStatus::Expired(deadline_base + PAYMENT_DEADLINE_SECONDS)
762764
},
763765
}));
764766
}

crates/bcr-ebill-core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bcr-ebill-core"
3-
version = "0.3.9"
3+
version = "0.3.10"
44
edition = "2024"
55

66
[lib]

crates/bcr-ebill-core/src/bill/validation.rs

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,6 @@ impl Validate for BillValidateActionData {
151151
self.bill_is_blocked()?;
152152
self.bill_can_only_be_recoursed()?;
153153
// not already requested to pay - checked above already
154-
// maturity date must have started
155-
let maturity_date_start =
156-
util::date::date_string_to_timestamp(&self.maturity_date, None)?;
157-
if self.timestamp < maturity_date_start {
158-
return Err(ValidationError::BillRequestedToPayBeforeMaturityDate);
159-
}
160154
// the caller has to be the bill holder
161155
if self.signer_node_id != holder_node_id {
162156
return Err(ValidationError::CallerIsNotHolder);
@@ -217,8 +211,8 @@ impl Validate for BillValidateActionData {
217211
{
218212
// only if the bill is not paid already - checked above
219213

220-
// only if the request to pay expired or was rejected
221-
let deadline_base = get_deadline_base_for_req_to_pay(
214+
// only if the deadline to pay expired or was rejected
215+
let deadline_base = get_expiration_deadline_base_for_req_to_pay(
222216
req_to_pay.timestamp,
223217
&self.maturity_date,
224218
)?;
@@ -428,18 +422,20 @@ impl Validate for BillValidateActionData {
428422
}
429423

430424
/// calculates the base for the expiration deadline of a request to pay - if it was before the
431-
/// maturity date, we take the end of the day of the maturity date, otherwise the req to pay
432-
/// timestamp
433-
pub fn get_deadline_base_for_req_to_pay(
425+
/// maturity date, we take the end of the day of the maturity date, otherwise the end of
426+
/// day of the req to pay timestamp
427+
pub fn get_expiration_deadline_base_for_req_to_pay(
434428
req_to_pay_ts: u64,
435429
bill_maturity_date: &str,
436430
) -> Result<u64, ValidationError> {
437431
let maturity_date = util::date::date_string_to_timestamp(bill_maturity_date, None)?;
432+
// we calculate from the end of the day
438433
let maturity_date_end_of_day = util::date::end_of_day_as_timestamp(maturity_date);
439-
let mut deadline_base = req_to_pay_ts;
434+
// we calculate from the end of the day of the request to pay
435+
let mut deadline_base = util::date::end_of_day_as_timestamp(req_to_pay_ts);
440436
// requested to pay after maturity date - deadline base is req to pay
441437
if deadline_base < maturity_date_end_of_day {
442-
// requested to pay before end of maturity date - deadline base is maturity
438+
// requested to pay before end of day of maturity date - deadline base is maturity
443439
// date end of day
444440
deadline_base = maturity_date_end_of_day;
445441
}
@@ -476,7 +472,7 @@ impl BillValidateActionData {
476472
.blockchain
477473
.get_last_version_block_with_op_code(BillOpCode::RequestToPay)
478474
{
479-
let deadline_base = get_deadline_base_for_req_to_pay(
475+
let deadline_base = get_expiration_deadline_base_for_req_to_pay(
480476
req_to_pay_block.timestamp,
481477
&self.maturity_date,
482478
)?;
@@ -551,17 +547,16 @@ impl BillValidateActionData {
551547
Ok(())
552548
}
553549

550+
/// active req to pay, calculated from the start of the request
554551
fn bill_waiting_for_req_to_pay(&self) -> Result<(), ValidationError> {
555552
if self.blockchain.get_latest_block().op_code == BillOpCode::RequestToPay {
556553
if let Some(req_to_pay) = self
557554
.blockchain
558555
.get_last_version_block_with_op_code(BillOpCode::RequestToPay)
559556
{
560-
let deadline_base =
561-
get_deadline_base_for_req_to_pay(req_to_pay.timestamp, &self.maturity_date)?;
562557
if !self.is_paid
563558
&& !util::date::check_if_deadline_has_passed(
564-
deadline_base,
559+
req_to_pay.timestamp, // calculated from start of request
565560
self.timestamp,
566561
PAYMENT_DEADLINE_SECONDS,
567562
)
@@ -593,7 +588,10 @@ mod tests {
593588
TEST_PRIVATE_KEY_SECP, TEST_PUB_KEY_SECP, VALID_PAYMENT_ADDRESS_TESTNET, valid_address,
594589
valid_identity_public_data, valid_other_identity_public_data,
595590
},
596-
util::{BcrKeys, date::now},
591+
util::{
592+
BcrKeys,
593+
date::{format_date_string, now},
594+
},
597595
};
598596

599597
use super::*;
@@ -1000,6 +998,8 @@ mod tests {
1000998

1001999
#[rstest]
10021000
#[case::req_to_pay(BillValidateActionData { signer_node_id: OTHER_TEST_PUB_KEY_SECP.into(), bill_action: BillAction::RequestToPay("sat".into()), ..valid_bill_validate_action_data(valid_bill_blockchain_issue( valid_bill_issue_block_data(),)) }, Ok(()))]
1001+
#[case::req_to_pay_after_maturity(BillValidateActionData { maturity_date: "2022-11-12".into(), signer_node_id: OTHER_TEST_PUB_KEY_SECP.into(), bill_action: BillAction::RequestToPay("sat".into()), ..valid_bill_validate_action_data(valid_bill_blockchain_issue( valid_bill_issue_block_data(),)) }, Ok(()))]
1002+
#[case::req_to_pay_before_maturity(BillValidateActionData { maturity_date: "2099-11-12".into(), signer_node_id: OTHER_TEST_PUB_KEY_SECP.into(), bill_action: BillAction::RequestToPay("sat".into()), ..valid_bill_validate_action_data(valid_bill_blockchain_issue( valid_bill_issue_block_data() ,)) }, Ok(()))]
10031003
fn test_validate_bill_req_to_pay_valid(
10041004
#[case] input: BillValidateActionData,
10051005
#[case] expected: Result<(), ValidationError>,
@@ -1019,7 +1019,6 @@ mod tests {
10191019
#[case::payment_expired_only_recourse(BillValidateActionData { bill_action: BillAction::RequestToPay("sat".into()), timestamp: now().timestamp() as u64 + (PAYMENT_DEADLINE_SECONDS * 2), ..valid_bill_validate_action_data(add_req_to_pay_block(valid_bill_blockchain_issue( valid_bill_issue_block_data(),))) }, Err(ValidationError::BillPaymentExpired))]
10201020
#[case::acceptance_expired_only_recourse(BillValidateActionData { bill_action: BillAction::RequestToPay("sat".into()), timestamp: now().timestamp() as u64 + (ACCEPT_DEADLINE_SECONDS * 2), ..valid_bill_validate_action_data(add_req_to_accept_block(valid_bill_blockchain_issue( valid_bill_issue_block_data(),))) }, Err(ValidationError::BillAcceptanceExpired))]
10211021
#[case::req_to_pay_not_holder(BillValidateActionData { bill_action: BillAction::RequestToPay("sat".into()), signer_node_id: TEST_PUB_KEY_SECP.into(), ..valid_bill_validate_action_data(valid_bill_blockchain_issue( valid_bill_issue_block_data(),)) }, Err(ValidationError::CallerIsNotHolder))]
1022-
#[case::req_to_pay_before_maturity_date(BillValidateActionData { maturity_date: "2099-01-01".into(), bill_action: BillAction::RequestToPay("sat".into()), ..valid_bill_validate_action_data(valid_bill_blockchain_issue( valid_bill_issue_block_data(),)) }, Err(ValidationError::BillRequestedToPayBeforeMaturityDate))]
10231022
#[case::req_to_pay_already_req_to_payed(BillValidateActionData { bill_action: BillAction::RequestToPay("sat".into()), ..valid_bill_validate_action_data(add_req_to_pay_block(valid_bill_blockchain_issue( valid_bill_issue_block_data(),))) }, Err(ValidationError::BillIsRequestedToPayAndWaitingForPayment))]
10241023
fn test_validate_bill_req_to_pay_errors(
10251024
#[case] input: BillValidateActionData,
@@ -1173,6 +1172,7 @@ mod tests {
11731172

11741173
#[rstest]
11751174
#[case::offer_to_sell(BillValidateActionData { bill_action: BillAction::OfferToSell(valid_other_identity_public_data(), 500, "sat".into()), signer_node_id: OTHER_TEST_PUB_KEY_SECP.into(), ..valid_bill_validate_action_data(valid_bill_blockchain_issue( valid_bill_issue_block_data(),)) }, Ok(()))]
1175+
#[case::offer_to_sell_req_to_pay_expired_before_maturity(BillValidateActionData { maturity_date: "2099-11-12".into(), timestamp: now().timestamp() as u64 + (PAYMENT_DEADLINE_SECONDS * 2) ,bill_action: BillAction::OfferToSell(valid_other_identity_public_data(), 500, "sat".into()), signer_node_id: OTHER_TEST_PUB_KEY_SECP.into(), ..valid_bill_validate_action_data(add_req_to_pay_block(valid_bill_blockchain_issue( valid_bill_issue_block_data(),))) }, Ok(()))]
11761176
fn test_validate_bill_offer_to_sell_valid(
11771177
#[case] input: BillValidateActionData,
11781178
#[case] expected: Result<(), ValidationError>,
@@ -1200,7 +1200,10 @@ mod tests {
12001200
}
12011201

12021202
#[rstest]
1203-
#[case::sell_invalid_data_buyer(BillValidateActionData { bill_action: BillAction::Sell(valid_identity_public_data(), 500, "sat".into(), VALID_PAYMENT_ADDRESS_TESTNET.into()), signer_node_id: OTHER_TEST_PUB_KEY_SECP.into(), ..valid_bill_validate_action_data(add_offer_to_sell_block(valid_bill_blockchain_issue( valid_bill_issue_block_data(),))) }, Ok(()))]
1203+
#[case::sell(BillValidateActionData { bill_action: BillAction::Sell(valid_identity_public_data(), 500, "sat".into(), VALID_PAYMENT_ADDRESS_TESTNET.into()), signer_node_id: OTHER_TEST_PUB_KEY_SECP.into(), ..valid_bill_validate_action_data(add_offer_to_sell_block(valid_bill_blockchain_issue( valid_bill_issue_block_data(),))) }, Ok(()))]
1204+
// minus 8 seconds so timestamp is before offer to sell expiry and after req to pay expiry
1205+
// as every block adds 1 sec to issue block, which is now() - 10
1206+
#[case::sell_req_to_pay_expired_before_maturity(BillValidateActionData { maturity_date: "2099-11-12".into(), timestamp: now().timestamp() as u64 + (PAYMENT_DEADLINE_SECONDS - 8), bill_action: BillAction::Sell(valid_identity_public_data(), 500, "sat".into(), VALID_PAYMENT_ADDRESS_TESTNET.into()), signer_node_id: OTHER_TEST_PUB_KEY_SECP.into(), ..valid_bill_validate_action_data(add_offer_to_sell_block(add_req_to_pay_block(valid_bill_blockchain_issue( valid_bill_issue_block_data(),)))) }, Ok(()))]
12041207
fn test_validate_bill_sell_valid(
12051208
#[case] input: BillValidateActionData,
12061209
#[case] expected: Result<(), ValidationError>,
@@ -1291,7 +1294,8 @@ mod tests {
12911294
}
12921295

12931296
#[rstest]
1294-
#[case::reject_to_pay_expired(BillValidateActionData { bill_action: BillAction::RejectPayment, ..valid_bill_validate_action_data(add_req_to_pay_block(valid_bill_blockchain_issue( valid_bill_issue_block_data(),))) }, Ok(()))]
1297+
#[case::reject_to_pay(BillValidateActionData { bill_action: BillAction::RejectPayment, ..valid_bill_validate_action_data(add_req_to_pay_block(valid_bill_blockchain_issue( valid_bill_issue_block_data(),))) }, Ok(()))]
1298+
#[case::reject_to_pay_maturity_not_expired(BillValidateActionData { maturity_date: format_date_string(now()), bill_action: BillAction::RejectPayment, ..valid_bill_validate_action_data(add_req_to_pay_block(valid_bill_blockchain_issue( valid_bill_issue_block_data(),))) }, Ok(()))]
12951299
fn test_validate_bill_reject_payment_valid(
12961300
#[case] input: BillValidateActionData,
12971301
#[case] expected: Result<(), ValidationError>,

crates/bcr-ebill-core/src/lib.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -317,10 +317,6 @@ pub enum ValidationError {
317317
#[error("Bill request to pay did not expire and was not rejected")]
318318
BillRequestToPayDidNotExpireAndWasNotRejected,
319319

320-
/// error returned if the bill was requested to pay before the maturity date started
321-
#[error("Bill requested to pay before maturity date started")]
322-
BillRequestedToPayBeforeMaturityDate,
323-
324320
/// error returned if the bill was not requester to recourse, e.g. when rejecting to pay for
325321
/// recourse
326322
#[error("Bill was not requested to recourse")]

crates/bcr-ebill-persistence/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bcr-ebill-persistence"
3-
version = "0.3.9"
3+
version = "0.3.10"
44
edition = "2024"
55

66
[lib]

crates/bcr-ebill-transport/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bcr-ebill-transport"
3-
version = "0.3.9"
3+
version = "0.3.10"
44
edition = "2024"
55

66
[lib]

0 commit comments

Comments
 (0)