use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Serialize, Deserialize)]
pub enum Environment {
Sandbox,
Production,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct IAPRequest {
#[serde(rename = "receipt-data")]
pub receipt_data: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub password: Option<String>,
#[serde(rename = "exclude-old-transactions")]
pub exclude_old_transactions: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct InApp {
pub cancellation_date: Option<String>,
pub cancellation_date_ms: Option<String>,
pub cancellation_date_pst: Option<String>,
pub cancellation_reason: Option<String>,
pub expires_date: Option<String>,
pub expires_date_ms: Option<String>,
pub expires_date_pst: Option<String>,
pub is_in_intro_offer_period: Option<String>,
pub is_trial_period: String,
pub original_purchase_date: String,
pub original_purchase_date_ms: String,
pub original_purchase_date_pst: String,
pub original_transaction_id: String,
pub product_id: String,
pub promotional_offer_id: Option<String>,
pub purchase_date: String,
pub purchase_date_ms: String,
pub purchase_date_pst: String,
pub quantity: String,
pub transaction_id: String,
pub web_order_line_item_id: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LatestReceiptInfo {
pub subscription_group_identifier: Option<String>,
pub in_app_ownership_type: Option<String>,
pub is_upgraded: Option<String>,
#[serde(flatten)]
pub in_app: InApp,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Receipt {
pub adam_id: i64,
pub app_item_id: i64,
pub application_version: String,
pub bundle_id: String,
pub download_id: i64,
pub expiration_date: Option<String>,
pub expiration_date_ms: Option<String>,
pub expiration_date_pst: Option<String>,
pub in_app: Vec<InApp>,
pub original_application_version: String,
pub original_purchase_date: String,
pub original_purchase_date_ms: String,
pub original_purchase_date_pst: String,
pub preorder_date: Option<String>,
pub preorder_date_ms: Option<String>,
pub preorder_date_pst: Option<String>,
pub receipt_creation_date: String,
pub receipt_creation_date_ms: String,
pub receipt_creation_date_pst: String,
pub receipt_type: String,
pub request_date: String,
pub request_date_ms: String,
pub request_date_pst: String,
pub version_external_identifier: i64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PendingRenewalInfo {
pub auto_renew_product_id: String,
pub auto_renew_status: String,
pub expiration_intent: Option<String>,
pub grace_period_expires_date: Option<String>,
pub grace_period_expires_date_ms: Option<String>,
pub grace_period_expires_date_pst: Option<String>,
pub is_in_billing_retry_period: String,
pub offer_code_ref_name: Option<String>,
pub original_transacyytion_id: String,
pub price_consent_status: String,
pub product_id: String,
pub promotional_offer_id: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct IAPResponse {
pub status: i32,
pub environment: Option<Environment>,
pub receipt: Option<Receipt>,
pub is_retryable: Option<bool>,
pub latest_receipt_info: Option<Vec<LatestReceiptInfo>>,
pub latest_receipt: Option<String>,
pub pending_renewal_info: Option<Vec<PendingRenewalInfo>>,
}
#[derive(Error, Debug, PartialEq)]
pub enum AppStoreVerifyError {
#[error("The App Store cloud not read the JSON object you provided.")]
InvalidJson,
#[error("The data in the receipt-data property was malformed or missing.")]
InvalidReceiptData,
#[error("The receipt could not be authenticated.")]
ReceiptUnauthenticated,
#[error(
"The shared secret you provided does not match the shared secret on file for your account."
)]
InvalidSharedSecret,
#[error("The receipt server is not currently available.")]
ServerUnavailable,
#[error("This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.")]
ReceiptIsForTest,
#[error("This receipt is from the production environment, but it was sent to the production environment for verification. Send it to the production environment instead.")]
ReceiptIsForProduction,
#[error(
"The receipt could not be authorized. Treat this the same as if a purchase was never made."
)]
ReceiptUnauthorized,
#[error("Internal data access error.")]
InternalDataAccessError,
#[error("An unknown error occurred")]
Unknown,
}
impl IAPResponse {
pub fn error(&self) -> Option<AppStoreVerifyError> {
match self.status {
0 => None,
21000 => Some(AppStoreVerifyError::InvalidJson),
21002 => Some(AppStoreVerifyError::InvalidReceiptData),
21003 => Some(AppStoreVerifyError::ReceiptUnauthenticated),
21004 => Some(AppStoreVerifyError::InvalidSharedSecret),
21005 => Some(AppStoreVerifyError::ServerUnavailable),
21007 => Some(AppStoreVerifyError::ReceiptIsForTest),
21008 => Some(AppStoreVerifyError::ReceiptIsForProduction),
21009 | 21100..=21199 => Some(AppStoreVerifyError::InternalDataAccessError),
21010 => Some(AppStoreVerifyError::ReceiptUnauthorized),
_ => Some(AppStoreVerifyError::Unknown),
}
}
}