Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions proto/sentry_protos/billing/v1/common/v1/stripe_charge.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ message PaymentMethodDetails {
}
}

// A snapshot of a single Stripe refund attached to a charge. Conveys
// per-refund metadata so handlers can record refunds individually and
// dedupe by ``id``; the aggregate ``amount_refunded`` on ``StripeCharge``
// alone is not enough for idempotent ingestion.
message StripeRefund {
// Stripe id of the refund (e.g. "re_xxx").
string id = 1;
// Refund amount in the charge's smallest currency unit (cents for USD).
uint64 amount = 2;
Comment thread
armcknight marked this conversation as resolved.
// Stripe-supplied reason (e.g. "requested_by_customer", "duplicate",
// "fraudulent"). Unset when Stripe did not provide one.
optional string reason = 3;
}

// A snapshot of a Stripe charge object. Used as the payload when reacting
// to Stripe webhook events.
message StripeCharge {
Expand All @@ -26,4 +40,8 @@ message StripeCharge {
int64 created_st = 6;
optional string failure_code = 7;
PaymentMethodDetails payment_method_details = 8;
// Per-refund records attached to this charge. Empty when the charge has
// no refunds. The list reflects the state of refunds at the time the
// webhook was emitted.
repeated StripeRefund refunds = 9;
}
32 changes: 30 additions & 2 deletions proto/sentry_protos/billing/v1/services/charge/v1/charge.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,35 @@ message PlatformCharge {
bool paid = 4;
uint64 amount = 5;
optional string failure_code = 6;
bool refunded = 7;
uint64 amount_refunded = 8;
// DEPRECATED: Source of truth for refund state is ``refunds`` below.
// Consumers should check ``len(refunds) > 0`` (or
// ``sum(refunds[*].amount_cents) == amount``) instead. Left in the proto
// because ``sentry-protos`` policy disallows field deletion. Not
// populated by current producers.
bool refunded = 7 [deprecated = true];
// DEPRECATED: Source of truth for refund amount is the sum of
// ``refunds[*].amount_cents`` below. Left in the proto because
// ``sentry-protos`` policy disallows field deletion. Not populated by
// current producers.
uint64 amount_refunded = 8 [deprecated = true];
optional string card_last_4 = 9;
// Recorded refunds against this charge, ordered by ``date_added_st``
// ascending. Source of truth for refund state on a charge -- consumers
// sum ``refunds[*].amount_cents`` if they need the aggregate, and
// ``len(refunds) > 0`` (or sum == amount) signals "refunded."
Comment thread
armcknight marked this conversation as resolved.
repeated PlatformRefund refunds = 10;
}

// Canonical projection of a stored platform refund. One row per recorded
// refund against a ``PlatformCharge``.
message PlatformRefund {
// Stripe id of the refund (e.g. "re_xxx").
string stripe_id = 1;
uint64 organization_id = 2;
// Refund amount in cents.
uint64 amount_cents = 3;
// Stripe-supplied refund reason. Unset when Stripe did not provide one.
optional string reason = 4;
// Unix epoch seconds when the refund was recorded by the platform.
int64 date_added_st = 5;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
syntax = "proto3";

package sentry_protos.billing.v1.services.charge.v1;

import "sentry_protos/billing/v1/common/v1/stripe_charge.proto";
import "sentry_protos/billing/v1/services/charge/v1/charge.proto";

// Records platform refunds for a Stripe charge from a webhook payload.
// Mirrors the contents of ``stripe_charge.refunds`` as ``PlatformRefund``
// rows idempotently keyed by Stripe refund id, and syncs the aggregate
// ``amount_refunded`` / ``refunded`` state on the stored ``PlatformCharge``.
Comment thread
cursor[bot] marked this conversation as resolved.
// Called by the presentation layer that owns the ``charge.refunded``
// webhook handler; the charge service does not call Stripe in this path
// (Stripe initiated the refund).
message RecordChargeRefundsRequest {
sentry_protos.billing.v1.common.v1.StripeCharge stripe_charge = 1;
}

message RecordChargeRefundsResponse {
// Unset when no platform charge exists for ``stripe_charge.id``. Callers
// use this to distinguish platform charges from legacy charges. When set,
// ``charge.refunds`` carries the platform refund rows that were recorded
// or already existed, ordered by ``date_added_st`` ascending.
optional PlatformCharge charge = 1;
}
24 changes: 23 additions & 1 deletion rust/src/sentry_protos.billing.v1.common.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,9 +384,26 @@ pub mod payment_method_details {
Card(super::Card),
}
}
/// A snapshot of a single Stripe refund attached to a charge. Conveys
/// per-refund metadata so handlers can record refunds individually and
/// dedupe by `id`; the aggregate `amount_refunded` on `StripeCharge`
/// alone is not enough for idempotent ingestion.
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct StripeRefund {
/// Stripe id of the refund (e.g. "re_xxx").
#[prost(string, tag = "1")]
pub id: ::prost::alloc::string::String,
/// Refund amount in the charge's smallest currency unit (cents for USD).
#[prost(uint64, tag = "2")]
pub amount: u64,
/// Stripe-supplied reason (e.g. "requested_by_customer", "duplicate",
/// "fraudulent"). Unset when Stripe did not provide one.
#[prost(string, optional, tag = "3")]
pub reason: ::core::option::Option<::prost::alloc::string::String>,
}
/// A snapshot of a Stripe charge object. Used as the payload when reacting
/// to Stripe webhook events.
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StripeCharge {
#[prost(string, tag = "1")]
pub id: ::prost::alloc::string::String,
Expand All @@ -405,4 +422,9 @@ pub struct StripeCharge {
pub failure_code: ::core::option::Option<::prost::alloc::string::String>,
#[prost(message, optional, tag = "8")]
pub payment_method_details: ::core::option::Option<PaymentMethodDetails>,
/// Per-refund records attached to this charge. Empty when the charge has
/// no refunds. The list reflects the state of refunds at the time the
/// webhook was emitted.
#[prost(message, repeated, tag = "9")]
pub refunds: ::prost::alloc::vec::Vec<StripeRefund>,
}
67 changes: 63 additions & 4 deletions rust/src/sentry_protos.billing.v1.services.charge.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/// Canonical projection of a stored platform charge. Returned by charge
/// service endpoints that need to expose the persisted charge state to
/// callers.
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PlatformCharge {
#[prost(string, tag = "1")]
pub stripe_id: ::prost::alloc::string::String,
Expand All @@ -18,12 +18,48 @@ pub struct PlatformCharge {
pub amount: u64,
#[prost(string, optional, tag = "6")]
pub failure_code: ::core::option::Option<::prost::alloc::string::String>,
/// DEPRECATED: Source of truth for refund state is `refunds` below.
/// Consumers should check `len(refunds) > 0` (or
/// `sum(refunds\[*\].amount_cents) == amount`) instead. Left in the proto
/// because `sentry-protos` policy disallows field deletion. Not
/// populated by current producers.
#[deprecated]
#[prost(bool, tag = "7")]
pub refunded: bool,
/// DEPRECATED: Source of truth for refund amount is the sum of
/// `refunds\[*\].amount_cents` below. Left in the proto because
/// `sentry-protos` policy disallows field deletion. Not populated by
/// current producers.
#[deprecated]
#[prost(uint64, tag = "8")]
pub amount_refunded: u64,
#[prost(string, optional, tag = "9")]
pub card_last_4: ::core::option::Option<::prost::alloc::string::String>,
/// Recorded refunds against this charge, ordered by `date_added_st`
/// ascending. Source of truth for refund state on a charge -- consumers
/// sum `refunds\[*\].amount_cents` if they need the aggregate, and
/// `len(refunds) > 0` (or sum == amount) signals "refunded."
#[prost(message, repeated, tag = "10")]
pub refunds: ::prost::alloc::vec::Vec<PlatformRefund>,
}
/// Canonical projection of a stored platform refund. One row per recorded
/// refund against a `PlatformCharge`.
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct PlatformRefund {
/// Stripe id of the refund (e.g. "re_xxx").
#[prost(string, tag = "1")]
pub stripe_id: ::prost::alloc::string::String,
#[prost(uint64, tag = "2")]
pub organization_id: u64,
/// Refund amount in cents.
#[prost(uint64, tag = "3")]
pub amount_cents: u64,
/// Stripe-supplied refund reason. Unset when Stripe did not provide one.
#[prost(string, optional, tag = "4")]
pub reason: ::core::option::Option<::prost::alloc::string::String>,
/// Unix epoch seconds when the refund was recorded by the platform.
#[prost(int64, tag = "5")]
Comment thread
armcknight marked this conversation as resolved.
pub date_added_st: i64,
}
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct CaptureChargeRequest {
Expand Down Expand Up @@ -99,7 +135,7 @@ pub struct GetChargeByStripeIdRequest {
#[prost(string, tag = "1")]
pub stripe_id: ::prost::alloc::string::String,
}
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetChargeByStripeIdResponse {
/// Unset when no platform charge with the given stripe_id exists.
#[prost(message, optional, tag = "1")]
Expand All @@ -126,18 +162,41 @@ pub struct ListChargesForInvoiceResponse {
#[prost(message, repeated, tag = "1")]
pub charges: ::prost::alloc::vec::Vec<Charge>,
}
/// Records platform refunds for a Stripe charge from a webhook payload.
/// Mirrors the contents of `stripe_charge.refunds` as `PlatformRefund`
/// rows idempotently keyed by Stripe refund id, and syncs the aggregate
/// `amount_refunded` / `refunded` state on the stored `PlatformCharge`.
/// Called by the presentation layer that owns the `charge.refunded`
/// webhook handler; the charge service does not call Stripe in this path
/// (Stripe initiated the refund).
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RecordChargeRefundsRequest {
#[prost(message, optional, tag = "1")]
pub stripe_charge: ::core::option::Option<
super::super::super::common::v1::StripeCharge,
>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RecordChargeRefundsResponse {
/// Unset when no platform charge exists for `stripe_charge.id`. Callers
/// use this to distinguish platform charges from legacy charges. When set,
/// `charge.refunds` carries the platform refund rows that were recorded
/// or already existed, ordered by `date_added_st` ascending.
#[prost(message, optional, tag = "1")]
pub charge: ::core::option::Option<PlatformCharge>,
}
/// Synchronizes a stored platform charge with the latest snapshot from
/// Stripe. The charge is identified by `stripe_charge.id`. Fields like
/// `paid`, `failure_code` and refund state are copied onto the stored
/// record so the database reflects the current payment-provider state.
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct UpdateChargeRequest {
#[prost(message, optional, tag = "1")]
pub stripe_charge: ::core::option::Option<
super::super::super::common::v1::StripeCharge,
>,
}
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct UpdateChargeResponse {
/// Unset when no platform charge with the given stripe_id exists.
#[prost(message, optional, tag = "1")]
Expand Down
2 changes: 1 addition & 1 deletion rust/src/sentry_protos.billing.v1.services.invoicer.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ pub struct HandleChargeDisputedResponse {
}
/// Request to react to a Stripe `charge.succeeded` webhook event for a
/// charge created by the billing platform.
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct HandleChargeSucceededRequest {
#[prost(message, optional, tag = "1")]
pub stripe_charge: ::core::option::Option<
Expand Down
Loading