rain_sdk/api/
applications.rs

1//! Applications API
2//!
3//! This module provides functionality to manage company and user applications in the Rain API.
4//!
5//! # Examples
6//!
7//! This module provides functionality to manage company and user applications.
8//!
9//! See the individual function documentation for examples.
10
11use crate::client::RainClient;
12use crate::error::Result;
13use crate::models::applications::*;
14use uuid::Uuid;
15
16impl RainClient {
17    // ============================================================================
18    // Company Application Methods
19    // ============================================================================
20
21    /// Create a company application
22    ///
23    /// # Arguments
24    ///
25    /// * `request` - The company application request
26    ///
27    /// # Returns
28    ///
29    /// Returns a [`CompanyApplicationResponse`] containing the application information.
30    ///
31    /// # Errors
32    ///
33    /// This method can return the following errors:
34    /// - `400` - Invalid request
35    /// - `401` - Invalid authorization
36    /// - `500` - Internal server error
37    ///
38    /// # Examples
39    ///
40    /// ```no_run
41    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
42    /// use rain_sdk::models::applications::*;
43    /// use rain_sdk::models::common::*;
44    ///
45    /// # #[cfg(feature = "async")]
46    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
47    /// let config = Config::new(Environment::Dev);
48    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
49    /// let client = RainClient::new(config, auth)?;
50    ///
51    /// // Note: In practice, you would populate all required fields.
52    /// // This is a simplified example - see the full struct definitions for required fields.
53    /// # let request = todo!();
54    /// let application = client.create_company_application(&request).await?;
55    /// # Ok(())
56    /// # }
57    /// ```
58    #[cfg(feature = "async")]
59    pub async fn create_company_application(
60        &self,
61        request: &CreateCompanyApplicationRequest,
62    ) -> Result<CompanyApplicationResponse> {
63        let path = "/applications/company";
64        self.post(path, request).await
65    }
66
67    /// Get a company application by ID
68    ///
69    /// # Arguments
70    ///
71    /// * `company_id` - The unique identifier of the company
72    ///
73    /// # Returns
74    ///
75    /// Returns a [`CompanyApplicationResponse`] containing the application information.
76    ///
77    /// # Errors
78    ///
79    /// This method can return the following errors:
80    /// - `401` - Invalid authorization
81    /// - `404` - Company not found
82    /// - `500` - Internal server error
83    ///
84    /// # Examples
85    ///
86    /// ```no_run
87    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
88    /// use uuid::Uuid;
89    ///
90    /// # #[cfg(feature = "async")]
91    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
92    /// let config = Config::new(Environment::Dev);
93    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
94    /// let client = RainClient::new(config, auth)?;
95    ///
96    /// let company_id = Uuid::new_v4();
97    /// let application = client.get_company_application(&company_id).await?;
98    /// # Ok(())
99    /// # }
100    /// ```
101    #[cfg(feature = "async")]
102    pub async fn get_company_application(
103        &self,
104        company_id: &Uuid,
105    ) -> Result<CompanyApplicationResponse> {
106        let path = format!("/applications/company/{company_id}");
107        self.get(&path).await
108    }
109
110    /// Update a company application
111    ///
112    /// # Arguments
113    ///
114    /// * `company_id` - The unique identifier of the company
115    /// * `request` - The update request
116    ///
117    /// # Returns
118    ///
119    /// Returns a [`CompanyApplicationResponse`] containing the updated application information.
120    ///
121    /// # Errors
122    ///
123    /// This method can return the following errors:
124    /// - `400` - Invalid request
125    /// - `401` - Invalid authorization
126    /// - `404` - Company not found
127    /// - `500` - Internal server error
128    ///
129    /// # Examples
130    ///
131    /// ```no_run
132    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
133    /// use rain_sdk::models::applications::UpdateCompanyApplicationRequest;
134    /// use uuid::Uuid;
135    ///
136    /// # #[cfg(feature = "async")]
137    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
138    /// let config = Config::new(Environment::Dev);
139    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
140    /// let client = RainClient::new(config, auth)?;
141    ///
142    /// let company_id = Uuid::new_v4();
143    /// let request = UpdateCompanyApplicationRequest {
144    ///     name: Some("Updated Name".to_string()),
145    ///     address: None,
146    ///     entity: None,
147    /// };
148    /// let application = client.update_company_application(&company_id, &request).await?;
149    /// # Ok(())
150    /// # }
151    /// ```
152    #[cfg(feature = "async")]
153    pub async fn update_company_application(
154        &self,
155        company_id: &Uuid,
156        request: &UpdateCompanyApplicationRequest,
157    ) -> Result<CompanyApplicationResponse> {
158        let path = format!("/applications/company/{company_id}");
159        self.patch(&path, request).await
160    }
161
162    /// Update an ultimate beneficial owner
163    ///
164    /// # Arguments
165    ///
166    /// * `company_id` - The unique identifier of the company
167    /// * `ubo_id` - The unique identifier of the ultimate beneficial owner
168    /// * `request` - The update request
169    ///
170    /// # Returns
171    ///
172    /// Returns a [`CompanyApplicationResponse`] containing the updated application information.
173    ///
174    /// # Errors
175    ///
176    /// This method can return the following errors:
177    /// - `400` - Invalid request
178    /// - `401` - Invalid authorization
179    /// - `404` - Company or UBO not found
180    /// - `500` - Internal server error
181    ///
182    /// # Examples
183    ///
184    /// ```no_run
185    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
186    /// use rain_sdk::models::applications::UpdateUltimateBeneficialOwnerRequest;
187    /// use uuid::Uuid;
188    ///
189    /// # #[cfg(feature = "async")]
190    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
191    /// let config = Config::new(Environment::Dev);
192    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
193    /// let client = RainClient::new(config, auth)?;
194    ///
195    /// let company_id = Uuid::new_v4();
196    /// let ubo_id = Uuid::new_v4();
197    /// let request = UpdateUltimateBeneficialOwnerRequest {
198    ///     first_name: Some("John".to_string()),
199    ///     last_name: Some("Doe".to_string()),
200    ///     birth_date: None,
201    ///     national_id: None,
202    ///     country_of_issue: None,
203    ///     email: None,
204    ///     address: None,
205    /// };
206    /// let application = client.update_ultimate_beneficial_owner(&company_id, &ubo_id, &request).await?;
207    /// # Ok(())
208    /// # }
209    /// ```
210    #[cfg(feature = "async")]
211    pub async fn update_ultimate_beneficial_owner(
212        &self,
213        company_id: &Uuid,
214        ubo_id: &Uuid,
215        request: &UpdateUltimateBeneficialOwnerRequest,
216    ) -> Result<CompanyApplicationResponse> {
217        let path = format!("/applications/company/{company_id}/ubo/{ubo_id}");
218        self.patch(&path, request).await
219    }
220
221    /// Upload a document for a company application
222    ///
223    /// # Arguments
224    ///
225    /// * `company_id` - The unique identifier of the company
226    /// * `params` - Document upload parameters
227    ///
228    /// # Returns
229    ///
230    /// Returns a success response.
231    ///
232    /// # Examples
233    ///
234    /// ```no_run
235    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
236    /// use rain_sdk::models::applications::DocumentUploadParams;
237    /// use uuid::Uuid;
238    ///
239    /// # #[cfg(feature = "async")]
240    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
241    /// let config = Config::new(Environment::Dev);
242    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
243    /// let client = RainClient::new(config, auth)?;
244    ///
245    /// let company_id = Uuid::new_v4();
246    /// let params = DocumentUploadParams {
247    ///     document_type: "directorsRegistry".to_string(),
248    ///     side: "front".to_string(),
249    ///     country: Some("US".to_string()),
250    ///     country_code: Some("US".to_string()),
251    ///     name: Some("Document Name".to_string()),
252    ///     file_path: "/path/to/file.pdf".to_string(),
253    /// };
254    /// client.upload_company_document(&company_id, &params).await?;
255    /// # Ok(())
256    /// # }
257    /// ```
258    #[cfg(feature = "async")]
259    pub async fn upload_company_document(
260        &self,
261        company_id: &Uuid,
262        params: &DocumentUploadParams,
263    ) -> Result<serde_json::Value> {
264        let path = format!("/applications/company/{company_id}/document");
265        let form = self.build_company_document_form(params)?;
266        self.put_multipart(&path, form).await
267    }
268
269    /// Upload a document for an ultimate beneficial owner
270    ///
271    /// # Arguments
272    ///
273    /// * `company_id` - The unique identifier of the company
274    /// * `ubo_id` - The unique identifier of the ultimate beneficial owner
275    /// * `params` - Document upload parameters
276    ///
277    /// # Returns
278    ///
279    /// Returns a success response.
280    ///
281    /// # Examples
282    ///
283    /// ```no_run
284    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
285    /// use rain_sdk::models::applications::DocumentUploadParams;
286    /// use uuid::Uuid;
287    ///
288    /// # #[cfg(feature = "async")]
289    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
290    /// let config = Config::new(Environment::Dev);
291    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
292    /// let client = RainClient::new(config, auth)?;
293    ///
294    /// let company_id = Uuid::new_v4();
295    /// let ubo_id = Uuid::new_v4();
296    /// let params = DocumentUploadParams {
297    ///     document_type: "idCard".to_string(),
298    ///     side: "front".to_string(),
299    ///     country: Some("US".to_string()),
300    ///     country_code: Some("US".to_string()),
301    ///     name: None,
302    ///     file_path: "/path/to/file.pdf".to_string(),
303    /// };
304    /// client.upload_ubo_document(&company_id, &ubo_id, &params).await?;
305    /// # Ok(())
306    /// # }
307    /// ```
308    #[cfg(feature = "async")]
309    pub async fn upload_ubo_document(
310        &self,
311        company_id: &Uuid,
312        ubo_id: &Uuid,
313        params: &DocumentUploadParams,
314    ) -> Result<serde_json::Value> {
315        let path = format!("/applications/company/{company_id}/ubo/{ubo_id}/document");
316        let form = self.build_user_document_form(params)?;
317        self.put_multipart(&path, form).await
318    }
319
320    // ============================================================================
321    // User Application Methods
322    // ============================================================================
323
324    /// Create a user application
325    ///
326    /// This method supports three verification methods (oneOf in OpenAPI spec):
327    /// 1. **Using Sumsub Share Token**: Provide only `sumsub_share_token`
328    /// 2. **Using Persona Share Token**: Provide only `persona_share_token`
329    /// 3. **Using API**: Provide full person data (all `IssuingApplicationPerson` fields)
330    ///
331    /// Exactly one verification method must be provided. The API will validate this at runtime.
332    ///
333    /// # Arguments
334    ///
335    /// * `request` - The user application request
336    ///
337    /// # Returns
338    ///
339    /// Returns a [`UserApplicationResponse`] containing the application information.
340    ///
341    /// # Errors
342    ///
343    /// This method can return the following errors:
344    /// - `400` - Invalid request
345    /// - `401` - Invalid authorization
346    /// - `500` - Internal server error
347    ///
348    /// # Examples
349    ///
350    /// ## Using Sumsub Share Token
351    ///
352    /// ```no_run
353    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
354    /// use rain_sdk::models::applications::CreateUserApplicationRequest;
355    ///
356    /// # #[cfg(feature = "async")]
357    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
358    /// let config = Config::new(Environment::Dev);
359    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
360    /// let client = RainClient::new(config, auth)?;
361    ///
362    /// let request = CreateUserApplicationRequest {
363    ///     // Verification method: Sumsub Share Token
364    ///     sumsub_share_token: Some("your-sumsub-token".to_string()),
365    ///     persona_share_token: None,
366    ///     // Person data fields should be None when using tokens
367    ///     id: None,
368    ///     first_name: None,
369    ///     last_name: None,
370    ///     birth_date: None,
371    ///     national_id: None,
372    ///     country_of_issue: None,
373    ///     email: None,
374    ///     phone_country_code: None,
375    ///     phone_number: None,
376    ///     address: None,
377    ///     // Required fields
378    ///     ip_address: "127.0.0.1".to_string(),
379    ///     occupation: "Engineer".to_string(),
380    ///     annual_salary: "100000".to_string(),
381    ///     account_purpose: "Business".to_string(),
382    ///     expected_monthly_volume: "5000".to_string(),
383    ///     is_terms_of_service_accepted: true,
384    ///     // Optional fields
385    ///     wallet_address: None,
386    ///     solana_address: None,
387    ///     tron_address: None,
388    ///     stellar_address: None,
389    ///     chain_id: None,
390    ///     contract_address: None,
391    ///     source_key: None,
392    ///     has_existing_documents: None,
393    /// };
394    /// let application = client.create_user_application(&request).await?;
395    /// # Ok(())
396    /// # }
397    /// ```
398    ///
399    /// ## Using Persona Share Token
400    ///
401    /// ```no_run
402    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
403    /// use rain_sdk::models::applications::CreateUserApplicationRequest;
404    ///
405    /// # #[cfg(feature = "async")]
406    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
407    /// let config = Config::new(Environment::Dev);
408    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
409    /// let client = RainClient::new(config, auth)?;
410    ///
411    /// let request = CreateUserApplicationRequest {
412    ///     // Verification method: Persona Share Token
413    ///     sumsub_share_token: None,
414    ///     persona_share_token: Some("your-persona-token".to_string()),
415    ///     // Person data fields should be None when using tokens
416    ///     id: None,
417    ///     first_name: None,
418    ///     last_name: None,
419    ///     birth_date: None,
420    ///     national_id: None,
421    ///     country_of_issue: None,
422    ///     email: None,
423    ///     phone_country_code: None,
424    ///     phone_number: None,
425    ///     address: None,
426    ///     // Required fields
427    ///     ip_address: "127.0.0.1".to_string(),
428    ///     occupation: "Engineer".to_string(),
429    ///     annual_salary: "100000".to_string(),
430    ///     account_purpose: "Business".to_string(),
431    ///     expected_monthly_volume: "5000".to_string(),
432    ///     is_terms_of_service_accepted: true,
433    ///     // Optional fields
434    ///     wallet_address: None,
435    ///     solana_address: None,
436    ///     tron_address: None,
437    ///     stellar_address: None,
438    ///     chain_id: None,
439    ///     contract_address: None,
440    ///     source_key: None,
441    ///     has_existing_documents: None,
442    /// };
443    /// let application = client.create_user_application(&request).await?;
444    /// # Ok(())
445    /// # }
446    /// ```
447    ///
448    /// ## Using Full API (IssuingApplicationPerson)
449    ///
450    /// ```no_run
451    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
452    /// use rain_sdk::models::applications::CreateUserApplicationRequest;
453    /// use rain_sdk::models::common::Address;
454    ///
455    /// # #[cfg(feature = "async")]
456    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
457    /// let config = Config::new(Environment::Dev);
458    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
459    /// let client = RainClient::new(config, auth)?;
460    ///
461    /// let request = CreateUserApplicationRequest {
462    ///     // Verification method: Full API - no tokens
463    ///     sumsub_share_token: None,
464    ///     persona_share_token: None,
465    ///     // Person data fields (required for API method)
466    ///     id: None, // Optional: only if application was previously initiated
467    ///     first_name: Some("John".to_string()),
468    ///     last_name: Some("Doe".to_string()),
469    ///     birth_date: Some("2000-01-01".to_string()),
470    ///     national_id: Some("123456789".to_string()),
471    ///     country_of_issue: Some("US".to_string()),
472    ///     email: Some("john@example.com".to_string()),
473    ///     phone_country_code: Some("1".to_string()),
474    ///     phone_number: Some("5555555555".to_string()),
475    ///     address: Some(Address {
476    ///         line1: "123 Main St".to_string(),
477    ///         line2: None,
478    ///         city: "New York".to_string(),
479    ///         region: "NY".to_string(),
480    ///         postal_code: "10001".to_string(),
481    ///         country_code: "US".to_string(),
482    ///         country: None,
483    ///     }),
484    ///     // Required fields
485    ///     ip_address: "127.0.0.1".to_string(),
486    ///     occupation: "Engineer".to_string(),
487    ///     annual_salary: "100000".to_string(),
488    ///     account_purpose: "Business".to_string(),
489    ///     expected_monthly_volume: "5000".to_string(),
490    ///     is_terms_of_service_accepted: true,
491    ///     // Optional fields
492    ///     wallet_address: None,
493    ///     solana_address: None,
494    ///     tron_address: None,
495    ///     stellar_address: None,
496    ///     chain_id: None,
497    ///     contract_address: None,
498    ///     source_key: None,
499    ///     has_existing_documents: None,
500    /// };
501    /// let application = client.create_user_application(&request).await?;
502    /// # Ok(())
503    /// # }
504    /// ```
505    #[cfg(feature = "async")]
506    pub async fn create_user_application(
507        &self,
508        request: &CreateUserApplicationRequest,
509    ) -> Result<UserApplicationResponse> {
510        let path = "/applications/user";
511        self.post(path, request).await
512    }
513
514    /// Initiate a user application
515    ///
516    /// # Arguments
517    ///
518    /// * `request` - The initiate user application request
519    ///
520    /// # Returns
521    ///
522    /// Returns a [`UserApplicationResponse`] containing the application information.
523    ///
524    /// # Errors
525    ///
526    /// This method can return the following errors:
527    /// - `400` - Invalid request
528    /// - `401` - Invalid authorization
529    /// - `500` - Internal server error
530    ///
531    /// # Examples
532    ///
533    /// ```no_run
534    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
535    /// use rain_sdk::models::applications::InitiateUserApplicationRequest;
536    ///
537    /// # #[cfg(feature = "async")]
538    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
539    /// let config = Config::new(Environment::Dev);
540    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
541    /// let client = RainClient::new(config, auth)?;
542    ///
543    /// let request = InitiateUserApplicationRequest {
544    ///     first_name: Some("John".to_string()),
545    ///     last_name: Some("Doe".to_string()),
546    ///     email: Some("john@example.com".to_string()),
547    ///     wallet_address: None,
548    /// };
549    /// let application = client.initiate_user_application(&request).await?;
550    /// # Ok(())
551    /// # }
552    /// ```
553    #[cfg(feature = "async")]
554    pub async fn initiate_user_application(
555        &self,
556        request: &InitiateUserApplicationRequest,
557    ) -> Result<UserApplicationResponse> {
558        let path = "/applications/user/initiate";
559        self.post(path, request).await
560    }
561
562    /// Get a user application by ID
563    ///
564    /// # Arguments
565    ///
566    /// * `user_id` - The unique identifier of the user
567    ///
568    /// # Returns
569    ///
570    /// Returns a [`UserApplicationResponse`] containing the application information.
571    ///
572    /// # Errors
573    ///
574    /// This method can return the following errors:
575    /// - `401` - Invalid authorization
576    /// - `404` - User not found
577    /// - `500` - Internal server error
578    ///
579    /// # Examples
580    ///
581    /// ```no_run
582    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
583    /// use uuid::Uuid;
584    ///
585    /// # #[cfg(feature = "async")]
586    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
587    /// let config = Config::new(Environment::Dev);
588    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
589    /// let client = RainClient::new(config, auth)?;
590    ///
591    /// let user_id = Uuid::new_v4();
592    /// let application = client.get_user_application(&user_id).await?;
593    /// # Ok(())
594    /// # }
595    /// ```
596    #[cfg(feature = "async")]
597    pub async fn get_user_application(&self, user_id: &Uuid) -> Result<UserApplicationResponse> {
598        let path = format!("/applications/user/{user_id}");
599        self.get(&path).await
600    }
601
602    /// Update a user application
603    ///
604    /// # Arguments
605    ///
606    /// * `user_id` - The unique identifier of the user
607    /// * `request` - The update request
608    ///
609    /// # Returns
610    ///
611    /// Returns a [`UserApplicationResponse`] containing the updated application information.
612    ///
613    /// # Errors
614    ///
615    /// This method can return the following errors:
616    /// - `400` - Invalid request
617    /// - `401` - Invalid authorization
618    /// - `404` - User not found
619    /// - `500` - Internal server error
620    ///
621    /// # Examples
622    ///
623    /// ```no_run
624    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
625    /// use rain_sdk::models::applications::UpdateUserApplicationRequest;
626    /// use uuid::Uuid;
627    ///
628    /// # #[cfg(feature = "async")]
629    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
630    /// let config = Config::new(Environment::Dev);
631    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
632    /// let client = RainClient::new(config, auth)?;
633    ///
634    /// let user_id = Uuid::new_v4();
635    /// let request = UpdateUserApplicationRequest {
636    ///     first_name: Some("John".to_string()),
637    ///     last_name: None,
638    ///     birth_date: None,
639    ///     national_id: None,
640    ///     country_of_issue: None,
641    ///     address: None,
642    ///     ip_address: None,
643    ///     occupation: None,
644    ///     annual_salary: None,
645    ///     account_purpose: None,
646    ///     expected_monthly_volume: None,
647    ///     is_terms_of_service_accepted: None,
648    ///     has_existing_documents: None,
649    /// };
650    /// let application = client.update_user_application(&user_id, &request).await?;
651    /// # Ok(())
652    /// # }
653    /// ```
654    #[cfg(feature = "async")]
655    pub async fn update_user_application(
656        &self,
657        user_id: &Uuid,
658        request: &UpdateUserApplicationRequest,
659    ) -> Result<UserApplicationResponse> {
660        let path = format!("/applications/user/{user_id}");
661        self.patch(&path, request).await
662    }
663
664    /// Upload a document for a user application
665    ///
666    /// # Arguments
667    ///
668    /// * `user_id` - The unique identifier of the user
669    /// * `params` - Document upload parameters
670    ///
671    /// # Returns
672    ///
673    /// Returns a success response.
674    ///
675    /// # Examples
676    ///
677    /// ```no_run
678    /// use rain_sdk::{RainClient, Config, Environment, AuthConfig};
679    /// use rain_sdk::models::applications::DocumentUploadParams;
680    /// use uuid::Uuid;
681    ///
682    /// # #[cfg(feature = "async")]
683    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
684    /// let config = Config::new(Environment::Dev);
685    /// let auth = AuthConfig::with_api_key("your-api-key".to_string());
686    /// let client = RainClient::new(config, auth)?;
687    ///
688    /// let user_id = Uuid::new_v4();
689    /// let params = DocumentUploadParams {
690    ///     document_type: "idCard".to_string(),
691    ///     side: "front".to_string(),
692    ///     country: Some("US".to_string()),
693    ///     country_code: Some("US".to_string()),
694    ///     name: Some("ID Card".to_string()),
695    ///     file_path: "/path/to/file.pdf".to_string(),
696    /// };
697    /// client.upload_user_document(&user_id, &params).await?;
698    /// # Ok(())
699    /// # }
700    /// ```
701    #[cfg(feature = "async")]
702    pub async fn upload_user_document(
703        &self,
704        user_id: &Uuid,
705        params: &DocumentUploadParams,
706    ) -> Result<serde_json::Value> {
707        let path = format!("/applications/user/{user_id}/document");
708        let form = self.build_user_document_form(params)?;
709        self.put_multipart(&path, form).await
710    }
711
712    // ============================================================================
713    // Helper Methods
714    // ============================================================================
715
716    #[cfg(feature = "async")]
717    fn build_company_document_form(
718        &self,
719        params: &DocumentUploadParams,
720    ) -> Result<reqwest::multipart::Form> {
721        use std::fs;
722
723        let file_bytes = fs::read(&params.file_path).map_err(|e| {
724            crate::error::RainError::Other(anyhow::anyhow!("Failed to read file: {e}"))
725        })?;
726
727        let file_name = params
728            .file_path
729            .split('/')
730            .next_back()
731            .unwrap_or("document")
732            .to_string();
733
734        let part = reqwest::multipart::Part::bytes(file_bytes)
735            .file_name(file_name)
736            .mime_str("application/octet-stream")
737            .map_err(|e| {
738                crate::error::RainError::Other(anyhow::anyhow!("Invalid MIME type: {e}"))
739            })?;
740
741        let mut form = reqwest::multipart::Form::new()
742            .part("document", part)
743            .text("type", params.document_type.clone())
744            .text("side", params.side.clone());
745
746        if let Some(ref name) = params.name {
747            form = form.text("name", name.clone());
748        }
749
750        if let Some(ref country) = params.country {
751            form = form.text("country", country.clone());
752        }
753
754        if let Some(ref country_code) = params.country_code {
755            form = form.text("countryCode", country_code.clone());
756        }
757
758        Ok(form)
759    }
760
761    #[cfg(feature = "async")]
762    fn build_user_document_form(
763        &self,
764        params: &DocumentUploadParams,
765    ) -> Result<reqwest::multipart::Form> {
766        use std::fs;
767
768        let file_bytes = fs::read(&params.file_path).map_err(|e| {
769            crate::error::RainError::Other(anyhow::anyhow!("Failed to read file: {e}"))
770        })?;
771
772        let file_name = params
773            .file_path
774            .split('/')
775            .next_back()
776            .unwrap_or("document")
777            .to_string();
778
779        let part = reqwest::multipart::Part::bytes(file_bytes)
780            .file_name(file_name)
781            .mime_str("application/octet-stream")
782            .map_err(|e| {
783                crate::error::RainError::Other(anyhow::anyhow!("Invalid MIME type: {e}"))
784            })?;
785
786        let mut form = reqwest::multipart::Form::new()
787            .part("document", part)
788            .text("type", params.document_type.clone())
789            .text("side", params.side.clone());
790
791        if let Some(ref name) = params.name {
792            form = form.text("name", name.clone());
793        }
794
795        if let Some(ref country) = params.country {
796            form = form.text("country", country.clone());
797        }
798
799        if let Some(ref country_code) = params.country_code {
800            form = form.text("countryCode", country_code.clone());
801        }
802
803        Ok(form)
804    }
805
806    // ============================================================================
807    // Blocking Methods
808    // ============================================================================
809
810    /// Create a company application (blocking)
811    #[cfg(feature = "sync")]
812    pub fn create_company_application_blocking(
813        &self,
814        request: &CreateCompanyApplicationRequest,
815    ) -> Result<CompanyApplicationResponse> {
816        let path = "/applications/company";
817        self.post_blocking(path, request)
818    }
819
820    /// Get a company application by ID (blocking)
821    #[cfg(feature = "sync")]
822    pub fn get_company_application_blocking(
823        &self,
824        company_id: &Uuid,
825    ) -> Result<CompanyApplicationResponse> {
826        let path = format!("/applications/company/{company_id}");
827        self.get_blocking(&path)
828    }
829
830    /// Update a company application (blocking)
831    #[cfg(feature = "sync")]
832    pub fn update_company_application_blocking(
833        &self,
834        company_id: &Uuid,
835        request: &UpdateCompanyApplicationRequest,
836    ) -> Result<CompanyApplicationResponse> {
837        let path = format!("/applications/company/{company_id}");
838        self.patch_blocking(&path, request)
839    }
840
841    /// Update an ultimate beneficial owner (blocking)
842    #[cfg(feature = "sync")]
843    pub fn update_ultimate_beneficial_owner_blocking(
844        &self,
845        company_id: &Uuid,
846        ubo_id: &Uuid,
847        request: &UpdateUltimateBeneficialOwnerRequest,
848    ) -> Result<CompanyApplicationResponse> {
849        let path = format!("/applications/company/{company_id}/ubo/{ubo_id}");
850        self.patch_blocking(&path, request)
851    }
852
853    /// Create a user application (blocking)
854    #[cfg(feature = "sync")]
855    pub fn create_user_application_blocking(
856        &self,
857        request: &CreateUserApplicationRequest,
858    ) -> Result<UserApplicationResponse> {
859        let path = "/applications/user";
860        self.post_blocking(path, request)
861    }
862
863    /// Initiate a user application (blocking)
864    #[cfg(feature = "sync")]
865    pub fn initiate_user_application_blocking(
866        &self,
867        request: &InitiateUserApplicationRequest,
868    ) -> Result<UserApplicationResponse> {
869        let path = "/applications/user/initiate";
870        self.post_blocking(path, request)
871    }
872
873    /// Get a user application by ID (blocking)
874    #[cfg(feature = "sync")]
875    pub fn get_user_application_blocking(&self, user_id: &Uuid) -> Result<UserApplicationResponse> {
876        let path = format!("/applications/user/{user_id}");
877        self.get_blocking(&path)
878    }
879
880    /// Update a user application (blocking)
881    #[cfg(feature = "sync")]
882    pub fn update_user_application_blocking(
883        &self,
884        user_id: &Uuid,
885        request: &UpdateUserApplicationRequest,
886    ) -> Result<UserApplicationResponse> {
887        let path = format!("/applications/user/{user_id}");
888        self.patch_blocking(&path, request)
889    }
890}