crates/monetize-server/src/services/reconcile_admin.rs
//! **`ReconcileAdmin.ListPaidAtVendor`** — the operator's read of what the
//! vendor says it took and monetize did not fulfil.
//!
//! One rpc, no write side. The store is [`crate::reconcile::ReconcileStore`],
//! written by the poller's reconcile pass alone, from the vendor's answer to
//! OUR authenticated read. An operator who acts on a row does so at the vendor
//! (a refund, by hand — nothing here refunds) or through `Entitlement.Set`,
//! where the act is recorded as an operator's.
//!
//! # Why a list as well as the bus
//!
//! The same reason `CallbackAdmin.ListRefusals` gives: every new case is also
//! published on `Events.Watch`, which lights up a console that is already
//! open, but the console that matters is the one opened after the fact, and
//! the bus is a ring with no log. "Tell me as it happens" and "tell me what
//! happened" are two questions.
use std::sync::Arc;
use monetize_proto::reconcile_admin_server::ReconcileAdmin;
use monetize_proto::{self as pb};
use tonic::{Request, Response, Status};
use super::blocking;
use crate::state::AppState;
pub struct ReconcileAdminService(pub Arc<AppState>);
#[tonic::async_trait]
impl ReconcileAdmin for ReconcileAdminService {
async fn list_paid_at_vendor(
&self,
req: Request<pb::ListPaidAtVendorRequest>,
) -> Result<Response<pb::ListPaidAtVendorResponse>, Status> {
let limit = req.into_inner().limit as usize;
let out = blocking(&self.0, move |s| {
let stats = s
.reconcile
.stats()
.map_err(|e| Status::internal(format!("reconcile store: {e}")))?;
let cases = s
.reconcile
.list(limit)
.map_err(|e| Status::internal(format!("reconcile store: {e}")))?;
Ok(pb::ListPaidAtVendorResponse {
cases: cases
.iter()
.map(crate::reconcile::PaidUnfulfilled::to_pb)
.collect(),
total: stats.total,
needs_operator: stats.needs_operator,
settled: stats.settled,
last_reconcile_unix_ms: stats.last_pass_unix_ms,
})
})
.await?;
Ok(Response::new(out))
}
}