1mod orders;
2mod orders_cancel;
3mod orders_capture;
4mod orders_id;
5mod orders_refund;
6mod orders_submit;
7mod orders_subscriptions;
8mod orders_subscriptions_id;
9mod orders_subscriptions_recur;
10mod serde_help;
11use std::sync::Arc;
12
13use builder_pattern::Builder;
14use bytes::Bytes;
15pub use orders::*;
16pub use orders_cancel::*;
17pub use orders_capture::*;
18pub use orders_id::*;
19pub use orders_refund::*;
20pub use orders_submit::*;
21pub use orders_subscriptions::*;
22pub use orders_subscriptions_id::*;
23pub use orders_subscriptions_recur::*;
24
25pub trait HttpClient: Clone {
26 fn send<T: serde::de::DeserializeOwned>(
27 &self,
28 request: YandexPayApiRequest,
29 ) -> impl Future<Output = R<T>>;
30}
31
32pub(crate) type R<T = (), E = YandexPayApiError> = std::result::Result<T, E>;
33pub(crate) type Time = chrono::DateTime<chrono::Utc>;
34
35#[derive(Debug, thiserror::Error)]
36#[error("Yandex Pay API error: {0}")]
37pub enum YandexPayApiError {
38 #[error("Yandex Pay reqwest error: {0}")]
39 Reqwest(#[from] reqwest::Error),
40 #[error("Yandex Pay serde error: {0}")]
41 Serde(#[from] serde_json::Error),
42 #[error("Yandex Pay API error: {0}")]
43 Api(YandexPayApiResponseError),
44}
45
46pub(crate) type S = Arc<str>;
47#[cfg(not(feature = "reqwest"))]
48#[derive(Debug, Clone)]
49pub struct YandexPayApi<C: HttpClient> {
50 pub client: C,
51 pub base_url: S,
52 pub api_key: S,
53}
54
55#[cfg(feature = "reqwest")]
56#[derive(Debug, Clone)]
57pub struct YandexPayApi<C: HttpClient = reqwest::Client> {
58 pub client: C,
59 pub base_url: S,
60 pub api_key: S,
61}
62impl<C: HttpClient> YandexPayApi<C> {
63 pub fn new(base_url: S, api_key: S, client: C) -> Self {
64 YandexPayApi {
65 client,
66 base_url,
67 api_key,
68 }
69 }
70
71 pub fn get_base_url(&self) -> &str {
72 &self.base_url
73 }
74
75 pub fn get_api_key(&self) -> &str {
76 &self.api_key
77 }
78}
79
80impl<C: HttpClient> YandexPayApi<C> {
82 pub async fn create_order(&self, request: CreateOrderRequest) -> R<CreateOrderResponse> {
86 let url = format!("{}/api/merchant/v1/orders", self.base_url);
87 let bytes = serde_json::to_vec(&request)?;
88 let r = YandexPayApiRequest::new()
89 .url(url)
90 .method(Method::Post)
91 .body(Some(bytes.into()))
92 .api_key(self.api_key.clone())
93 .build();
94 let response = self.client.send(r).await?;
95 Ok(response)
96 }
97 pub async fn get_order(&self, order_id: impl Into<String>) -> R<OrderResponseData> {
101 let url = format!(
102 "{}/api/merchant/v1/orders/{}",
103 self.base_url,
104 order_id.into()
105 );
106 let r = YandexPayApiRequest::new()
107 .url(url)
108 .api_key(self.api_key.clone())
109 .build();
110 let response = self.client.send(r).await?;
111 Ok(response)
112 }
113 pub async fn cancel_order(
117 &self,
118 order_id: impl Into<String>,
119 request: CancelOrderRequest,
120 ) -> R<OperationResponseData> {
121 let url = format!(
122 "{}/api/merchant/v1/orders/{}/cancel",
123 self.base_url,
124 order_id.into()
125 );
126 let bytes = serde_json::to_vec(&request)?;
127 let r = YandexPayApiRequest::new()
128 .url(url)
129 .api_key(self.api_key.clone())
130 .method(Method::Post)
131 .body(Some(bytes.into()))
132 .build();
133 let response = self.client.send(r).await?;
134 Ok(response)
135 }
136 pub async fn refund_order(
161 &self,
162 order_id: impl Into<String>,
163 request: RefundRequest,
164 ) -> R<OperationResponseData> {
165 let url = format!(
166 "{}/api/merchant/v2/orders/{}/refund",
167 self.base_url,
168 order_id.into()
169 );
170 let bytes = serde_json::to_vec(&request)?;
171 let r = YandexPayApiRequest::new()
172 .url(url)
173 .api_key(self.api_key.clone())
174 .method(Method::Post)
175 .body(Some(bytes.into()))
176 .build();
177 let response = self.client.send(r).await?;
178 Ok(response)
179 }
180 pub async fn capture_order(
186 &self,
187 order_id: impl Into<String>,
188 request: CaptureOrderRequest,
189 ) -> R<OperationResponseData> {
190 let url = format!(
191 "{}/api/merchant/v1/orders/{}/capture",
192 self.base_url,
193 order_id.into()
194 );
195 let bytes = serde_json::to_vec(&request)?;
196 let r = YandexPayApiRequest::new()
197 .url(url)
198 .api_key(self.api_key.clone())
199 .method(Method::Post)
200 .body(Some(bytes.into()))
201 .build();
202 let response = self.client.send(r).await?;
203 Ok(response)
204 }
205 pub async fn rollback_order(&self, order_id: impl Into<String>) -> R<serde_json::Value> {
209 let url = format!(
210 "{}/api/merchant/v1/orders/{}/rollback",
211 self.base_url,
212 order_id.into()
213 );
214 let r = YandexPayApiRequest::new()
215 .url(url)
216 .api_key(self.api_key.clone())
217 .method(Method::Post)
218 .build();
219 let response = self.client.send(r).await?;
220 Ok(response)
221 }
222
223 pub async fn submit_order(
229 &self,
230 order_id: impl Into<String>,
231 request: SubmitRequest,
232 ) -> R<OperationResponseData> {
233 let url = format!(
234 "{}/api/merchant/v1/orders/{}/submit",
235 self.base_url,
236 order_id.into()
237 );
238 let bytes = serde_json::to_vec(&request)?;
239 let r = YandexPayApiRequest::new()
240 .url(url)
241 .api_key(self.api_key.clone())
242 .method(Method::Post)
243 .body(Some(bytes.into()))
244 .build();
245 let response = self.client.send(r).await?;
246 Ok(response)
247 }
248
249 pub async fn get_operation(
250 &self,
251 external_operation_id: impl Into<String>,
252 ) -> R<OperationResponseData> {
253 let url = format!(
254 "{}/api/merchant/v1/operations/{}",
255 self.base_url,
256 external_operation_id.into()
257 );
258 let r = YandexPayApiRequest::new()
259 .url(url)
260 .api_key(self.api_key.clone())
261 .method(Method::Get)
262 .build();
263 let response = self.client.send(r).await?;
264 Ok(response)
265 }
266 pub async fn create_subscription(
270 &self,
271 subscription: CreateSubscriptionRequest,
272 ) -> Result<CreateSubscriptionResponseData, YandexPayApiError> {
273 let url = format!("{}/api/merchant/v1/subscriptions", self.base_url);
274 let bytes = serde_json::to_vec(&subscription)?;
275 let r = YandexPayApiRequest::new()
276 .url(url)
277 .api_key(self.api_key.clone())
278 .method(Method::Post)
279 .body(Some(bytes.into()))
280 .build();
281 let response = self.client.send(r).await?;
282 Ok(response)
283 }
284
285 pub async fn recur_subscription(
289 &self,
290 subscription: CreateRecurrentChargeRequest,
291 ) -> Result<RecurSubscriptionResponseData, YandexPayApiError> {
292 let url = format!("{}/api/merchant/v1/subscriptions/recur", self.base_url);
293 let bytes = serde_json::to_vec(&subscription)?;
294 let r = YandexPayApiRequest::new()
295 .url(url)
296 .api_key(self.api_key.clone())
297 .method(Method::Post)
298 .body(Some(bytes.into()))
299 .build();
300 let response = self.client.send(r).await?;
301 Ok(response)
302 }
303
304 pub async fn get_subscription(
308 &self,
309 customer_subscription_id: impl Into<String>,
311 request: GetSubscriptionRequest,
312 ) -> Result<CustomerSubscriptionResponseData, YandexPayApiError> {
313 let url = format!(
314 "{}/api/merchant/v1/subscriptions/{}",
315 self.base_url,
316 customer_subscription_id.into()
317 );
318 let bytes = serde_json::to_vec(&request)?;
319 let r = YandexPayApiRequest::new()
320 .url(url)
321 .api_key(self.api_key.clone())
322 .method(Method::Get)
323 .body(Some(bytes.into()))
324 .build();
325 let response = self.client.send(r).await?;
326 Ok(response)
327 }
328}
329
330#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
331pub enum Method {
332 Get,
333 Post,
334}
335
336#[derive(Debug, Clone, Builder)]
337pub struct YandexPayApiRequest {
338 #[default(None)]
339 pub body: Option<Bytes>,
341 #[default(Method::Get)]
342 pub method: Method,
344 #[into]
345 pub url: S,
347 #[into]
348 pub api_key: S,
350 #[into]
351 #[default(default_request_id())]
352 pub request_id: S,
354 #[default(9999)]
355 pub request_timeout: u32,
357 #[default(0)]
358 pub request_attempt: u32,
360}
361
362fn default_request_id() -> S {
363 uuid::Uuid::now_v7().to_string().into()
364}
365#[cfg(feature = "reqwest")]
366impl HttpClient for reqwest::Client {
367 fn send<T: serde::de::DeserializeOwned>(
368 &self,
369 request: YandexPayApiRequest,
370 ) -> impl Future<Output = R<T>> {
371 let client = self.clone();
372
373 async move {
374 let body = request.body.clone();
375 let method = match request.method {
376 Method::Get => reqwest::Method::GET,
377 Method::Post => reqwest::Method::POST,
378 };
379 let mut request_builder = client
380 .request(method, &*request.url)
381 .header("Authorization", format!("Api-Key {}", request.api_key))
382 .header("X-Request-Id", &*request.request_id)
383 .header("X-Request-Timeout", request.request_timeout.to_string())
384 .header("X-Request-Attempt", request.request_attempt.to_string())
385 .header("Content-Type", "application/json");
386 if let Some(body) = body {
387 request_builder = request_builder.body(body);
388 }
389 let response = request_builder.send().await?;
390
391 if response.status().is_success() {
392 let result = response.text().await?;
393 let result = serde_json::from_str::<YandexPayApiResponse<T>>(&result)?;
394 Ok(result.data)
395 } else {
396 let error_message = response.text().await?;
397 tracing::error!("{}", error_message);
398 let error = serde_json::from_str::<YandexPayApiResponseError>(&error_message)?;
399 Err(YandexPayApiError::Api(error))
400 }
401 }
402 }
403}
404
405#[derive(Debug, serde::Deserialize)]
406pub struct YandexPayApiResponse<T> {
407 pub data: T,
408 pub code: Option<u32>,
409 pub status: Option<String>,
410}
411
412#[derive(Debug, serde::Deserialize)]
413pub struct YandexPayApiResponseError {
414 pub code: Option<u32>,
415 pub status: Option<String>,
416 #[serde(default = "Default::default")]
417 pub message: S,
418}
419
420impl std::fmt::Display for YandexPayApiResponseError {
421 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422 write!(
423 f,
424 "Yandex Pay API error: StatusCode: {:?}, Status: {:?}, Message: {}",
425 self.code, self.status, self.message
426 )
427 }
428}