From 546f2b8801b27f1a090ad6f6448f153460a607ef Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 16:43:10 +0000 Subject: [PATCH 1/4] feat(billing): Add ApplySponsorship checkout endpoint Sponsoring a contract on the billing platform had no service method, so getsentry orchestrated it outside the platform with an unwrapped invoicer call. ApplySponsorship moves a contract onto the package a sponsorship grants in a new term. The package catalog now names the package each sponsored type grants, so checkout does not hard-code package uids, and BillContractChange documents that a credit larger than the invoice's charges carries onto the new contract's balance. Refs REVENG-663 Co-Authored-By: Steve Konves --- .../v1/endpoint_apply_sponsorship.proto | 27 +++++++++++++++++++ .../v1/endpoint_bill_contract_change.proto | 4 ++- .../services/package/v1/package_catalog.proto | 16 +++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 proto/sentry_protos/billing/v1/services/checkout/v1/endpoint_apply_sponsorship.proto diff --git a/proto/sentry_protos/billing/v1/services/checkout/v1/endpoint_apply_sponsorship.proto b/proto/sentry_protos/billing/v1/services/checkout/v1/endpoint_apply_sponsorship.proto new file mode 100644 index 00000000..f79d6e05 --- /dev/null +++ b/proto/sentry_protos/billing/v1/services/checkout/v1/endpoint_apply_sponsorship.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package sentry_protos.billing.v1.services.checkout.v1; + +import "sentry_protos/billing/v1/common/v1/sponsored_type.proto"; + +// Moves an organization's contract onto the package a sponsorship grants, now, +// in a new term. The closing invoice settles accrued pay-as-you-go usage and +// credits the unused part of the current term; reservations, PAYG budgets, +// soft caps, and add-on activations bought on the paid plan are cleared rather +// than carried onto the sponsored package, which grants its own. +// +// A sponsorship that ends with its term, such as Education, also schedules the +// contract's move onto the free package at the next rollover, the same way +// CancelContract does. +// +// A partner-billed contract cannot be sponsored, nor can a sponsorship the +// latest package catalog grants no package for. +message ApplySponsorshipRequest { + uint64 organization_id = 1; + sentry_protos.billing.v1.common.v1.SponsoredType sponsored_type = 2; +} + +message ApplySponsorshipResponse { + // The invoice that closes the contract the sponsorship replaced. + uint64 invoice_id = 1; +} diff --git a/proto/sentry_protos/billing/v1/services/invoicer/v1/endpoint_bill_contract_change.proto b/proto/sentry_protos/billing/v1/services/invoicer/v1/endpoint_bill_contract_change.proto index 4a197f8a..e9f3bf06 100644 --- a/proto/sentry_protos/billing/v1/services/invoicer/v1/endpoint_bill_contract_change.proto +++ b/proto/sentry_protos/billing/v1/services/invoicer/v1/endpoint_bill_contract_change.proto @@ -12,7 +12,9 @@ import "sentry_protos/billing/v1/services/contract/v1/invoice.proto"; // the credit for unused time on the current plan itself, settles accrued // pay-as-you-go usage at the current contract's rates, applies monetary grants // and sales tax, closes the current contract immediately, and opens the new -// contract with the supplied change applied. +// contract with the supplied change applied. When the credit for unused time +// exceeds the invoice's charges, as it can on a move onto a cheaper package, +// the surplus is added to the new contract's balance rather than forgiven. message BillContractChangeRequest { uint64 contract_id = 1; // The plan-change line items billed alongside accrued usage; they take the diff --git a/proto/sentry_protos/billing/v1/services/package/v1/package_catalog.proto b/proto/sentry_protos/billing/v1/services/package/v1/package_catalog.proto index 33acf77a..54850985 100644 --- a/proto/sentry_protos/billing/v1/services/package/v1/package_catalog.proto +++ b/proto/sentry_protos/billing/v1/services/package/v1/package_catalog.proto @@ -2,12 +2,22 @@ syntax = "proto3"; package sentry_protos.billing.v1.services.package.v1; +import "sentry_protos/billing/v1/common/v1/sponsored_type.proto"; + // How a customer can start on a package. enum AcquisitionMode { ACQUISITION_MODE_UNSPECIFIED = 0; ACQUISITION_MODE_SELF_SERVE = 1; // customer can sign up on their own } +// The package a sponsorship grants. +message SponsoredPackageOption { + sentry_protos.billing.v1.common.v1.SponsoredType sponsored_type = 1; + + // uid of the granted option within the catalog's `packages`. + string package_uid = 2; +} + // A package a customer can subscribe to, such as the Team or Business package. // Carries only what a catalog needs beyond a bare reference; fetch full details // (title, pricing, supported intervals) with GetPackage / GetPackages. @@ -36,4 +46,10 @@ message PackageCatalog { // uid of the default option within `packages`: the one checkout pre-selects. string default_package_uid = 3; + + // The package each sponsorship grants, at most one per sponsored type. A + // sponsored type with no entry cannot be granted from this catalog, such as a + // partner sponsorship provisioned by the partner. Empty on catalogs of older + // package generations, which no longer take on new sponsorships. + repeated SponsoredPackageOption sponsored_packages = 4; } From f9afd822ffa15e2617d8f9dd2b6b4202e9abe6ec Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 20:12:27 +0000 Subject: [PATCH 2/4] chore: Regenerate Rust bindings --- Cargo.lock | 2 +- ..._protos.billing.v1.services.checkout.v1.rs | 25 +++++++++++++++++++ ..._protos.billing.v1.services.invoicer.v1.rs | 4 ++- ...y_protos.billing.v1.services.package.v1.rs | 15 +++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 09d2f89c..0af9158c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -717,7 +717,7 @@ checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "sentry_protos" -version = "0.90.0" +version = "0.91.0" dependencies = [ "prost", "prost-types", diff --git a/rust/src/sentry_protos.billing.v1.services.checkout.v1.rs b/rust/src/sentry_protos.billing.v1.services.checkout.v1.rs index 074b51cf..82cbfaef 100644 --- a/rust/src/sentry_protos.billing.v1.services.checkout.v1.rs +++ b/rust/src/sentry_protos.billing.v1.services.checkout.v1.rs @@ -1,4 +1,29 @@ // This file is @generated by prost-build. +/// Moves an organization's contract onto the package a sponsorship grants, now, +/// in a new term. The closing invoice settles accrued pay-as-you-go usage and +/// credits the unused part of the current term; reservations, PAYG budgets, +/// soft caps, and add-on activations bought on the paid plan are cleared rather +/// than carried onto the sponsored package, which grants its own. +/// +/// A sponsorship that ends with its term, such as Education, also schedules the +/// contract's move onto the free package at the next rollover, the same way +/// CancelContract does. +/// +/// A partner-billed contract cannot be sponsored, nor can a sponsorship the +/// latest package catalog grants no package for. +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ApplySponsorshipRequest { + #[prost(uint64, tag = "1")] + pub organization_id: u64, + #[prost(enumeration = "super::super::super::common::v1::SponsoredType", tag = "2")] + pub sponsored_type: i32, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ApplySponsorshipResponse { + /// The invoice that closes the contract the sponsorship replaced. + #[prost(uint64, tag = "1")] + pub invoice_id: u64, +} /// Schedules an organization's contract to end when the current billing period /// closes. The contract is downgraded onto the free package at the next /// rollover, and the reservations, PAYG budgets, and soft caps in effect are diff --git a/rust/src/sentry_protos.billing.v1.services.invoicer.v1.rs b/rust/src/sentry_protos.billing.v1.services.invoicer.v1.rs index c6ea3659..f307151c 100644 --- a/rust/src/sentry_protos.billing.v1.services.invoicer.v1.rs +++ b/rust/src/sentry_protos.billing.v1.services.invoicer.v1.rs @@ -5,7 +5,9 @@ /// the credit for unused time on the current plan itself, settles accrued /// pay-as-you-go usage at the current contract's rates, applies monetary grants /// and sales tax, closes the current contract immediately, and opens the new -/// contract with the supplied change applied. +/// contract with the supplied change applied. When the credit for unused time +/// exceeds the invoice's charges, as it can on a move onto a cheaper package, +/// the surplus is added to the new contract's balance rather than forgiven. #[derive(Clone, PartialEq, ::prost::Message)] pub struct BillContractChangeRequest { #[prost(uint64, tag = "1")] diff --git a/rust/src/sentry_protos.billing.v1.services.package.v1.rs b/rust/src/sentry_protos.billing.v1.services.package.v1.rs index 95663d27..7b365642 100644 --- a/rust/src/sentry_protos.billing.v1.services.package.v1.rs +++ b/rust/src/sentry_protos.billing.v1.services.package.v1.rs @@ -222,6 +222,15 @@ pub struct GetPackageResponse { #[prost(message, optional, tag = "1")] pub package_config: ::core::option::Option, } +/// The package a sponsorship grants. +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct SponsoredPackageOption { + #[prost(enumeration = "super::super::super::common::v1::SponsoredType", tag = "1")] + pub sponsored_type: i32, + /// uid of the granted option within the catalog's `packages`. + #[prost(string, tag = "2")] + pub package_uid: ::prost::alloc::string::String, +} /// A package a customer can subscribe to, such as the Team or Business package. /// Carries only what a catalog needs beyond a bare reference; fetch full details /// (title, pricing, supported intervals) with GetPackage / GetPackages. @@ -253,6 +262,12 @@ pub struct PackageCatalog { /// uid of the default option within `packages`: the one checkout pre-selects. #[prost(string, tag = "3")] pub default_package_uid: ::prost::alloc::string::String, + /// The package each sponsorship grants, at most one per sponsored type. A + /// sponsored type with no entry cannot be granted from this catalog, such as a + /// partner sponsorship provisioned by the partner. Empty on catalogs of older + /// package generations, which no longer take on new sponsorships. + #[prost(message, repeated, tag = "4")] + pub sponsored_packages: ::prost::alloc::vec::Vec, } /// How a customer can start on a package. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] From ae165776f2a8d50d541a7036cebd662a01a71259 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 20:17:23 +0000 Subject: [PATCH 3/4] ref(billing): Shorten ApplySponsorshipRequest description --- .../checkout/v1/endpoint_apply_sponsorship.proto | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/proto/sentry_protos/billing/v1/services/checkout/v1/endpoint_apply_sponsorship.proto b/proto/sentry_protos/billing/v1/services/checkout/v1/endpoint_apply_sponsorship.proto index f79d6e05..0d23e42f 100644 --- a/proto/sentry_protos/billing/v1/services/checkout/v1/endpoint_apply_sponsorship.proto +++ b/proto/sentry_protos/billing/v1/services/checkout/v1/endpoint_apply_sponsorship.proto @@ -4,18 +4,8 @@ package sentry_protos.billing.v1.services.checkout.v1; import "sentry_protos/billing/v1/common/v1/sponsored_type.proto"; -// Moves an organization's contract onto the package a sponsorship grants, now, -// in a new term. The closing invoice settles accrued pay-as-you-go usage and -// credits the unused part of the current term; reservations, PAYG budgets, -// soft caps, and add-on activations bought on the paid plan are cleared rather -// than carried onto the sponsored package, which grants its own. -// -// A sponsorship that ends with its term, such as Education, also schedules the -// contract's move onto the free package at the next rollover, the same way -// CancelContract does. -// -// A partner-billed contract cannot be sponsored, nor can a sponsorship the -// latest package catalog grants no package for. +// Moves an organization's contract onto the package a sponsorship grants, +// starting a new term. message ApplySponsorshipRequest { uint64 organization_id = 1; sentry_protos.billing.v1.common.v1.SponsoredType sponsored_type = 2; From 282760058146aa96888b376973da607a27024ed2 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 20:18:33 +0000 Subject: [PATCH 4/4] chore: Regenerate Rust bindings --- ...entry_protos.billing.v1.services.checkout.v1.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/rust/src/sentry_protos.billing.v1.services.checkout.v1.rs b/rust/src/sentry_protos.billing.v1.services.checkout.v1.rs index 82cbfaef..6e38a088 100644 --- a/rust/src/sentry_protos.billing.v1.services.checkout.v1.rs +++ b/rust/src/sentry_protos.billing.v1.services.checkout.v1.rs @@ -1,16 +1,6 @@ // This file is @generated by prost-build. -/// Moves an organization's contract onto the package a sponsorship grants, now, -/// in a new term. The closing invoice settles accrued pay-as-you-go usage and -/// credits the unused part of the current term; reservations, PAYG budgets, -/// soft caps, and add-on activations bought on the paid plan are cleared rather -/// than carried onto the sponsored package, which grants its own. -/// -/// A sponsorship that ends with its term, such as Education, also schedules the -/// contract's move onto the free package at the next rollover, the same way -/// CancelContract does. -/// -/// A partner-billed contract cannot be sponsored, nor can a sponsorship the -/// latest package catalog grants no package for. +/// Moves an organization's contract onto the package a sponsorship grants, +/// starting a new term. #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct ApplySponsorshipRequest { #[prost(uint64, tag = "1")]