app_store_server_library/
jws_signature_creator.rs1use base64::engine::general_purpose::STANDARD as BASE64;
2use base64::Engine;
3use chrono::Utc;
4use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7use uuid::Uuid;
8
9#[derive(Error, Debug)]
10pub enum JWSSignatureCreatorError {
11 #[error("InvalidPrivateKey")]
12 InvalidPrivateKey,
13
14 #[error("JWTEncodingError: [{0}]")]
15 JWTEncodingError(#[from] jsonwebtoken::errors::Error),
16
17 #[error("SerializationError: [{0}]")]
18 SerializationError(#[from] serde_json::Error),
19}
20
21#[derive(Debug, Serialize, Deserialize)]
22struct BasePayload {
23 nonce: String,
24 iss: String,
25 bid: String,
26 aud: String,
27 iat: i64,
28}
29
30#[derive(Debug, Serialize, Deserialize)]
31struct PromotionalOfferV2Payload {
32 #[serde(flatten)]
33 base: BasePayload,
34 #[serde(rename = "productId")]
35 product_id: String,
36 #[serde(rename = "offerIdentifier")]
37 offer_identifier: String,
38 #[serde(rename = "transactionId", skip_serializing_if = "Option::is_none")]
39 transaction_id: Option<String>,
40}
41
42#[derive(Debug, Serialize, Deserialize)]
43struct IntroductoryOfferEligibilityPayload {
44 #[serde(flatten)]
45 base: BasePayload,
46 #[serde(rename = "productId")]
47 product_id: String,
48 #[serde(rename = "allowIntroductoryOffer")]
49 allow_introductory_offer: bool,
50 #[serde(rename = "transactionId")]
51 transaction_id: String,
52}
53
54#[derive(Debug, Serialize, Deserialize)]
55struct AdvancedCommerceInAppPayload {
56 #[serde(flatten)]
57 base: BasePayload,
58 request: String,
59}
60
61pub trait AdvancedCommerceInAppRequest: Serialize {}
63
64struct JWSSignatureCreator {
66 audience: String,
67 signing_key: EncodingKey,
68 key_id: String,
69 issuer_id: String,
70 bundle_id: String,
71}
72
73impl JWSSignatureCreator {
74 fn new(
75 audience: String,
76 signing_key: &str,
77 key_id: String,
78 issuer_id: String,
79 bundle_id: String,
80 ) -> Result<Self, JWSSignatureCreatorError> {
81 let key = EncodingKey::from_ec_pem(signing_key.as_bytes())
82 .map_err(|_| JWSSignatureCreatorError::InvalidPrivateKey)?;
83
84 Ok(Self {
85 audience,
86 signing_key: key,
87 key_id,
88 issuer_id,
89 bundle_id,
90 })
91 }
92
93 fn get_base_payload(&self) -> BasePayload {
94 BasePayload {
95 nonce: Uuid::new_v4().to_string(),
96 iss: self.issuer_id.clone(),
97 bid: self.bundle_id.clone(),
98 aud: self.audience.clone(),
99 iat: Utc::now().timestamp(),
100 }
101 }
102
103 fn create_signature<T: Serialize>(&self, payload: &T) -> Result<String, JWSSignatureCreatorError> {
104 let mut header = Header::new(Algorithm::ES256);
105 header.kid = Some(self.key_id.clone());
106 header.typ = Some("JWT".to_string());
107
108 let token = encode(&header, payload, &self.signing_key)?;
109 Ok(token)
110 }
111}
112
113pub struct PromotionalOfferV2SignatureCreator {
115 base: JWSSignatureCreator,
116}
117
118impl PromotionalOfferV2SignatureCreator {
119 pub fn new(
132 signing_key: &str,
133 key_id: String,
134 issuer_id: String,
135 bundle_id: String,
136 ) -> Result<Self, JWSSignatureCreatorError> {
137 let base = JWSSignatureCreator::new(
138 "promotional-offer".to_string(),
139 signing_key,
140 key_id,
141 issuer_id,
142 bundle_id,
143 )?;
144
145 Ok(Self { base })
146 }
147
148 pub fn create_signature(
166 &self,
167 product_id: &str,
168 offer_identifier: &str,
169 transaction_id: Option<String>,
170 ) -> Result<String, JWSSignatureCreatorError> {
171 let base_payload = self.base.get_base_payload();
172 let payload = PromotionalOfferV2Payload {
173 base: base_payload,
174 product_id: product_id.to_string(),
175 offer_identifier: offer_identifier.to_string(),
176 transaction_id,
177 };
178
179 self.base.create_signature(&payload)
180 }
181}
182
183pub struct IntroductoryOfferEligibilitySignatureCreator {
185 base: JWSSignatureCreator,
186}
187
188impl IntroductoryOfferEligibilitySignatureCreator {
189 pub fn new(
202 signing_key: &str,
203 key_id: String,
204 issuer_id: String,
205 bundle_id: String,
206 ) -> Result<Self, JWSSignatureCreatorError> {
207 let base = JWSSignatureCreator::new(
208 "introductory-offer-eligibility".to_string(),
209 signing_key,
210 key_id,
211 issuer_id,
212 bundle_id,
213 )?;
214
215 Ok(Self { base })
216 }
217
218 pub fn create_signature(
237 &self,
238 product_id: &str,
239 allow_introductory_offer: bool,
240 transaction_id: &str,
241 ) -> Result<String, JWSSignatureCreatorError> {
242 let base_payload = self.base.get_base_payload();
243 let payload = IntroductoryOfferEligibilityPayload {
244 base: base_payload,
245 product_id: product_id.to_string(),
246 allow_introductory_offer,
247 transaction_id: transaction_id.to_string(),
248 };
249
250 self.base.create_signature(&payload)
251 }
252}
253
254pub struct AdvancedCommerceInAppSignatureCreator {
256 base: JWSSignatureCreator,
257}
258
259impl AdvancedCommerceInAppSignatureCreator {
260 pub fn new(
273 signing_key: &str,
274 key_id: String,
275 issuer_id: String,
276 bundle_id: String,
277 ) -> Result<Self, JWSSignatureCreatorError> {
278 let base = JWSSignatureCreator::new(
279 "advanced-commerce-api".to_string(),
280 signing_key,
281 key_id,
282 issuer_id,
283 bundle_id,
284 )?;
285
286 Ok(Self { base })
287 }
288
289 pub fn create_signature<T: AdvancedCommerceInAppRequest>(
303 &self,
304 advanced_commerce_in_app_request: &T,
305 ) -> Result<String, JWSSignatureCreatorError> {
306 let json_data = serde_json::to_vec(advanced_commerce_in_app_request)?;
307 let base64_encoded_body = BASE64.encode(&json_data);
308
309 let base_payload = self.base.get_base_payload();
310 let payload = AdvancedCommerceInAppPayload {
311 base: base_payload,
312 request: base64_encoded_body,
313 };
314
315 self.base.create_signature(&payload)
316 }
317}