From 8013abbee950a525f27f427d4781203d9e835af2 Mon Sep 17 00:00:00 2001 From: David Rauschenbach Date: Wed, 1 Oct 2025 14:55:18 -0700 Subject: [PATCH] Support enabling new Stripe payment provider --- CHANGELOG.md | 4 + src/cmd/enable/mod.rs | 7 + src/cmd/enable/payment_provider.rs | 41 ++++++ src/protocol/saas_rs.user.v1.rs | 45 ++++++- src/protocol/saas_rs.user.v1.serde.rs | 179 ++++++++++++++++++++++++++ 5 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 src/cmd/enable/payment_provider.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e146049..0fa4e38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # SaaS RS :: CLI Changelog +## [Unreleased] +### Added +- [#84](https://github.com/saas-rs/cli/issues/84) Support enabling new Stripe payment provider + ## [0.3.6] - 2025-09-27 ### Changed - [#82](https://github.com/saas-rs/cli/issues/82) Upgrade Rust from 1.88.0 → 1.89.0 diff --git a/src/cmd/enable/mod.rs b/src/cmd/enable/mod.rs index c434dde..c8c059e 100644 --- a/src/cmd/enable/mod.rs +++ b/src/cmd/enable/mod.rs @@ -1,4 +1,5 @@ pub(super) mod identity_provider; +pub(super) mod payment_provider; pub(super) mod storage_provider; use clap::Parser; @@ -15,6 +16,9 @@ pub enum Subcommand { #[command(name = "identity-provider", alias = "identityProvider")] IdentityProvider(identity_provider::Opts), + #[command(name = "payment-provider", alias = "paymentProvider")] + PaymentProvider(payment_provider::Opts), + #[command(name = "storage-provider", alias = "storageProvider")] StorageProvider(storage_provider::Opts), } @@ -24,6 +28,9 @@ pub async fn run(subcommand: Subcommand) -> Result<(), Box { identity_provider::run(provider).await?; } + Subcommand::PaymentProvider(payment_provider::Opts { provider }) => { + payment_provider::run(provider).await?; + } Subcommand::StorageProvider(storage_provider::Opts { provider }) => { storage_provider::run(provider).await?; } diff --git a/src/cmd/enable/payment_provider.rs b/src/cmd/enable/payment_provider.rs new file mode 100644 index 0000000..57a691b --- /dev/null +++ b/src/cmd/enable/payment_provider.rs @@ -0,0 +1,41 @@ +use crate::cmd::generate::{do_generate, do_generate_preflight}; +use crate::protocol::saas_rs::user::v1::generate_request::UsePaymentProvider; +use crate::protocol::saas_rs::user::v1::{ + generate_request::{self, use_payment_provider::Provider}, + GenerateRequest, +}; +use clap::{ + builder::PossibleValue, + {Parser, ValueEnum}, +}; + +#[derive(Debug, Parser)] +pub struct Opts { + /// The identity provider + #[arg(value_name = "provider", value_enum)] + pub provider: Provider, +} + +pub async fn run(provider: Provider) -> Result<(), Box> { + let (project_id, snapshot) = do_generate_preflight(false).await?; + let req = { + GenerateRequest { + project_id, + snapshot: Some(snapshot), + what: Some(generate_request::What::UsePaymentProvider(UsePaymentProvider { + provider: provider as i32, + })), + } + }; + do_generate(req).await +} + +impl ValueEnum for Provider { + fn value_variants<'a>() -> &'a [Self] { + &[Self::Stripe] + } + + fn to_possible_value(&self) -> Option { + Some(PossibleValue::new(self.as_str_name())) + } +} diff --git a/src/protocol/saas_rs.user.v1.rs b/src/protocol/saas_rs.user.v1.rs index 4015234..7bc90fa 100644 --- a/src/protocol/saas_rs.user.v1.rs +++ b/src/protocol/saas_rs.user.v1.rs @@ -617,7 +617,7 @@ pub struct GenerateRequest { pub project_id: ::prost::alloc::string::String, #[prost(oneof = "generate_request::Snapshot", tags = "2, 3")] pub snapshot: ::core::option::Option, - #[prost(oneof = "generate_request::What", tags = "4, 5, 6, 7, 8, 9, 10, 11")] + #[prost(oneof = "generate_request::What", tags = "4, 5, 6, 7, 8, 9, 10, 11, 12")] pub what: ::core::option::Option, } /// Nested message and enum types in `GenerateRequest`. @@ -774,6 +774,47 @@ pub mod generate_request { } } #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] + pub struct UsePaymentProvider { + #[prost(enumeration = "use_payment_provider::Provider", tag = "1")] + pub provider: i32, + } + /// Nested message and enum types in `UsePaymentProvider`. + pub mod use_payment_provider { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum Provider { + Stripe = 0, + } + impl Provider { + /// 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::Stripe => "Stripe", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "Stripe" => Some(Self::Stripe), + _ => None, + } + } + } + } + #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct UseStorageProvider { #[prost(enumeration = "use_storage_provider::Provider", tag = "1")] pub provider: i32, @@ -854,6 +895,8 @@ pub mod generate_request { UseStorageProvider(UseStorageProvider), #[prost(message, tag = "11")] UseIdentityProvider(UseIdentityProvider), + #[prost(message, tag = "12")] + UsePaymentProvider(UsePaymentProvider), } } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] diff --git a/src/protocol/saas_rs.user.v1.serde.rs b/src/protocol/saas_rs.user.v1.serde.rs index ffea0f2..a487aea 100644 --- a/src/protocol/saas_rs.user.v1.serde.rs +++ b/src/protocol/saas_rs.user.v1.serde.rs @@ -13537,6 +13537,9 @@ impl serde::Serialize for GenerateRequest { generate_request::What::UseIdentityProvider(v) => { struct_ser.serialize_field("useIdentityProvider", v)?; } + generate_request::What::UsePaymentProvider(v) => { + struct_ser.serialize_field("usePaymentProvider", v)?; + } } } struct_ser.end() @@ -13564,6 +13567,8 @@ impl<'de> serde::Deserialize<'de> for GenerateRequest { "useStorageProvider", "use_identity_provider", "useIdentityProvider", + "use_payment_provider", + "usePaymentProvider", ]; #[allow(clippy::enum_variant_names)] @@ -13579,6 +13584,7 @@ impl<'de> serde::Deserialize<'de> for GenerateRequest { Feature, UseStorageProvider, UseIdentityProvider, + UsePaymentProvider, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -13612,6 +13618,7 @@ impl<'de> serde::Deserialize<'de> for GenerateRequest { "feature" => Ok(GeneratedField::Feature), "useStorageProvider" | "use_storage_provider" => Ok(GeneratedField::UseStorageProvider), "useIdentityProvider" | "use_identity_provider" => Ok(GeneratedField::UseIdentityProvider), + "usePaymentProvider" | "use_payment_provider" => Ok(GeneratedField::UsePaymentProvider), _ => Ok(GeneratedField::__SkipField__), } } @@ -13710,6 +13717,13 @@ impl<'de> serde::Deserialize<'de> for GenerateRequest { return Err(serde::de::Error::duplicate_field("useIdentityProvider")); } what__ = map_.next_value::<::std::option::Option<_>>()?.map(generate_request::What::UseIdentityProvider) +; + } + GeneratedField::UsePaymentProvider => { + if what__.is_some() { + return Err(serde::de::Error::duplicate_field("usePaymentProvider")); + } + what__ = map_.next_value::<::std::option::Option<_>>()?.map(generate_request::What::UsePaymentProvider) ; } GeneratedField::__SkipField__ => { @@ -15065,6 +15079,171 @@ impl<'de> serde::Deserialize<'de> for generate_request::use_identity_provider::P deserializer.deserialize_any(GeneratedVisitor) } } +impl serde::Serialize for generate_request::UsePaymentProvider { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if true { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("saas_rs.user.v1.GenerateRequest.UsePaymentProvider", len)?; + if true { + let v = generate_request::use_payment_provider::Provider::try_from(self.provider) + .map_err(|_| serde::ser::Error::custom(format!("Invalid variant {}", self.provider)))?; + struct_ser.serialize_field("provider", &v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for generate_request::UsePaymentProvider { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "provider", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Provider, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "provider" => Ok(GeneratedField::Provider), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = generate_request::UsePaymentProvider; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct saas_rs.user.v1.GenerateRequest.UsePaymentProvider") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut provider__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Provider => { + if provider__.is_some() { + return Err(serde::de::Error::duplicate_field("provider")); + } + provider__ = Some(map_.next_value::()? as i32); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(generate_request::UsePaymentProvider { + provider: provider__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("saas_rs.user.v1.GenerateRequest.UsePaymentProvider", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for generate_request::use_payment_provider::Provider { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let variant = match self { + Self::Stripe => "Stripe", + }; + serializer.serialize_str(variant) + } +} +impl<'de> serde::Deserialize<'de> for generate_request::use_payment_provider::Provider { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "Stripe", + ]; + + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = generate_request::use_payment_provider::Provider; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + fn visit_i64(self, v: i64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self) + }) + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + i32::try_from(v) + .ok() + .and_then(|x| x.try_into().ok()) + .ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(v), &self) + }) + } + + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "Stripe" => Ok(generate_request::use_payment_provider::Provider::Stripe), + _ => Err(serde::de::Error::unknown_variant(value, FIELDS)), + } + } + } + deserializer.deserialize_any(GeneratedVisitor) + } +} impl serde::Serialize for generate_request::UseStorageProvider { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result