From ae6d5a633de86c6df377963ed1114581002abb3f Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Fri, 31 Jul 2026 15:17:35 +0530 Subject: [PATCH 1/4] feat(frontier)!: move CreatePlan and UpdatePlan to AdminService and add ListAllPlans --- raystack/frontier/v1beta1/admin.proto | 63 ++++++++++++++++++++++++ raystack/frontier/v1beta1/frontier.proto | 49 ------------------ 2 files changed, 63 insertions(+), 49 deletions(-) diff --git a/raystack/frontier/v1beta1/admin.proto b/raystack/frontier/v1beta1/admin.proto index d021bdb8..5c14666c 100644 --- a/raystack/frontier/v1beta1/admin.proto +++ b/raystack/frontier/v1beta1/admin.proto @@ -117,6 +117,15 @@ service AdminService { rpc ListAllBillingAccounts(ListAllBillingAccountsRequest) returns (ListAllBillingAccountsResponse) {} + // Plans + rpc CreatePlan(CreatePlanRequest) returns (CreatePlanResponse) {} + + rpc UpdatePlan(UpdatePlanRequest) returns (UpdatePlanResponse) {} + + // ListAllPlans returns every plan, including disabled ones, unlike + // FrontierService.ListPlans which returns active plans only. + rpc ListAllPlans(ListAllPlansRequest) returns (ListAllPlansResponse) {} + // Usage rpc RevertBillingUsage(RevertBillingUsageRequest) returns (RevertBillingUsageResponse) {} @@ -429,6 +438,60 @@ message ListAllBillingAccountsResponse { repeated BillingAccount billing_accounts = 1; } +message ListAllPlansRequest { + // filter by plan state, e.g. "active" or "disabled". an empty value returns all plans + string state = 1; +} + +message ListAllPlansResponse { + repeated Plan plans = 1; +} + +message PlanRequestBody { + string name = 1; + string title = 2; + string description = 3; + + repeated Product products = 4; + // known intervals are "day", "week", "month", and "year" + string interval = 5 [(buf.validate.field).string = { + in: [ + "day", + "week", + "month", + "year" + ] + }]; + int64 on_start_credits = 6; + int64 trial_days = 7; + + string state = 8; + + google.protobuf.Struct metadata = 20; +} + +message CreatePlanRequest { + // Plan to create + PlanRequestBody body = 1 [(buf.validate.field).required = true]; +} + +message CreatePlanResponse { + // Created plan + Plan plan = 1; +} + +message UpdatePlanRequest { + // ID of the plan to update + string id = 1 [(buf.validate.field).string.min_len = 1]; + // Plan to update + PlanRequestBody body = 2; +} + +message UpdatePlanResponse { + // Updated plan + Plan plan = 1; +} + message RevertBillingUsageRequest { string org_id = 1; // DEPRECATED diff --git a/raystack/frontier/v1beta1/frontier.proto b/raystack/frontier/v1beta1/frontier.proto index 7515c94e..89b6e095 100644 --- a/raystack/frontier/v1beta1/frontier.proto +++ b/raystack/frontier/v1beta1/frontier.proto @@ -339,14 +339,10 @@ service FrontierService { rpc ListFeatures(ListFeaturesRequest) returns (ListFeaturesResponse) {} // Plans - rpc CreatePlan(CreatePlanRequest) returns (CreatePlanResponse) {} - rpc ListPlans(ListPlansRequest) returns (ListPlansResponse) {} rpc GetPlan(GetPlanRequest) returns (GetPlanResponse) {} - rpc UpdatePlan(UpdatePlanRequest) returns (UpdatePlanResponse) {} - // Checkout rpc CreateCheckout(CreateCheckoutRequest) returns (CreateCheckoutResponse) {} @@ -864,39 +860,6 @@ message ListFeaturesResponse { repeated Feature features = 1; } -message PlanRequestBody { - string name = 1; - string title = 2; - string description = 3; - - repeated Product products = 4; - // known intervals are "day", "week", "month", and "year" - string interval = 5 [(buf.validate.field).string = { - in: [ - "day", - "week", - "month", - "year" - ] - }]; - int64 on_start_credits = 6; - int64 trial_days = 7; - - string state = 8; - - google.protobuf.Struct metadata = 20; -} - -message CreatePlanRequest { - // Plan to create - PlanRequestBody body = 1 [(buf.validate.field).required = true]; -} - -message CreatePlanResponse { - // Created plan - Plan plan = 1; -} - message GetPlanRequest { // ID of the plan to get string id = 1 [(buf.validate.field).string.min_len = 1]; @@ -907,18 +870,6 @@ message GetPlanResponse { Plan plan = 1; } -message UpdatePlanRequest { - // ID of the plan to update - string id = 1 [(buf.validate.field).string.min_len = 1]; - // Plan to update - PlanRequestBody body = 2; -} - -message UpdatePlanResponse { - // Updated plan - Plan plan = 1; -} - message ListInvoicesRequest { string org_id = 1 [(buf.validate.field).string.min_len = 3]; reserved 2; From fc68fa30cf84a128e091d42def61616c0048aad9 Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Fri, 31 Jul 2026 15:40:40 +0530 Subject: [PATCH 2/4] feat(frontier): validate plan state enum and non-negative credits and trial days --- raystack/frontier/v1beta1/admin.proto | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/raystack/frontier/v1beta1/admin.proto b/raystack/frontier/v1beta1/admin.proto index 5c14666c..3a957b86 100644 --- a/raystack/frontier/v1beta1/admin.proto +++ b/raystack/frontier/v1beta1/admin.proto @@ -440,7 +440,13 @@ message ListAllBillingAccountsResponse { message ListAllPlansRequest { // filter by plan state, e.g. "active" or "disabled". an empty value returns all plans - string state = 1; + string state = 1 [(buf.validate.field).string = { + in: [ + "", + "active", + "disabled" + ] + }]; } message ListAllPlansResponse { @@ -462,10 +468,16 @@ message PlanRequestBody { "year" ] }]; - int64 on_start_credits = 6; - int64 trial_days = 7; + int64 on_start_credits = 6 [(buf.validate.field).int64.gte = 0]; + int64 trial_days = 7 [(buf.validate.field).int64.gte = 0]; - string state = 8; + string state = 8 [(buf.validate.field).string = { + in: [ + "", + "active", + "disabled" + ] + }]; google.protobuf.Struct metadata = 20; } From 44039cde037b0242986b0cb6db58d88cf274a5b8 Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Mon, 3 Aug 2026 12:52:06 +0530 Subject: [PATCH 3/4] feat(frontier): add UpdatePlanRequestBody and require non-empty plan state --- raystack/frontier/v1beta1/admin.proto | 64 ++++++++++++++++++--------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/raystack/frontier/v1beta1/admin.proto b/raystack/frontier/v1beta1/admin.proto index 3a957b86..317d86d2 100644 --- a/raystack/frontier/v1beta1/admin.proto +++ b/raystack/frontier/v1beta1/admin.proto @@ -438,21 +438,6 @@ message ListAllBillingAccountsResponse { repeated BillingAccount billing_accounts = 1; } -message ListAllPlansRequest { - // filter by plan state, e.g. "active" or "disabled". an empty value returns all plans - string state = 1 [(buf.validate.field).string = { - in: [ - "", - "active", - "disabled" - ] - }]; -} - -message ListAllPlansResponse { - repeated Plan plans = 1; -} - message PlanRequestBody { string name = 1; string title = 2; @@ -468,12 +453,12 @@ message PlanRequestBody { "year" ] }]; - int64 on_start_credits = 6 [(buf.validate.field).int64.gte = 0]; - int64 trial_days = 7 [(buf.validate.field).int64.gte = 0]; + int64 on_start_credits = 6 [(buf.validate.field).int64 = {gte: 0}]; + int64 trial_days = 7 [(buf.validate.field).int64 = {gte: 0}]; + // known states are "active" and "disabled" string state = 8 [(buf.validate.field).string = { in: [ - "", "active", "disabled" ] @@ -492,11 +477,33 @@ message CreatePlanResponse { Plan plan = 1; } +// UpdatePlanRequestBody carries the plan fields UpdatePlan writes. It has no +// name, interval, or products because UpdatePlan does not change those; a +// plan's products are managed through CreatePlan's upsert. +message UpdatePlanRequestBody { + string title = 1; + string description = 2; + int64 on_start_credits = 3 [(buf.validate.field).int64 = {gte: 0}]; + int64 trial_days = 4 [(buf.validate.field).int64 = {gte: 0}]; + + // known states are "active" and "disabled" + string state = 5 [(buf.validate.field).string = { + in: [ + "active", + "disabled" + ] + }]; + + google.protobuf.Struct metadata = 20; +} + message UpdatePlanRequest { // ID of the plan to update string id = 1 [(buf.validate.field).string.min_len = 1]; - // Plan to update - PlanRequestBody body = 2; + // field 2 held a PlanRequestBody before UpdatePlan moved to its own body type + reserved 2; + // Plan fields to write + UpdatePlanRequestBody body = 3 [(buf.validate.field).required = true]; } message UpdatePlanResponse { @@ -504,6 +511,23 @@ message UpdatePlanResponse { Plan plan = 1; } +message ListAllPlansRequest { + // filter by plan state. an empty value returns plans in every state + string state = 1 [(buf.validate.field) = { + ignore: IGNORE_IF_ZERO_VALUE + string: { + in: [ + "active", + "disabled" + ] + } + }]; +} + +message ListAllPlansResponse { + repeated Plan plans = 1; +} + message RevertBillingUsageRequest { string org_id = 1; // DEPRECATED From 01c105d3f77a2404197d9fd5423977a307e48457 Mon Sep 17 00:00:00 2001 From: Rohil Surana Date: Mon, 3 Aug 2026 15:22:10 +0530 Subject: [PATCH 4/4] fix(frontier): use inactive not disabled for plan state, document update semantics --- raystack/frontier/v1beta1/admin.proto | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/raystack/frontier/v1beta1/admin.proto b/raystack/frontier/v1beta1/admin.proto index 317d86d2..033ddf65 100644 --- a/raystack/frontier/v1beta1/admin.proto +++ b/raystack/frontier/v1beta1/admin.proto @@ -122,7 +122,7 @@ service AdminService { rpc UpdatePlan(UpdatePlanRequest) returns (UpdatePlanResponse) {} - // ListAllPlans returns every plan, including disabled ones, unlike + // ListAllPlans returns every plan, including inactive ones, unlike // FrontierService.ListPlans which returns active plans only. rpc ListAllPlans(ListAllPlansRequest) returns (ListAllPlansResponse) {} @@ -456,11 +456,11 @@ message PlanRequestBody { int64 on_start_credits = 6 [(buf.validate.field).int64 = {gte: 0}]; int64 trial_days = 7 [(buf.validate.field).int64 = {gte: 0}]; - // known states are "active" and "disabled" + // known states are "active" and "inactive" string state = 8 [(buf.validate.field).string = { in: [ "active", - "disabled" + "inactive" ] }]; @@ -479,18 +479,19 @@ message CreatePlanResponse { // UpdatePlanRequestBody carries the plan fields UpdatePlan writes. It has no // name, interval, or products because UpdatePlan does not change those; a -// plan's products are managed through CreatePlan's upsert. +// plan's products are managed through CreatePlan's upsert. UpdatePlan is a full +// write of these fields: an omitted field is cleared, not left unchanged. message UpdatePlanRequestBody { string title = 1; string description = 2; int64 on_start_credits = 3 [(buf.validate.field).int64 = {gte: 0}]; int64 trial_days = 4 [(buf.validate.field).int64 = {gte: 0}]; - // known states are "active" and "disabled" + // known states are "active" and "inactive" string state = 5 [(buf.validate.field).string = { in: [ "active", - "disabled" + "inactive" ] }]; @@ -498,7 +499,7 @@ message UpdatePlanRequestBody { } message UpdatePlanRequest { - // ID of the plan to update + // ID or name of the plan to update string id = 1 [(buf.validate.field).string.min_len = 1]; // field 2 held a PlanRequestBody before UpdatePlan moved to its own body type reserved 2; @@ -518,7 +519,7 @@ message ListAllPlansRequest { string: { in: [ "active", - "disabled" + "inactive" ] } }];