Skip to main content

app_store_server_library/models/
transaction_commitment_info.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_with::formats::Flexible;
4use serde_with::TimestampMilliSeconds;
5
6/// The inclusive bounds Apple enforces on a commitment's billing period number.
7const MINIMUM_PERIOD: i32 = 1;
8const MAXIMUM_PERIOD: i32 = 12;
9
10/// Information about a subscription commitment.
11///
12/// [TransactionCommitmentInfo](https://developer.apple.com/documentation/appstoreserverapi/transactioncommitmentinfo)
13#[serde_with::serde_as]
14#[derive(Debug, Clone, Deserialize, Serialize, Hash)]
15#[serde(rename_all = "camelCase")]
16pub struct TransactionCommitmentInfo {
17    /// The number of the billing period when the commitment expires.
18    ///
19    /// [billingPeriodNumber](https://developer.apple.com/documentation/appstoreserverapi/billingperiodnumber)
20    pub billing_period_number: Option<i32>,
21
22    /// The UNIX time, in milliseconds, when the commitment expires.
23    ///
24    /// [commitmentExpiresDate](https://developer.apple.com/documentation/appstoreserverapi/commitmentexpiresdate)
25    #[serde_as(as = "Option<TimestampMilliSeconds<String, Flexible>>")]
26    pub commitment_expires_date: Option<DateTime<Utc>>,
27
28    /// The price of the subscription after the commitment period ends.
29    ///
30    /// [commitmentPrice](https://developer.apple.com/documentation/appstoreserverapi/commitmentprice)
31    pub commitment_price: Option<i64>,
32
33    /// The total number of billing periods in the commitment.
34    ///
35    /// [totalBillingPeriods](https://developer.apple.com/documentation/appstoreserverapi/totalbillingperiods)
36    pub total_billing_periods: Option<i32>,
37}
38
39impl TransactionCommitmentInfo {
40    /// Validates `billing_period_number`, the only field Apple's library validates.
41    ///
42    /// # Errors
43    ///
44    /// Returns `TransactionCommitmentInfoValidationError::BillingPeriodNumberOutOfRange`
45    /// when `billing_period_number` is present and outside 1..=12. A `None` value is
46    /// valid, matching Swift, which skips validation when the field is absent.
47    pub fn validate(&self) -> Result<(), TransactionCommitmentInfoValidationError> {
48        if let Some(billing_period) = self.billing_period_number {
49            if !(MINIMUM_PERIOD..=MAXIMUM_PERIOD).contains(&billing_period) {
50                return Err(
51                    TransactionCommitmentInfoValidationError::BillingPeriodNumberOutOfRange { value: billing_period },
52                );
53            }
54        }
55        Ok(())
56    }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum TransactionCommitmentInfoValidationError {
61    BillingPeriodNumberOutOfRange { value: i32 },
62}
63
64impl std::fmt::Display for TransactionCommitmentInfoValidationError {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        match self {
67            TransactionCommitmentInfoValidationError::BillingPeriodNumberOutOfRange { value } => {
68                write!(
69                    f,
70                    "billingPeriodNumber must be between {} and {} inclusive, got {}",
71                    MINIMUM_PERIOD, MAXIMUM_PERIOD, value
72                )
73            }
74        }
75    }
76}
77
78impl std::error::Error for TransactionCommitmentInfoValidationError {}