rovenue/purchases/
client.rs1use std::sync::Arc;
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::{RovenueError, RovenueResult};
6use crate::transport::api::ApiEnvelope;
7use crate::transport::http_client::HttpClient;
8use crate::transport::types::HttpPostRequest;
9
10#[derive(Debug, Serialize)]
12struct OfferSignatureBody<'a> {
13 #[serde(rename = "productId")]
14 product_id: &'a str,
15 #[serde(rename = "offerId")]
16 offer_id: &'a str,
17 #[serde(rename = "appAccountToken", skip_serializing_if = "Option::is_none")]
18 app_account_token: Option<&'a str>,
19}
20
21#[derive(Debug, Deserialize)]
23struct OfferSignatureResponse {
24 #[serde(rename = "keyIdentifier")]
25 key_identifier: String,
26 nonce: String,
27 signature: String,
28 timestamp: i64,
29}
30
31#[derive(Debug, Clone, PartialEq)]
33pub struct AppleOfferSignature {
34 pub key_identifier: String,
35 pub nonce: String,
36 pub signature: String,
37 pub timestamp: i64,
38}
39
40pub struct PurchasesClient {
41 http: Arc<HttpClient>,
42}
43
44impl PurchasesClient {
45 pub fn new(http: Arc<HttpClient>) -> Self {
46 Self { http }
47 }
48
49 pub fn get_apple_offer_signature(
54 &self,
55 product_id: &str,
56 offer_id: &str,
57 app_account_token: Option<&str>,
58 ) -> RovenueResult<AppleOfferSignature> {
59 let body = OfferSignatureBody {
60 product_id,
61 offer_id,
62 app_account_token,
63 };
64 let resp = self
65 .http
66 .post_json::<OfferSignatureBody<'_>, ApiEnvelope<OfferSignatureResponse>>(
67 HttpPostRequest::new("/v1/purchases/apple-offer-signature"),
68 &body,
69 )?;
70 let data = resp.body.ok_or(RovenueError::Internal())?.data;
71 Ok(AppleOfferSignature {
72 key_identifier: data.key_identifier,
73 nonce: data.nonce,
74 signature: data.signature,
75 timestamp: data.timestamp,
76 })
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use std::time::Duration;
84
85 #[test]
86 fn get_apple_offer_signature_parses_response() {
87 let mut server = mockito::Server::new();
88 let _m = server
89 .mock("POST", "/v1/purchases/apple-offer-signature")
90 .with_status(200)
91 .with_header("content-type", "application/json")
92 .with_body(
93 r#"{"data":{"keyIdentifier":"K","nonce":"n","signature":"s","timestamp":123}}"#,
94 )
95 .create();
96
97 let http = Arc::new(
98 HttpClient::new(server.url(), "pk_test".into())
99 .with_max_attempts(1)
100 .with_request_timeout(Duration::from_millis(500)),
101 );
102 let client = PurchasesClient::new(http);
103 let result = client
104 .get_apple_offer_signature("prod_id", "offer_id", None)
105 .expect("signature ok");
106
107 assert_eq!(result.key_identifier, "K");
108 assert_eq!(result.nonce, "n");
109 assert_eq!(result.signature, "s");
110 assert_eq!(result.timestamp, 123);
111 }
112
113 #[test]
114 fn get_apple_offer_signature_includes_app_account_token_when_some() {
115 let mut server = mockito::Server::new();
116 let _m = server
117 .mock("POST", "/v1/purchases/apple-offer-signature")
118 .match_body(mockito::Matcher::JsonString(
119 r#"{"productId":"prod_id","offerId":"offer_id","appAccountToken":"tok-abc"}"#
120 .into(),
121 ))
122 .with_status(200)
123 .with_header("content-type", "application/json")
124 .with_body(
125 r#"{"data":{"keyIdentifier":"K2","nonce":"n2","signature":"s2","timestamp":456}}"#,
126 )
127 .create();
128
129 let http = Arc::new(
130 HttpClient::new(server.url(), "pk_test".into())
131 .with_max_attempts(1)
132 .with_request_timeout(Duration::from_millis(500)),
133 );
134 let client = PurchasesClient::new(http);
135 let result = client
136 .get_apple_offer_signature("prod_id", "offer_id", Some("tok-abc"))
137 .expect("signature ok");
138
139 assert_eq!(result.key_identifier, "K2");
140 assert_eq!(result.timestamp, 456);
141 }
142
143 #[test]
144 fn get_apple_offer_signature_omits_app_account_token_when_none() {
145 let mut server = mockito::Server::new();
146 let _m = server
147 .mock("POST", "/v1/purchases/apple-offer-signature")
148 .match_body(mockito::Matcher::JsonString(
149 r#"{"productId":"prod_id","offerId":"offer_id"}"#.into(),
150 ))
151 .with_status(200)
152 .with_header("content-type", "application/json")
153 .with_body(
154 r#"{"data":{"keyIdentifier":"K3","nonce":"n3","signature":"s3","timestamp":789}}"#,
155 )
156 .create();
157
158 let http = Arc::new(
159 HttpClient::new(server.url(), "pk_test".into())
160 .with_max_attempts(1)
161 .with_request_timeout(Duration::from_millis(500)),
162 );
163 let client = PurchasesClient::new(http);
164 let result = client
165 .get_apple_offer_signature("prod_id", "offer_id", None)
166 .expect("signature ok");
167
168 assert_eq!(result.key_identifier, "K3");
169 assert_eq!(result.timestamp, 789);
170 }
171}