Skip to content

Commit

Permalink
add mark_invoice_as_received endpoint (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
ofux authored Jan 3, 2024
1 parent d23f40f commit 4655664
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 1 deletion.
1 change: 1 addition & 0 deletions api/src/presentation/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ pub fn serve(
routes::issues::fetch_issue_by_repo_owner_name_issue_number,
routes::pull_requests::fetch_pull_request,
routes::payment::request_payment,
routes::payment::mark_invoice_as_received,
routes::payment::cancel_payment,
routes::payment::receipts::create,
routes::sponsors::create_sponsor,
Expand Down
2 changes: 1 addition & 1 deletion api/src/presentation/http/routes/payment/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod request;
pub use request::request_payment;
pub use request::{mark_invoice_as_received, request_payment};

pub mod cancel;
pub use cancel::cancel_payment;
Expand Down
41 changes: 41 additions & 0 deletions api/src/presentation/http/routes/payment/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,44 @@ pub async fn request_payment(
command_id,
}))
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InvoiceReceivedRequest {
payments: Vec<PaymentId>,
}

#[put(
"/payments/invoiceReceivedAt",
data = "<request>",
format = "application/json"
)]
pub async fn mark_invoice_as_received(
_api_key: ApiKey,
request: Json<InvoiceReceivedRequest>,
role: Role,
payment_repository: &State<AggregateRepository<Payment>>,
invoice_payment_usecase: application::payment::invoice::Usecase,
) -> Result<(), HttpApiProblem> {
let InvoiceReceivedRequest { payments } = request.into_inner();

let caller_permissions = role.to_permissions((*payment_repository).clone());

if payments
.iter()
.any(|payment_id| !caller_permissions.can_mark_invoice_as_received_for_payment(payment_id))
{
return Err(HttpApiProblem::new(StatusCode::UNAUTHORIZED)
.title("Only recipient can mark invoice as received"));
}

invoice_payment_usecase.mark_invoice_as_received(payments).await.map_err(|e| {
{
HttpApiProblem::new(StatusCode::INTERNAL_SERVER_ERROR)
.title("Unable to mark_invoice_as_received")
.detail(e.to_string())
}
})?;

Ok(())
}
22 changes: 22 additions & 0 deletions api/src/presentation/http/usecases/payment/invoice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use anyhow::Error;
use rocket::{
outcome::try_outcome,
request::{FromRequest, Outcome},
Request,
};

use crate::{
application::payment::invoice::Usecase, presentation::http::usecases::FromRocketState,
};

#[async_trait]
impl<'r> FromRequest<'r> for Usecase {
type Error = Error;

async fn from_request(request: &'r Request<'_>) -> Outcome<Usecase, Self::Error> {
Outcome::Success(Self::new(
try_outcome!(FromRocketState::from_state(request.rocket())),
try_outcome!(FromRocketState::from_state(request.rocket())),
))
}
}
1 change: 1 addition & 0 deletions api/src/presentation/http/usecases/payment/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod cancel;
mod invoice;
mod process;
mod request;
101 changes: 101 additions & 0 deletions api/tests/payment_it.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ pub async fn payment_processing(docker: &'static Cli) {
.await
.expect("admin_can_add_a_lords_receipt");
test.admin_can_add_a_usdc_receipt().await.expect("admin_can_add_a_usdc_receipt");

test.recipient_can_mark_invoice_as_received()
.await
.expect("recipient_can_mark_invoice_as_received");
}

struct Test<'a> {
Expand Down Expand Up @@ -1403,4 +1407,101 @@ impl<'a> Test<'a> {

Ok(())
}

async fn recipient_can_mark_invoice_as_received(&mut self) -> Result<()> {
info!("recipient_can_mark_invoice_as_received");

// Given
let project_id = ProjectId::new();
let budget_id = BudgetId::new();
let payment_id = PaymentId::new();

self.context
.event_publisher
.publish_many(&[
ProjectEvent::Created { id: project_id }.into(),
ProjectEvent::BudgetLinked {
id: project_id,
budget_id,
currency: currencies::USDC,
}
.into(),
BudgetEvent::Created {
id: budget_id,
currency: currencies::USDC,
}
.into(),
BudgetEvent::Allocated {
id: budget_id,
amount: Decimal::from(1_000),
sponsor_id: None,
}
.into(),
BudgetEvent::Spent {
id: budget_id,
amount: Decimal::from(100),
}
.into(),
PaymentEvent::Requested {
id: payment_id,
project_id,
requestor_id: UserId::new(),
recipient_id: GithubUserId::from(43467246u64),
amount: Amount::from_decimal(dec!(100), currencies::USD),
duration_worked: Some(Duration::hours(2)),
reason: PaymentReason { work_items: vec![] },
requested_at: Utc::now().naive_utc(),
}
.into(),
])
.await?;

let request = json!({
"payments": vec![payment_id],
});

let before = Utc::now().naive_utc();

// When
let response = self
.context
.http_client
.put("/api/payments/invoiceReceivedAt")
.header(ContentType::JSON)
.header(api_key_header())
.header(Header::new("x-hasura-role", "registered_user"))
.header(Header::new(
"Authorization",
format!("Bearer {}", jwt(None)),
))
.body(request.to_string())
.dispatch()
.await;

// Then
assert_eq!(
response.status(),
Status::Ok,
"{}",
response.into_string().await.unwrap_or_default()
);

let after = Utc::now().naive_utc();

assert_matches!(
self.context.amqp.listen(EXCHANGE_NAME).await.unwrap(),
Event::Payment(event) => {
assert_matches!(event, PaymentEvent::InvoiceReceived {
id,
received_at
} => {
assert_eq!(id, payment_id);
assert!(received_at > before);
assert!(received_at < after);
});
}
);

Ok(())
}
}

0 comments on commit 4655664

Please sign in to comment.