Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workaround for DebitNote in Received state for eternity #3348

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions core/payment/src/api/debit_notes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,15 @@ async fn accept_debit_note(
activity_id,
activity.agreement_id
);
// Status must be changed to Accepted, otherwise this DebitNote will always be in Received
// state and Requestor will get it again from the events api.
// TODO: This is a workaround. From this point we return early to avoid double scheduling
// of the payment for both DebitNote and Invoice. Proper way would be to fix db structure
// so we could find out here, if payment was already scheduled or we need to schedule it.
dao.accept(debit_note_id.clone(), node_id).await.ok();
// Sync status later. We could do it here, but it is workaround anyway and this sync
// is not crucial since the Agreement is already finished.
sync_dao.upsert(debit_note.issuer_id).await.ok();
return response::ok(Null);
}
},
Expand Down
126 changes: 125 additions & 1 deletion core/payment/tests/test_debit_notes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ use std::time::Duration;
use test_context::test_context;

use ya_client_model::payment::allocation::PaymentPlatformEnum;
use ya_client_model::payment::{Acceptance, DocumentStatus, NewAllocation, NewDebitNote};
use ya_client_model::payment::{
Acceptance, DocumentStatus, NewAllocation, NewDebitNote, NewInvoice,
};
use ya_framework_basic::async_drop::DroppableTestContext;
use ya_framework_basic::log::enable_logs;
use ya_framework_basic::{resource, temp_dir};
use ya_framework_mocks::market::FakeMarket;
use ya_framework_mocks::net::MockNet;
use ya_framework_mocks::node::MockNode;
use ya_framework_mocks::payment::fake_payment::FakePayment;
use ya_framework_mocks::payment::{Driver, PaymentRestExt};

#[cfg_attr(not(feature = "framework-test"), ignore)]
Expand Down Expand Up @@ -228,3 +231,124 @@ async fn test_debit_note_flow(ctx: &mut DroppableTestContext) -> anyhow::Result<
log::info!(" 👍🏻 Example completed successfully ❤️");
Ok(())
}

#[cfg_attr(not(feature = "framework-test"), ignore)]
#[test_context(DroppableTestContext)]
#[serial_test::serial]
async fn test_debit_note_acceptance_after_invoice(
ctx: &mut DroppableTestContext,
) -> anyhow::Result<()> {
enable_logs(true);

let dir = temp_dir!("test_debit_note_acceptance_after_invoice")?;
let dir = dir.path();

let net = MockNet::new().bind();
let node1 = MockNode::new(net.clone(), "node-1", dir)
.with_identity()
.with_payment(None)
.with_fake_market()
.with_fake_activity();
node1.bind_gsb().await?;
node1.start_server(ctx).await?;

let identity = node1.get_identity()?;
let appkey_prov = identity.create_identity_key("provider").await?;
let appkey_req = identity
.create_from_private_key(&resource!("ci-requestor-1.key.priv"))
.await?;

node1
.get_payment()?
.fund_account(Driver::Erc20, &appkey_req.identity.to_string())
.await?;

let requestor = node1.rest_payments(&appkey_req.key)?;
let provider = node1.rest_payments(&appkey_prov.key)?;

log::info!("Creating mock Agreement...");
let agreement =
FakeMarket::create_fake_agreement(appkey_req.identity, appkey_prov.identity).unwrap();
node1.get_market()?.add_agreement(agreement.clone()).await;

log::info!("Creating mock Activity...");
let activity_id = node1
.get_activity()?
.create_activity(&agreement.agreement_id)
.await;

log::info!("Creating allocation...");
let new_allocation = FakePayment::default_allocation(&agreement, BigDecimal::from(10u64))?;
let allocation = requestor.create_allocation(&new_allocation).await?;
log::info!(
"Allocation created. ({}) Issuing DebitNote and Invoice...",
allocation.allocation_id
);

let now = Utc::now();
let debit_note = provider
.issue_debit_note(&NewDebitNote {
activity_id: activity_id.clone(),
total_amount_due: BigDecimal::from(1u64),
usage_counter_vector: None,
payment_due_date: Some(Utc::now()),
})
.await?;
provider.send_debit_note(&debit_note.debit_note_id).await?;
log::info!(
"DebitNote sent ({}). Sending invoice...",
debit_note.debit_note_id
);

let invoice = provider
.issue_invoice(&NewInvoice {
agreement_id: agreement.agreement_id.to_string(),
activity_ids: Some(vec![activity_id.clone()]),
amount: BigDecimal::from(1u64),
payment_due_date: Utc::now(),
})
.await?;
provider.send_invoice(&invoice.invoice_id).await?;

log::info!(
"Invoice sent. Accepting Invoice ({})...",
invoice.invoice_id
);

// Accepting Invoice before last DebitNote in Agreement.
// DebitNote should change status to Accepted, but it shouldn't trigger payment, what
// will be checked later.
requestor
.simple_accept_invoice(&invoice, &allocation)
.await
.unwrap();
requestor
.simple_accept_debit_note(&debit_note, &allocation)
.await
.unwrap();

let debit_snapshot = requestor
.get_debit_note(&debit_note.debit_note_id)
.await
.unwrap();
assert_eq!(debit_snapshot.status, DocumentStatus::Accepted);

// Validating if DebitNote acceptance didn't trigger additional payment.
log::info!("Waiting for payment...");
let payments = provider
.wait_for_payment(Some(&now), Duration::from_secs(1000), None, None)
.await?;
assert_eq!(payments.len(), 1);
assert_eq!(payments[0].amount, BigDecimal::from(1u64));
let last = payments[0].timestamp;

log::info!("Making sure no other payments were scheduled...");
assert!(provider
.wait_for_payment(Some(&last), Duration::from_secs(400), None, None)
.await
.unwrap_err()
.to_string()
.contains("Timeout"));
log::info!("Correct! Only single transaction was sent.");
Ok(())
}
Loading