rovenue/offerings/
client.rs1use std::sync::Arc;
2
3use crate::cache::offerings::OfferingsCacheRepo;
4use crate::cache::CacheStore;
5use crate::error::{ErrorKind, RovenueError, RovenueResult};
6use crate::time::{Clock, SystemClock};
7use crate::transport::api::ApiEnvelope;
8use crate::transport::http_client::HttpClient;
9use crate::transport::types::HttpRequest;
10
11use super::types::{
12 CoreOffering, CoreOfferingProduct, CoreOfferings, OfferingWire, OfferingsResponse,
13};
14
15const OFFERINGS_RESOURCE: &str = "offerings";
17
18pub struct OfferingsClient {
19 http: Arc<HttpClient>,
20 store: Arc<CacheStore>,
21 clock: Arc<dyn Clock>,
22}
23
24impl OfferingsClient {
25 pub fn new(http: Arc<HttpClient>, store: Arc<CacheStore>) -> Self {
26 Self {
27 http,
28 store,
29 clock: Arc::new(SystemClock),
30 }
31 }
32
33 pub fn with_clock(mut self, clock: Arc<dyn Clock>) -> Self {
34 self.clock = clock;
35 self
36 }
37
38 pub fn get_offerings(&self) -> RovenueResult<CoreOfferings> {
50 match self
51 .http
52 .get_json::<ApiEnvelope<OfferingsResponse>>(HttpRequest::new("/v1/offerings"))
53 {
54 Ok(resp) => {
55 let body = resp.body.ok_or(RovenueError::Internal())?;
56 if let Ok(raw) = serde_json::to_string(&body.data) {
59 let _ = OfferingsCacheRepo::new(&self.store).put(
60 OFFERINGS_RESOURCE,
61 &raw,
62 self.clock.now_unix_ms(),
63 );
64 }
65 Ok(map_response(body.data))
66 }
67 Err(e) if matches!(e.kind, ErrorKind::NetworkUnavailable | ErrorKind::Timeout) => {
68 match OfferingsCacheRepo::new(&self.store).get(OFFERINGS_RESOURCE)? {
71 Some(raw) => {
72 let parsed: OfferingsResponse =
73 serde_json::from_str(&raw).map_err(|_| RovenueError::Internal())?;
74 Ok(map_response(parsed))
75 }
76 None => Err(e),
77 }
78 }
79 Err(e) => Err(e),
80 }
81 }
82}
83
84fn map_response(resp: OfferingsResponse) -> CoreOfferings {
85 let offerings: Vec<CoreOffering> = resp.offerings.into_iter().map(map_offering).collect();
86 let current = offerings
87 .iter()
88 .find(|o| o.is_default)
89 .map(|o| o.identifier.clone());
90 CoreOfferings { current, offerings }
91}
92
93pub(crate) fn map_offering(o: OfferingWire) -> CoreOffering {
97 CoreOffering {
98 identifier: o.identifier,
99 is_default: o.is_default,
100 packages: o
101 .packages
102 .into_iter()
103 .map(|p| CoreOfferingProduct {
104 package_identifier: p.package_identifier,
105 identifier: p.identifier,
106 product_type: p.product_type,
107 display_name: p.display_name,
108 apple_product_id: p.store_ids.apple,
109 google_product_id: p.store_ids.google,
110 android_base_plan_id: p.android_base_plan_id,
111 android_offer_id: p.android_offer_id,
112 })
113 .collect(),
114 }
115}