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}