Remove reconciliation step
This commit is contained in:
@@ -284,53 +284,6 @@ impl Billing {
|
||||
}
|
||||
}
|
||||
|
||||
/// If an open invoice's bolt11 has settled on Lightning, record it as paid.
|
||||
/// This is the shared settlement path for both NWC payments that landed late
|
||||
/// and manual payments; it is driven by the actual Lightning settlement
|
||||
/// rather than our local state, so it self-corrects if a previous attempt
|
||||
/// updated Stripe but not our row (or vice versa).
|
||||
pub async fn reconcile_invoice(&self, invoice: &StripeInvoice) -> Result<StripeInvoice> {
|
||||
if invoice.status != "open" {
|
||||
return Ok(invoice.clone());
|
||||
}
|
||||
|
||||
let Some(row) = self.query.get_lightning_invoice(&invoice.id).await? else {
|
||||
return Ok(invoice.clone());
|
||||
};
|
||||
|
||||
let settled = match self.wallet.is_settled(&row.bolt11).await {
|
||||
Ok(settled) => settled,
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
error = %error,
|
||||
stripe_invoice_id = %invoice.id,
|
||||
"failed to look up bolt11 invoice settlement"
|
||||
);
|
||||
return Ok(invoice.clone());
|
||||
}
|
||||
};
|
||||
|
||||
if !settled {
|
||||
return Ok(invoice.clone());
|
||||
}
|
||||
|
||||
if let Err(error) = self.settle_invoice(&invoice.id, &row.tenant_pubkey, "manual").await {
|
||||
tracing::warn!(
|
||||
error = %error,
|
||||
stripe_invoice_id = %invoice.id,
|
||||
"failed to mark settled bolt11 invoice as paid"
|
||||
);
|
||||
}
|
||||
|
||||
// The invoice existed a moment ago; if Stripe suddenly returns 404, fall
|
||||
// back to the pre-reconcile snapshot rather than failing the request.
|
||||
Ok(self
|
||||
.stripe
|
||||
.get_invoice(&invoice.id)
|
||||
.await?
|
||||
.unwrap_or_else(|| invoice.clone()))
|
||||
}
|
||||
|
||||
/// Record a settled bolt11 invoice as paid by `method`, mark the Stripe
|
||||
/// invoice paid out of band, and clear any lingering NWC error. The Stripe
|
||||
/// call is idempotent across retries, and `mark_lightning_invoice_paid` is
|
||||
|
||||
@@ -26,13 +26,22 @@ pub async fn get_invoice(
|
||||
AuthedPubkey(auth): AuthedPubkey,
|
||||
Path(id): Path<String>,
|
||||
) -> ApiResult {
|
||||
let (invoice, _tenant) = load_authorized_invoice(&api, &auth, &id).await?;
|
||||
let Some(invoice) = api.stripe.get_invoice(stripe_invoice_id).await.map_err(internal)? else {
|
||||
return Err(not_found("invoice not found"));
|
||||
};
|
||||
|
||||
let invoice = api
|
||||
.billing
|
||||
.reconcile_invoice(&invoice)
|
||||
let Some(_) = api
|
||||
.query
|
||||
.get_tenant_by_stripe_customer_id(&invoice.customer)
|
||||
.await
|
||||
.map_err(internal)?;
|
||||
.map_err(internal)?
|
||||
else {
|
||||
return Err(not_found("invoice not found"));
|
||||
};
|
||||
|
||||
api.require_admin_or_tenant(auth, &tenant.pubkey)?;
|
||||
|
||||
Ok((invoice, tenant))
|
||||
|
||||
ok(invoice)
|
||||
}
|
||||
@@ -42,38 +51,6 @@ pub async fn get_lightning_invoice(
|
||||
AuthedPubkey(auth): AuthedPubkey,
|
||||
Path(id): Path<String>,
|
||||
) -> ApiResult {
|
||||
let (invoice, tenant) = load_authorized_invoice(&api, &auth, &id).await?;
|
||||
|
||||
// Settle first: this checks the currently-stored bolt11 against the wallet,
|
||||
// so a payment that landed before expiry is always caught before we'd
|
||||
// consider regenerating below.
|
||||
let invoice = api
|
||||
.billing
|
||||
.reconcile_invoice(&invoice)
|
||||
.await
|
||||
.map_err(internal)?;
|
||||
|
||||
if invoice.status != "open" {
|
||||
return Err(bad_request("invoice-not-open", "invoice is not open"));
|
||||
}
|
||||
|
||||
let invoice = api
|
||||
.billing
|
||||
.ensure_lightning_invoice(&invoice.id, &tenant.pubkey, invoice.amount_due, &invoice.currency)
|
||||
.await
|
||||
.map_err(internal)?;
|
||||
|
||||
ok(serde_json::json!(invoice))
|
||||
}
|
||||
|
||||
/// Fetch a Stripe invoice and the tenant that owns it, enforcing that the
|
||||
/// caller is that tenant (or an admin). Returns 404 if the invoice or its
|
||||
/// tenant can't be found.
|
||||
async fn load_authorized_invoice(
|
||||
api: &Api,
|
||||
auth: &str,
|
||||
stripe_invoice_id: &str,
|
||||
) -> Result<(crate::stripe::StripeInvoice, crate::models::Tenant), crate::web::ApiError> {
|
||||
let Some(invoice) = api.stripe.get_invoice(stripe_invoice_id).await.map_err(internal)? else {
|
||||
return Err(not_found("invoice not found"));
|
||||
};
|
||||
@@ -87,7 +64,11 @@ async fn load_authorized_invoice(
|
||||
return Err(not_found("invoice not found"));
|
||||
};
|
||||
|
||||
api.require_admin_or_tenant(auth, &tenant.pubkey)?;
|
||||
let lightning_invoice = api
|
||||
.billing
|
||||
.ensure_lightning_invoice(&invoice.id, &tenant.pubkey, invoice.amount_due, &invoice.currency)
|
||||
.await
|
||||
.map_err(internal)?;
|
||||
|
||||
Ok((invoice, tenant))
|
||||
ok(serde_json::json!(lightning_invoice))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user