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