diff --git a/Cargo.lock b/Cargo.lock index 56fa1bdd..20d57b06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -717,7 +717,7 @@ checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "sentry_protos" -version = "0.81.1" +version = "0.82.0" dependencies = [ "prost", "prost-types", diff --git a/proto/sentry_protos/billing/v1/services/contract/v1/invoice.proto b/proto/sentry_protos/billing/v1/services/contract/v1/invoice.proto index 4896bbba..92a77602 100644 --- a/proto/sentry_protos/billing/v1/services/contract/v1/invoice.proto +++ b/proto/sentry_protos/billing/v1/services/contract/v1/invoice.proto @@ -6,6 +6,15 @@ import "google/protobuf/timestamp.proto"; import "sentry_protos/billing/v1/common/v1/address.proto"; import "sentry_protos/billing/v1/common/v1/external_billing_provider.proto"; +enum InvoiceKind { + // Invoices written before kinds existed; these are all contract invoices. + INVOICE_KIND_UNSPECIFIED = 0; + // Created or rolled over alongside a contract. + INVOICE_KIND_CONTRACT = 1; + // Billed to an organization outside of any contract, charged immediately. + INVOICE_KIND_ONE_OFF = 2; +} + // This is not the same as LineItemDetails, it includes // items not related to the package such as tax. Each line item may carry an // optional type that classifies it (e.g. "tax") so consumers can treat it @@ -17,6 +26,9 @@ message InvoiceLineItem { // Optional classifier for the line item, e.g. "tax". Unset for ordinary // priced line items. optional string type = 3; + // Caller-defined identifier for what this line bills, set on one-off + // invoice lines. Opaque to the platform. + optional string external_id = 4; } message Invoice { @@ -52,4 +64,15 @@ message Invoice { // (e.g. pre-migration shadow rollovers and the $0 migration stub). Default // false means the invoice is visible on receipts. bool hide_from_customer_receipts = 15; + + InvoiceKind kind = 16; + + // Caller-supplied key that makes one-off invoice creation safe to retry. + // Unique per organization and client_id; unset on contract invoices. + optional string idempotency_key = 17; + + // Identifies the service that requested a one-off invoice, so it can + // recognize its own invoices and map idempotency_key back to its own + // records. Unset on contract invoices. + optional string client_id = 18; } diff --git a/proto/sentry_protos/billing/v1/services/events/v1/billing_platform_event.proto b/proto/sentry_protos/billing/v1/services/events/v1/billing_platform_event.proto index 77d39408..5146bc7b 100644 --- a/proto/sentry_protos/billing/v1/services/events/v1/billing_platform_event.proto +++ b/proto/sentry_protos/billing/v1/services/events/v1/billing_platform_event.proto @@ -5,6 +5,7 @@ package sentry_protos.billing.v1.services.events.v1; import "google/protobuf/timestamp.proto"; import "sentry_protos/billing/v1/services/events/v1/billing_alert_evaluation_requested.proto"; import "sentry_protos/billing/v1/services/events/v1/contract_rolled_over.proto"; +import "sentry_protos/billing/v1/services/events/v1/one_off_invoice_updated.proto"; import "sentry_protos/billing/v1/services/events/v1/organization_over_usage.proto"; import "sentry_protos/billing/v1/services/events/v1/organization_package_changed.proto"; import "sentry_protos/billing/v1/services/events/v1/organization_payg_changed.proto"; @@ -25,6 +26,7 @@ message EventPayload { sentry_protos.billing.v1.services.events.v1.ContractRolledOver contract_rolled_over = 5; sentry_protos.billing.v1.services.events.v1.OrganizationPackageChanged organization_package_changed = 6; sentry_protos.billing.v1.services.events.v1.BillingAlertEvaluationRequested billing_alert_evaluation_requested = 7; + sentry_protos.billing.v1.services.events.v1.OneOffInvoiceUpdated one_off_invoice_updated = 8; } } diff --git a/proto/sentry_protos/billing/v1/services/events/v1/one_off_invoice_updated.proto b/proto/sentry_protos/billing/v1/services/events/v1/one_off_invoice_updated.proto new file mode 100644 index 00000000..539e50b1 --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/events/v1/one_off_invoice_updated.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.events.v1; + +// Published when a one-off invoice changes state, so the service that +// requested it can grant, withhold or claw back what was purchased. +message OneOffInvoiceUpdated { + enum Outcome { + OUTCOME_UNSPECIFIED = 0; + OUTCOME_PAID = 1; + // Retries ran out or the invoice was closed. It can still be paid later, + // which publishes OUTCOME_PAID. + OUTCOME_CLOSED_UNPAID = 2; + OUTCOME_REFUNDED = 3; + } + + uint64 invoice_id = 1; + string invoice_guid = 2; + string idempotency_key = 3; + Outcome outcome = 4; + // PAID: the invoice's amount_billed. REFUNDED: the amount of this refund, + // for the invoice as a whole rather than per line. + uint64 amount_cents = 5; + // The service that requested the invoice. A handler acts only on events + // carrying its own client_id; idempotency_key is unique within it. + string client_id = 6; +} diff --git a/rust/src/sentry_protos.billing.v1.services.contract.v1.rs b/rust/src/sentry_protos.billing.v1.services.contract.v1.rs index 3d3d61cc..421b536f 100644 --- a/rust/src/sentry_protos.billing.v1.services.contract.v1.rs +++ b/rust/src/sentry_protos.billing.v1.services.contract.v1.rs @@ -547,6 +547,10 @@ pub struct InvoiceLineItem { /// priced line items. #[prost(string, optional, tag = "3")] pub r#type: ::core::option::Option<::prost::alloc::string::String>, + /// Caller-defined identifier for what this line bills, set on one-off + /// invoice lines. Opaque to the platform. + #[prost(string, optional, tag = "4")] + pub external_id: ::core::option::Option<::prost::alloc::string::String>, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct Invoice { @@ -595,6 +599,49 @@ pub struct Invoice { /// false means the invoice is visible on receipts. #[prost(bool, tag = "15")] pub hide_from_customer_receipts: bool, + #[prost(enumeration = "InvoiceKind", tag = "16")] + pub kind: i32, + /// Caller-supplied key that makes one-off invoice creation safe to retry. + /// Unique per organization and client_id; unset on contract invoices. + #[prost(string, optional, tag = "17")] + pub idempotency_key: ::core::option::Option<::prost::alloc::string::String>, + /// Identifies the service that requested a one-off invoice, so it can + /// recognize its own invoices and map idempotency_key back to its own + /// records. Unset on contract invoices. + #[prost(string, optional, tag = "18")] + pub client_id: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum InvoiceKind { + /// Invoices written before kinds existed; these are all contract invoices. + Unspecified = 0, + /// Created or rolled over alongside a contract. + Contract = 1, + /// Billed to an organization outside of any contract, charged immediately. + OneOff = 2, +} +impl InvoiceKind { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "INVOICE_KIND_UNSPECIFIED", + Self::Contract => "INVOICE_KIND_CONTRACT", + Self::OneOff => "INVOICE_KIND_ONE_OFF", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "INVOICE_KIND_UNSPECIFIED" => Some(Self::Unspecified), + "INVOICE_KIND_CONTRACT" => Some(Self::Contract), + "INVOICE_KIND_ONE_OFF" => Some(Self::OneOff), + _ => None, + } + } } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct OptionValue { diff --git a/rust/src/sentry_protos.billing.v1.services.events.v1.rs b/rust/src/sentry_protos.billing.v1.services.events.v1.rs index 01cea830..ae6d34d7 100644 --- a/rust/src/sentry_protos.billing.v1.services.events.v1.rs +++ b/rust/src/sentry_protos.billing.v1.services.events.v1.rs @@ -6,6 +6,74 @@ pub struct BillingAlertEvaluationRequested {} /// Future: uint64 new_contract_id = 2; #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct ContractRolledOver {} +/// Published when a one-off invoice changes state, so the service that +/// requested it can grant, withhold or claw back what was purchased. +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct OneOffInvoiceUpdated { + #[prost(uint64, tag = "1")] + pub invoice_id: u64, + #[prost(string, tag = "2")] + pub invoice_guid: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub idempotency_key: ::prost::alloc::string::String, + #[prost(enumeration = "one_off_invoice_updated::Outcome", tag = "4")] + pub outcome: i32, + /// PAID: the invoice's amount_billed. REFUNDED: the amount of this refund, + /// for the invoice as a whole rather than per line. + #[prost(uint64, tag = "5")] + pub amount_cents: u64, + /// The service that requested the invoice. A handler acts only on events + /// carrying its own client_id; idempotency_key is unique within it. + #[prost(string, tag = "6")] + pub client_id: ::prost::alloc::string::String, +} +/// Nested message and enum types in `OneOffInvoiceUpdated`. +pub mod one_off_invoice_updated { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum Outcome { + Unspecified = 0, + Paid = 1, + /// Retries ran out or the invoice was closed. It can still be paid later, + /// which publishes OUTCOME_PAID. + ClosedUnpaid = 2, + Refunded = 3, + } + impl Outcome { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "OUTCOME_UNSPECIFIED", + Self::Paid => "OUTCOME_PAID", + Self::ClosedUnpaid => "OUTCOME_CLOSED_UNPAID", + Self::Refunded => "OUTCOME_REFUNDED", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "OUTCOME_UNSPECIFIED" => Some(Self::Unspecified), + "OUTCOME_PAID" => Some(Self::Paid), + "OUTCOME_CLOSED_UNPAID" => Some(Self::ClosedUnpaid), + "OUTCOME_REFUNDED" => Some(Self::Refunded), + _ => None, + } + } + } +} #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct OrganizationOverUsage {} /// Published when an organization's contract moves to a different package, e.g. a @@ -33,7 +101,7 @@ pub struct EventMeta { } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct EventPayload { - #[prost(oneof = "event_payload::Payload", tags = "1, 2, 3, 4, 5, 6, 7")] + #[prost(oneof = "event_payload::Payload", tags = "1, 2, 3, 4, 5, 6, 7, 8")] pub payload: ::core::option::Option, } /// Nested message and enum types in `EventPayload`. @@ -54,6 +122,8 @@ pub mod event_payload { OrganizationPackageChanged(super::OrganizationPackageChanged), #[prost(message, tag = "7")] BillingAlertEvaluationRequested(super::BillingAlertEvaluationRequested), + #[prost(message, tag = "8")] + OneOffInvoiceUpdated(super::OneOffInvoiceUpdated), } } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]