Skip to main content

uarp_sdk/generated/api/
billing.rs

1// Code generated by @uarp/codegen from spec/openapi.json. DO NOT EDIT.
2//!
3//! Usage tracking, quota management, and Stripe webhooks
4
5#![allow(unused_imports, clippy::too_many_arguments)]
6
7use reqwest::Method;
8use serde::{Deserialize, Serialize};
9
10use crate::client::{Client, Request, NO_BODY, NO_QUERY};
11use crate::error::Result;
12use crate::generated::models;
13use crate::multipart::{field_text, FilePart};
14use crate::util::encode_path;
15
16/// Query and header parameters for `getUsage`.
17#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
18pub struct GetUsageParams {
19    /// Billing period in YYYY-MM format. Defaults to current month.
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub period: Option<String>,
22}
23
24/// Query and header parameters for `getUsageTimeseries`.
25#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
26pub struct GetUsageTimeseriesParams {
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub metric: Option<models::GetUsageTimeseriesMetric>,
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub days: Option<i64>,
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub agent_id: Option<String>,
33}
34
35/// Query and header parameters for `handleStripeWebhook`.
36#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
37pub struct HandleStripeWebhookParams {
38    /// Stripe webhook signature in `t=\<timestamp\>,v1=\<sig\>` format. Required; missing or
39    /// invalid signatures are rejected with 400.
40    #[serde(skip)]
41    pub stripe_signature: String,
42}
43
44/// Usage tracking, quota management, and Stripe webhooks
45#[derive(Debug, Clone)]
46pub struct BillingApi {
47    pub(crate) client: Client,
48}
49
50impl Client {
51    /// Usage tracking, quota management, and Stripe webhooks
52    pub fn billing(&self) -> BillingApi {
53        BillingApi { client: self.clone() }
54    }
55}
56
57impl BillingApi {
58    /// Check tenant quota status
59    ///
60    /// `GET /api/v1/usage/quota`
61    ///
62    /// Required scopes: `billing:read`.
63    pub async fn check_quota(&self) -> Result<serde_json::Value> {
64        self.client
65            .request_json(Request {
66                method: Method::GET,
67                path: "/api/v1/usage/quota".to_string(),
68                query: NO_QUERY,
69                body: NO_BODY,
70                headers: Vec::new(),
71                idempotent: false,
72            })
73            .await
74    }
75
76    /// Create a Stripe Customer Portal session URL
77    ///
78    /// Returns a Stripe-hosted URL where the customer can manage subscriptions and payment methods.
79    /// `return_url` must be same-origin as the request; defaults to `\<origin\>/admin/billing`.
80    ///
81    /// `POST /api/v1/billing/portal-session`
82    ///
83    /// Required scopes: `billing:write`.
84    pub async fn create_billing_portal_session(&self, body: &models::CreateBillingPortalSessionRequest) -> Result<models::CreateBillingPortalSessionResponse> {
85        self.client
86            .request_json(Request {
87                method: Method::POST,
88                path: "/api/v1/billing/portal-session".to_string(),
89                query: NO_QUERY,
90                body: Some(body),
91                headers: Vec::new(),
92                idempotent: true,
93            })
94            .await
95    }
96
97    /// Create Stripe checkout session
98    ///
99    /// `POST /api/v1/billing/checkout-session`
100    ///
101    /// Required scopes: `billing:read`.
102    pub async fn create_checkout_session(&self, body: &models::CreateCheckoutSessionRequest) -> Result<models::CreateCheckoutSessionResponse> {
103        self.client
104            .request_json(Request {
105                method: Method::POST,
106                path: "/api/v1/billing/checkout-session".to_string(),
107                query: NO_QUERY,
108                body: Some(body),
109                headers: Vec::new(),
110                idempotent: true,
111            })
112            .await
113    }
114
115    /// Start Stripe checkout for a SPEC package
116    ///
117    /// Answers a Stripe-hosted URL to redirect to.
118    ///
119    /// `success_url` and `cancel_url` must be SAME-ORIGIN with the request; anything else is
120    /// refused. Both default to the billing settings page, so a caller that has no opinion should
121    /// omit them rather than construct one.
122    ///
123    /// Three refusals worth telling apart. **501** — billing is not configured on this deployment.
124    /// Not 502, deliberately: no upstream was contacted, and a 502 sends an operator hunting an
125    /// outage when the fix is one admin setting. **400** — the package exists but has no Stripe
126    /// price wired, and the message names the admin screen that creates one. **404** — no such
127    /// package, or it is archived.
128    ///
129    /// `POST /api/v1/billing/spec-packages/{packageId}/checkout-session`
130    ///
131    /// Required scopes: `billing:read`.
132    pub async fn create_spec_package_checkout_session(&self, package_id: &str, body: &models::CreateSpecPackageCheckoutSessionRequest) -> Result<models::CreateSpecPackageCheckoutSessionResponse> {
133        self.client
134            .request_json(Request {
135                method: Method::POST,
136                path: format!("/api/v1/billing/spec-packages/{}/checkout-session", encode_path(package_id)),
137                query: NO_QUERY,
138                body: Some(body),
139                headers: Vec::new(),
140                idempotent: true,
141            })
142            .await
143    }
144
145    /// Trial window and a live usage-based plan recommendation
146    ///
147    /// `GET /api/v1/billing/trial`
148    ///
149    /// Required scopes: `billing:read`.
150    pub async fn get_billing_trial(&self) -> Result<models::GetBillingTrialResponse> {
151        self.client
152            .request_json(Request {
153                method: Method::GET,
154                path: "/api/v1/billing/trial".to_string(),
155                query: NO_QUERY,
156                body: NO_BODY,
157                headers: Vec::new(),
158                idempotent: false,
159            })
160            .await
161    }
162
163    /// Image and video generation usage against plan quotas
164    ///
165    /// `GET /api/v1/usage/media`
166    ///
167    /// Required scopes: `billing:read`.
168    pub async fn get_media_usage(&self) -> Result<models::GetMediaUsageResponse> {
169        self.client
170            .request_json(Request {
171                method: Method::GET,
172                path: "/api/v1/usage/media".to_string(),
173                query: NO_QUERY,
174                body: NO_BODY,
175                headers: Vec::new(),
176                idempotent: false,
177            })
178            .await
179    }
180
181    /// Get current tenant usage
182    ///
183    /// `GET /api/v1/usage`
184    ///
185    /// Required scopes: `billing:read`.
186    pub async fn get_usage(&self, params: &GetUsageParams) -> Result<models::UsageSummary> {
187        self.client
188            .request_json(Request {
189                method: Method::GET,
190                path: "/api/v1/usage".to_string(),
191                query: Some(params),
192                body: NO_BODY,
193                headers: Vec::new(),
194                idempotent: false,
195            })
196            .await
197    }
198
199    /// Get usage analytics over time
200    ///
201    /// `GET /api/v1/usage/timeseries`
202    ///
203    /// Required scopes: `billing:read`.
204    pub async fn get_usage_timeseries(&self, params: &GetUsageTimeseriesParams) -> Result<models::GetUsageTimeseriesResponse> {
205        self.client
206            .request_json(Request {
207                method: Method::GET,
208                path: "/api/v1/usage/timeseries".to_string(),
209                query: Some(params),
210                body: NO_BODY,
211                headers: Vec::new(),
212                idempotent: false,
213            })
214            .await
215    }
216
217    /// Handle Stripe webhook events
218    ///
219    /// Processes incoming Stripe events (invoice.paid, invoice.payment_failed,
220    /// customer.subscription.deleted). Bypasses normal auth — `Stripe-Signature` header is
221    /// HMAC-verified against the webhook signing secret.
222    ///
223    /// `POST /api/v1/webhooks/stripe`
224    pub async fn handle_stripe_webhook(&self, body: &models::HandleStripeWebhookRequest, params: &HandleStripeWebhookParams) -> Result<models::HandleStripeWebhookResponse> {
225        let mut headers: Vec<(&'static str, String)> = Vec::new();
226        headers.push(("Stripe-Signature", params.stripe_signature.clone()));
227        self.client
228            .request_json(Request {
229                method: Method::POST,
230                path: "/api/v1/webhooks/stripe".to_string(),
231                query: NO_QUERY,
232                body: Some(body),
233                headers,
234                idempotent: true,
235            })
236            .await
237    }
238
239    /// List available billing plans
240    ///
241    /// `GET /api/v1/billing/plans`
242    ///
243    /// Required scopes: `billing:read`.
244    pub async fn list_billing_plans(&self) -> Result<models::ListBillingPlansResponse> {
245        self.client
246            .request_json(Request {
247                method: Method::GET,
248                path: "/api/v1/billing/plans".to_string(),
249                query: NO_QUERY,
250                body: NO_BODY,
251                headers: Vec::new(),
252                idempotent: false,
253            })
254            .await
255    }
256
257    /// SPEC packages this tenant can see, with entitlement
258    ///
259    /// The tenant-facing view, and deliberately narrower than the admin one: archived packages are
260    /// omitted and **the Stripe price id is never returned** — `checkout_available` is the boolean
261    /// derived from whether one is wired.
262    ///
263    /// `entitlement` is a three-way discriminator a client should branch on rather than infer:
264    /// `plan_included` (comes with the tenant's plan tier), `purchased` (bought a la carte),
265    /// `available` (not entitled, and buyable). It carries the same values as
266    /// `/api/v1/billing/packages` so one card component serves both.
267    ///
268    /// `program` is the nav entry and pages a package contributes, and is ABSENT for agent-only
269    /// packages with no UI — which is what lets a client build the entitlement-gated navigation
270    /// from this single call.
271    ///
272    /// Sorted by `display_order`, then by name.
273    ///
274    /// `GET /api/v1/billing/spec-packages`
275    ///
276    /// Required scopes: `billing:read`.
277    pub async fn list_billing_spec_packages(&self) -> Result<models::ListBillingSpecPackagesResponse> {
278        self.client
279            .request_json(Request {
280                method: Method::GET,
281                path: "/api/v1/billing/spec-packages".to_string(),
282                query: NO_QUERY,
283                body: NO_BODY,
284                headers: Vec::new(),
285                idempotent: false,
286            })
287            .await
288    }
289}