From 280e77c5f3d34b19eb5ff4258d4ba4c8c7f49a12 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 15 Jun 2026 14:46:41 -0800 Subject: [PATCH 1/2] feat(billing-platform): add batched ChargeService.list_charges_for_invoice_ids (REVENG-157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a batched endpoint that returns ``PlatformCharge`` (the canonical projection, with refunds embedded via ``PlatformCharge.refunds`` from sentry-protos#303) for every charge tied to any of a list of invoices. Avoids the N+1 of fetching refunds (or charges) per invoice when rendering paginated invoice lists. - **`services/charge/v1/endpoint_list_charges.proto`** — new ``ListChargesForInvoiceIdsRequest`` (``repeated uint64 invoice_ids``) + ``ListChargesForInvoiceIdsResponse`` (flat list of ``PlatformCharge`` ordered by ``(invoice_id, date_added)``). The singleton ``ListChargesForInvoiceRequest`` / ``Charge`` slim type in the same file is left as-is for now (currently unused; deferring deprecation to a separate proto-policy follow-up). Picked the charges-with-embedded-refunds shape over a separate ``list_refunds_by_invoice_ids`` because it leverages the existing ``PlatformCharge.refunds`` field and sets up admin invoice surfaces that want full charge + refund detail in one round trip. Consumers that only need refund totals (e.g. customer invoice list) still get what they need by summing ``charge.refunds[*].amount_cents`` grouped by ``charge.invoice_id``. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../charge/v1/endpoint_list_charges.proto | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/proto/sentry_protos/billing/v1/services/charge/v1/endpoint_list_charges.proto b/proto/sentry_protos/billing/v1/services/charge/v1/endpoint_list_charges.proto index 4fa90da4..c6354302 100644 --- a/proto/sentry_protos/billing/v1/services/charge/v1/endpoint_list_charges.proto +++ b/proto/sentry_protos/billing/v1/services/charge/v1/endpoint_list_charges.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package sentry_protos.billing.v1.services.charge.v1; +import "sentry_protos/billing/v1/services/charge/v1/charge.proto"; + message Charge { uint64 amount_cents = 1; bool paid = 2; @@ -17,3 +19,22 @@ message ListChargesForInvoiceRequest { message ListChargesForInvoiceResponse { repeated Charge charges = 1; } + +// Batched variant for paginated invoice surfaces. Returns +// ``PlatformCharge`` (the canonical projection, with embedded refunds via +// ``PlatformCharge.refunds``) for every charge tied to any of the +// requested invoices, so callers get full per-invoice charge + refund +// state in one round trip. Avoids the N+1 of fetching refunds or +// charges per invoice when rendering paginated invoice lists. +message ListChargesForInvoiceIdsRequest { + repeated uint64 invoice_ids = 1; +} + +message ListChargesForInvoiceIdsResponse { + // Flat list of charges across all requested invoices, ordered by + // ``(invoice_id, date_added)`` ascending. Each ``PlatformCharge`` + // carries its ``invoice_id`` and embedded ``refunds``; group by + // ``charge.invoice_id`` to reconstruct per-invoice state. Invoices + // with no charges simply don't appear. + repeated PlatformCharge charges = 1; +} From 1d9032dedb40ddb32dcb361dc6879299a59de00d Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 17:16:52 +0000 Subject: [PATCH 2/2] chore: Regenerate Rust bindings --- Cargo.lock | 2 +- ...ry_protos.billing.v1.services.charge.v1.rs | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ad5ee1d4..e8698790 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -717,7 +717,7 @@ checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "sentry_protos" -version = "0.30.0" +version = "0.30.1" dependencies = [ "prost", "prost-types", diff --git a/rust/src/sentry_protos.billing.v1.services.charge.v1.rs b/rust/src/sentry_protos.billing.v1.services.charge.v1.rs index 07f1219c..bc6c75c3 100644 --- a/rust/src/sentry_protos.billing.v1.services.charge.v1.rs +++ b/rust/src/sentry_protos.billing.v1.services.charge.v1.rs @@ -168,6 +168,27 @@ pub struct ListChargesForInvoiceResponse { #[prost(message, repeated, tag = "1")] pub charges: ::prost::alloc::vec::Vec, } +/// Batched variant for paginated invoice surfaces. Returns +/// `PlatformCharge` (the canonical projection, with embedded refunds via +/// `PlatformCharge.refunds`) for every charge tied to any of the +/// requested invoices, so callers get full per-invoice charge + refund +/// state in one round trip. Avoids the N+1 of fetching refunds or +/// charges per invoice when rendering paginated invoice lists. +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ListChargesForInvoiceIdsRequest { + #[prost(uint64, repeated, tag = "1")] + pub invoice_ids: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListChargesForInvoiceIdsResponse { + /// Flat list of charges across all requested invoices, ordered by + /// `(invoice_id, date_added)` ascending. Each `PlatformCharge` + /// carries its `invoice_id` and embedded `refunds`; group by + /// `charge.invoice_id` to reconstruct per-invoice state. Invoices + /// with no charges simply don't appear. + #[prost(message, repeated, tag = "1")] + pub charges: ::prost::alloc::vec::Vec, +} /// Lists every recorded refund associated with the charges for a single /// platform invoice. Callers in the presentation layer use this to render /// invoice-level refund state without crossing the charge service boundary.