Skip to main content

storekit/
transaction.rs

1use core::ffi::c_void;
2use core::ptr;
3use std::ptr::NonNull;
4use std::time::Duration;
5
6use serde::{Deserialize, Serialize};
7
8use crate::advanced_commerce::{
9    TransactionAdvancedCommerceInfo, TransactionAdvancedCommerceInfoPayload,
10};
11use crate::app_store::AppStoreEnvironment;
12use crate::error::{StoreKitError, VerificationFailure};
13use crate::ffi;
14use crate::private::{
15    cstring_from_str, decode_base64, duration_to_timeout_ms, error_from_status, json_cstring,
16    parse_json_ptr, parse_optional_json_ptr,
17};
18use crate::product::ProductType;
19use crate::refund::{Refund, RefundRequestStatus};
20use crate::storefront::{Storefront, StorefrontPayload};
21use crate::subscription::{SubscriptionPeriod, SubscriptionPeriodPayload};
22pub use crate::verification_result::VerificationResult;
23
24use crate::verification_result::VerificationResultPayload;
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27/// Wraps `StoreKit.Transaction.Reason`.
28pub enum TransactionReason {
29    /// Represents the `Purchase` `StoreKit` case.
30    Purchase,
31    /// Represents the `Renewal` `StoreKit` case.
32    Renewal,
33    /// Preserves an unrecognized `StoreKit` case.
34    Unknown(String),
35}
36
37impl TransactionReason {
38    /// Returns the raw `StoreKit` string for this transaction reason.
39    pub fn as_str(&self) -> &str {
40        match self {
41            Self::Purchase => "purchase",
42            Self::Renewal => "renewal",
43            Self::Unknown(value) => value.as_str(),
44        }
45    }
46
47    fn from_raw(raw: String) -> Self {
48        match raw.as_str() {
49            "purchase" => Self::Purchase,
50            "renewal" => Self::Renewal,
51            _ => Self::Unknown(raw),
52        }
53    }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57/// Wraps `StoreKit.Transaction.RevocationReason`.
58pub enum RevocationReason {
59    /// Represents the `DeveloperIssue` `StoreKit` case.
60    DeveloperIssue,
61    /// Represents the `other` `StoreKit` case.
62    Other,
63    /// Preserves an unrecognized `StoreKit` case.
64    Unknown(String),
65}
66
67impl RevocationReason {
68    /// Returns the raw `StoreKit` string for this revocation reason.
69    pub fn as_str(&self) -> &str {
70        match self {
71            Self::DeveloperIssue => "developerIssue",
72            Self::Other => "other",
73            Self::Unknown(value) => value.as_str(),
74        }
75    }
76
77    fn from_raw(raw: String) -> Self {
78        match raw.as_str() {
79            "developerIssue" => Self::DeveloperIssue,
80            "other" => Self::Other,
81            _ => Self::Unknown(raw),
82        }
83    }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
87/// Wraps the offer type attached to `StoreKit.Transaction`.
88pub enum OfferType {
89    /// Represents the `Introductory` `StoreKit` case.
90    Introductory,
91    /// Represents the `Promotional` `StoreKit` case.
92    Promotional,
93    /// Represents the `Code` `StoreKit` case.
94    Code,
95    /// Represents the `WinBack` `StoreKit` case.
96    WinBack,
97    /// Preserves an unrecognized `StoreKit` case.
98    Unknown(String),
99}
100
101impl OfferType {
102    /// Returns the raw `StoreKit` string for this offer type.
103    pub fn as_str(&self) -> &str {
104        match self {
105            Self::Introductory => "introductory",
106            Self::Promotional => "promotional",
107            Self::Code => "code",
108            Self::WinBack => "winBack",
109            Self::Unknown(value) => value.as_str(),
110        }
111    }
112
113    fn from_raw(raw: String) -> Self {
114        match raw.as_str() {
115            "introductory" => Self::Introductory,
116            "promotional" => Self::Promotional,
117            "code" => Self::Code,
118            "winBack" => Self::WinBack,
119            _ => Self::Unknown(raw),
120        }
121    }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125/// Wraps the offer payment mode attached to `StoreKit.Transaction`.
126pub enum OfferPaymentMode {
127    /// Represents the `FreeTrial` `StoreKit` case.
128    FreeTrial,
129    /// Represents the `PayAsYouGo` `StoreKit` case.
130    PayAsYouGo,
131    /// Represents the `PayUpFront` `StoreKit` case.
132    PayUpFront,
133    /// Represents the `OneTime` `StoreKit` case.
134    OneTime,
135    /// Preserves an unrecognized `StoreKit` case.
136    Unknown(String),
137}
138
139impl OfferPaymentMode {
140    /// Returns the raw `StoreKit` string for this offer payment mode.
141    pub fn as_str(&self) -> &str {
142        match self {
143            Self::FreeTrial => "freeTrial",
144            Self::PayAsYouGo => "payAsYouGo",
145            Self::PayUpFront => "payUpFront",
146            Self::OneTime => "oneTime",
147            Self::Unknown(value) => value.as_str(),
148        }
149    }
150
151    fn from_raw(raw: String) -> Self {
152        match raw.as_str() {
153            "freeTrial" => Self::FreeTrial,
154            "payAsYouGo" => Self::PayAsYouGo,
155            "payUpFront" => Self::PayUpFront,
156            "oneTime" => Self::OneTime,
157            _ => Self::Unknown(raw),
158        }
159    }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
163/// Carries offer metadata attached to `StoreKit.Transaction`.
164pub struct TransactionOffer {
165    /// `StoreKit` identifier for this value.
166    pub id: Option<String>,
167    /// Offer type reported by `StoreKit`.
168    pub offer_type: OfferType,
169    /// Payment mode reported by `StoreKit`.
170    pub payment_mode: Option<OfferPaymentMode>,
171    /// Subscription period reported by `StoreKit`.
172    pub period: Option<SubscriptionPeriod>,
173}
174
175#[derive(Debug, Clone, PartialEq, Eq)]
176/// Wraps `StoreKit.Transaction.OwnershipType`.
177pub enum OwnershipType {
178    /// Represents the `Purchased` `StoreKit` case.
179    Purchased,
180    /// Represents the `FamilyShared` `StoreKit` case.
181    FamilyShared,
182    /// Preserves an unrecognized `StoreKit` case.
183    Unknown(String),
184}
185
186impl OwnershipType {
187    fn from_raw(raw: String) -> Self {
188        match raw.as_str() {
189            "purchased" => Self::Purchased,
190            "familyShared" => Self::FamilyShared,
191            _ => Self::Unknown(raw),
192        }
193    }
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
197/// Carries decoded fields from a `StoreKit.Transaction` payload.
198pub struct TransactionData {
199    /// `StoreKit` identifier for this value.
200    pub id: u64,
201    /// Original `StoreKit` transaction identifier.
202    pub original_id: u64,
203    /// Web order line item identifier reported by `StoreKit`.
204    pub web_order_line_item_id: Option<String>,
205    /// Product identifier reported by `StoreKit`.
206    pub product_id: String,
207    /// Subscription group identifier reported by `StoreKit`.
208    pub subscription_group_id: Option<String>,
209    /// Bundle identifier reported by `StoreKit`.
210    pub app_bundle_id: String,
211    /// Purchase date reported by `StoreKit`.
212    pub purchase_date: String,
213    /// Original purchase date reported by `StoreKit`.
214    pub original_purchase_date: String,
215    /// Expiration date reported by `StoreKit`.
216    pub expiration_date: Option<String>,
217    /// Purchased quantity reported by `StoreKit`.
218    pub purchased_quantity: u64,
219    /// Whether `StoreKit` reported `upgraded`.
220    pub is_upgraded: bool,
221    /// Ownership type reported by `StoreKit`.
222    pub ownership_type: OwnershipType,
223    /// Signature timestamp reported by `StoreKit`.
224    pub signed_date: String,
225    /// `StoreKit`-provided `jws_representation` value.
226    pub jws_representation: String,
227    /// Verification failure reported by `StoreKit`.
228    pub verification_failure: Option<VerificationFailure>,
229    /// Revocation date reported by `StoreKit`.
230    pub revocation_date: Option<String>,
231    /// Revocation reason reported by `StoreKit`.
232    pub revocation_reason: Option<RevocationReason>,
233    /// Product type reported by `StoreKit`.
234    pub product_type: Option<ProductType>,
235    /// App account token reported by `StoreKit`.
236    pub app_account_token: Option<String>,
237    /// Environment reported by `StoreKit`.
238    pub environment: Option<AppStoreEnvironment>,
239    /// Reason reported by `StoreKit`.
240    pub reason: Option<TransactionReason>,
241    /// Storefront metadata reported by `StoreKit`.
242    pub storefront: Option<Storefront>,
243    /// Price reported by `StoreKit`.
244    pub price: Option<String>,
245    /// Currency code reported by `StoreKit`.
246    pub currency_code: Option<String>,
247    /// App transaction identifier reported by `StoreKit`.
248    pub app_transaction_id: Option<String>,
249    /// Offer metadata reported by `StoreKit`.
250    pub offer: Option<TransactionOffer>,
251    /// Decoded JSON representation returned by `StoreKit`.
252    pub json_representation: Vec<u8>,
253}
254
255#[derive(Debug)]
256/// Wraps a live `StoreKit.Transaction` handle plus decoded payload data.
257pub struct Transaction {
258    handle: Option<NonNull<c_void>>,
259    data: TransactionData,
260    advanced_commerce_info: Option<TransactionAdvancedCommerceInfo>,
261}
262
263impl Clone for Transaction {
264    fn clone(&self) -> Self {
265        let handle = self.handle.map(|handle| {
266            // SAFETY: handle is a valid, non-null StoreKit transaction pointer maintained
267            // by this Transaction.  sk_transaction_retain increments the retain count
268            // and returns the same pointer (never null for a live transaction).
269            let retained = unsafe { ffi::sk_transaction_retain(handle.as_ptr()) };
270            NonNull::new(retained).expect("StoreKit transaction retain returned null")
271        });
272        Self {
273            handle,
274            data: self.data.clone(),
275            advanced_commerce_info: self.advanced_commerce_info.clone(),
276        }
277    }
278}
279
280impl Drop for Transaction {
281    fn drop(&mut self) {
282        if let Some(handle) = self.handle {
283            // SAFETY: handle is a valid, non-null StoreKit transaction pointer that
284            // this Transaction uniquely owns (or co-owns with a matching retain from
285            // Clone).  Drop is the unique release point per ownership token.
286            unsafe { ffi::sk_transaction_release(handle.as_ptr()) };
287        }
288    }
289}
290
291impl Transaction {
292    /// Creates a stream backed by `StoreKit.Transaction.currentEntitlements`.
293    pub fn current_entitlements() -> Result<TransactionStream, StoreKitError> {
294        TransactionStream::new(&TransactionStreamConfig::current_entitlements())
295    }
296
297    /// Creates a stream backed by `StoreKit.Transaction.all`.
298    pub fn all() -> Result<TransactionStream, StoreKitError> {
299        TransactionStream::new(&TransactionStreamConfig::all())
300    }
301
302    /// Creates a stream backed by `StoreKit.Transaction.updates`.
303    pub fn updates() -> Result<TransactionStream, StoreKitError> {
304        TransactionStream::new(&TransactionStreamConfig::updates())
305    }
306
307    /// Creates a stream backed by `StoreKit.Transaction.unfinished`.
308    pub fn unfinished() -> Result<TransactionStream, StoreKitError> {
309        TransactionStream::new(&TransactionStreamConfig::unfinished())
310    }
311
312    /// Creates a stream of all `StoreKit` transactions for the supplied product identifier.
313    pub fn all_for(product_id: &str) -> Result<TransactionStream, StoreKitError> {
314        TransactionStream::new(&TransactionStreamConfig::all_for(product_id))
315    }
316
317    /// Creates a stream of current `StoreKit` entitlements for the supplied product identifier.
318    pub fn current_entitlements_for(product_id: &str) -> Result<TransactionStream, StoreKitError> {
319        TransactionStream::new(&TransactionStreamConfig::current_entitlements_for(
320            product_id,
321        ))
322    }
323
324    /// Fetches the latest `StoreKit` transaction for the supplied product identifier.
325    pub fn latest_for(product_id: &str) -> Result<Option<VerificationResult<Self>>, StoreKitError> {
326        let product_id = cstring_from_str(product_id, "product id")?;
327        let mut transaction_handle = ptr::null_mut();
328        let mut result_json = ptr::null_mut();
329        let mut error_message = ptr::null_mut();
330        let status = unsafe {
331            ffi::sk_transaction_latest_for(
332                product_id.as_ptr(),
333                &mut transaction_handle,
334                &mut result_json,
335                &mut error_message,
336            )
337        };
338        if status != ffi::status::OK {
339            return Err(unsafe { error_from_status(status, error_message) });
340        }
341
342        let payload = unsafe {
343            parse_optional_json_ptr::<VerificationResultPayload<TransactionPayload>>(
344                result_json,
345                "latest transaction",
346            )
347        }?;
348        payload
349            .map(|payload| {
350                payload.into_result(|payload| Self::from_raw_parts(transaction_handle, payload))
351            })
352            .transpose()
353    }
354
355    /// Fetches the current `StoreKit` entitlement transaction for the supplied product identifier.
356    pub fn current_entitlement_for(
357        product_id: &str,
358    ) -> Result<Option<VerificationResult<Self>>, StoreKitError> {
359        let product_id = cstring_from_str(product_id, "product id")?;
360        let mut transaction_handle = ptr::null_mut();
361        let mut result_json = ptr::null_mut();
362        let mut error_message = ptr::null_mut();
363        let status = unsafe {
364            ffi::sk_transaction_current_entitlement_for(
365                product_id.as_ptr(),
366                &mut transaction_handle,
367                &mut result_json,
368                &mut error_message,
369            )
370        };
371        if status != ffi::status::OK {
372            return Err(unsafe { error_from_status(status, error_message) });
373        }
374
375        let payload = unsafe {
376            parse_optional_json_ptr::<VerificationResultPayload<TransactionPayload>>(
377                result_json,
378                "current entitlement transaction",
379            )
380        }?;
381        payload
382            .map(|payload| {
383                payload.into_result(|payload| Self::from_raw_parts(transaction_handle, payload))
384            })
385            .transpose()
386    }
387
388    /// Returns the decoded `StoreKit.Transaction` payload data.
389    pub const fn data(&self) -> &TransactionData {
390        &self.data
391    }
392
393    /// Returns advanced-commerce metadata attached to this `StoreKit` transaction.
394    pub const fn advanced_commerce_info(&self) -> Option<&TransactionAdvancedCommerceInfo> {
395        self.advanced_commerce_info.as_ref()
396    }
397
398    /// Returns whether this wrapper still owns a live `StoreKit` handle.
399    pub const fn has_live_handle(&self) -> bool {
400        self.handle.is_some()
401    }
402
403    /// Asks `StoreKit` to verify this transaction again.
404    pub fn verify(&self) -> Result<(), StoreKitError> {
405        self.handle.map_or_else(
406            || {
407                self.data
408                    .verification_failure
409                    .clone()
410                    .map_or(Ok(()), |failure| Err(StoreKitError::Verification(failure)))
411            },
412            |handle| {
413                let mut error_message = ptr::null_mut();
414                let status =
415                    unsafe { ffi::sk_transaction_verify(handle.as_ptr(), &mut error_message) };
416                if status == ffi::status::OK {
417                    Ok(())
418                } else {
419                    Err(unsafe { error_from_status(status, error_message) })
420                }
421            },
422        )
423    }
424
425    /// Calls `StoreKit.Transaction.finish()`.
426    pub fn finish(&self) -> Result<(), StoreKitError> {
427        self.handle.map_or_else(
428            || {
429                Err(StoreKitError::NotSupported(
430                    "transaction snapshots cannot be finished because they do not carry a live StoreKit handle"
431                        .to_owned(),
432                ))
433            },
434            |handle| {
435                let mut error_message = ptr::null_mut();
436                let status = unsafe { ffi::sk_transaction_finish(handle.as_ptr(), &mut error_message) };
437                if status == ffi::status::OK {
438                    Ok(())
439                } else {
440                    Err(unsafe { error_from_status(status, error_message) })
441                }
442            },
443        )
444    }
445
446    /// Begins a `StoreKit` refund request for this transaction.
447    pub fn begin_refund_request(&self) -> Result<RefundRequestStatus, StoreKitError> {
448        Refund::begin_for_transaction_id(self.data.id)
449    }
450
451    pub(crate) fn from_raw_parts(
452        handle: *mut c_void,
453        payload: TransactionPayload,
454    ) -> Result<Self, StoreKitError> {
455        let (data, advanced_commerce_info) = payload.into_transaction_parts()?;
456        Ok(Self {
457            handle: NonNull::new(handle),
458            data,
459            advanced_commerce_info,
460        })
461    }
462
463    pub(crate) fn from_snapshot_payload(
464        payload: TransactionPayload,
465    ) -> Result<Self, StoreKitError> {
466        let (data, advanced_commerce_info) = payload.into_transaction_parts()?;
467        Ok(Self {
468            handle: None,
469            data,
470            advanced_commerce_info,
471        })
472    }
473}
474
475#[derive(Debug)]
476/// Wraps the `StoreKit` transaction stream APIs.
477pub struct TransactionStream {
478    handle: NonNull<c_void>,
479    finished: bool,
480}
481
482impl Drop for TransactionStream {
483    fn drop(&mut self) {
484        unsafe { ffi::sk_transaction_stream_release(self.handle.as_ptr()) };
485    }
486}
487
488impl TransactionStream {
489    fn new(config: &TransactionStreamConfig) -> Result<Self, StoreKitError> {
490        let config_json = json_cstring(config, "transaction stream config")?;
491        let mut error_message = ptr::null_mut();
492        let handle =
493            unsafe { ffi::sk_transaction_stream_create(config_json.as_ptr(), &mut error_message) };
494        let handle = NonNull::new(handle)
495            .ok_or_else(|| unsafe { error_from_status(ffi::status::UNKNOWN, error_message) })?;
496        Ok(Self {
497            handle,
498            finished: false,
499        })
500    }
501
502    /// Returns whether this `StoreKit` stream has reached the end of the sequence.
503    pub const fn is_finished(&self) -> bool {
504        self.finished
505    }
506
507    #[allow(clippy::should_implement_trait)]
508    /// Waits for the next value from the `StoreKit` stream using the default timeout.
509    pub fn next(&mut self) -> Result<Option<VerificationResult<Transaction>>, StoreKitError> {
510        self.next_timeout(Duration::from_secs(30))
511    }
512
513    /// Waits for the next value from the `StoreKit` stream up to the supplied timeout.
514    pub fn next_timeout(
515        &mut self,
516        timeout: Duration,
517    ) -> Result<Option<VerificationResult<Transaction>>, StoreKitError> {
518        let mut transaction_handle = ptr::null_mut();
519        let mut verification_json = ptr::null_mut();
520        let mut error_message = ptr::null_mut();
521        let status = unsafe {
522            ffi::sk_transaction_stream_next(
523                self.handle.as_ptr(),
524                duration_to_timeout_ms(timeout),
525                &mut transaction_handle,
526                &mut verification_json,
527                &mut error_message,
528            )
529        };
530
531        match status {
532            ffi::status::OK => {
533                let payload = unsafe {
534                    parse_json_ptr::<VerificationResultPayload<TransactionPayload>>(
535                        verification_json,
536                        "transaction verification result",
537                    )
538                };
539                match payload {
540                    Ok(payload) => payload
541                        .into_result(|payload| {
542                            Transaction::from_raw_parts(transaction_handle, payload)
543                        })
544                        .map(Some),
545                    Err(error) => {
546                        if !transaction_handle.is_null() {
547                            unsafe { ffi::sk_transaction_release(transaction_handle) };
548                        }
549                        Err(error)
550                    }
551                }
552            }
553            ffi::status::END_OF_STREAM => {
554                self.finished = true;
555                Ok(None)
556            }
557            ffi::status::TIMED_OUT => Ok(None),
558            _ => Err(unsafe { error_from_status(status, error_message) }),
559        }
560    }
561}
562
563#[derive(Debug, Serialize)]
564struct TransactionStreamConfig {
565    kind: &'static str,
566    #[serde(rename = "productID", skip_serializing_if = "Option::is_none")]
567    product_id: Option<String>,
568}
569
570impl TransactionStreamConfig {
571    const fn all() -> Self {
572        Self {
573            kind: "all",
574            product_id: None,
575        }
576    }
577
578    const fn current_entitlements() -> Self {
579        Self {
580            kind: "currentEntitlements",
581            product_id: None,
582        }
583    }
584
585    const fn updates() -> Self {
586        Self {
587            kind: "updates",
588            product_id: None,
589        }
590    }
591
592    const fn unfinished() -> Self {
593        Self {
594            kind: "unfinished",
595            product_id: None,
596        }
597    }
598
599    fn all_for(product_id: &str) -> Self {
600        Self {
601            kind: "allFor",
602            product_id: Some(product_id.to_owned()),
603        }
604    }
605
606    fn current_entitlements_for(product_id: &str) -> Self {
607        Self {
608            kind: "currentEntitlementsFor",
609            product_id: Some(product_id.to_owned()),
610        }
611    }
612}
613
614#[derive(Debug, Deserialize)]
615pub(crate) struct TransactionOfferPayload {
616    id: Option<String>,
617    #[serde(rename = "type")]
618    offer_type: String,
619    #[serde(rename = "paymentMode")]
620    payment_mode: Option<String>,
621    period: Option<SubscriptionPeriodPayload>,
622}
623
624impl TransactionOfferPayload {
625    pub(crate) fn into_transaction_offer(self) -> TransactionOffer {
626        TransactionOffer {
627            id: self.id,
628            offer_type: OfferType::from_raw(self.offer_type),
629            payment_mode: self.payment_mode.map(OfferPaymentMode::from_raw),
630            period: self
631                .period
632                .map(SubscriptionPeriodPayload::into_subscription_period),
633        }
634    }
635}
636
637#[derive(Debug, Deserialize)]
638pub(crate) struct TransactionPayload {
639    id: u64,
640    #[serde(rename = "originalID")]
641    original_id: u64,
642    #[serde(rename = "webOrderLineItemID")]
643    web_order_line_item_id: Option<String>,
644    #[serde(rename = "productID")]
645    product_id: String,
646    #[serde(rename = "subscriptionGroupID")]
647    subscription_group_id: Option<String>,
648    #[serde(rename = "appBundleID")]
649    app_bundle_id: String,
650    #[serde(rename = "purchaseDate")]
651    purchase_date: String,
652    #[serde(rename = "originalPurchaseDate")]
653    original_purchase_date: String,
654    #[serde(rename = "expirationDate")]
655    expiration_date: Option<String>,
656    #[serde(rename = "purchasedQuantity")]
657    purchased_quantity: u64,
658    #[serde(rename = "isUpgraded")]
659    is_upgraded: bool,
660    #[serde(rename = "ownershipType")]
661    ownership_type: String,
662    #[serde(rename = "signedDate")]
663    signed_date: String,
664    #[serde(rename = "jwsRepresentation")]
665    jws_representation: String,
666    #[serde(rename = "verificationError")]
667    verification_error: Option<crate::error::VerificationErrorPayload>,
668    #[serde(rename = "revocationDate")]
669    revocation_date: Option<String>,
670    #[serde(rename = "revocationReason")]
671    revocation_reason: Option<String>,
672    #[serde(rename = "productType")]
673    product_type: Option<String>,
674    #[serde(rename = "appAccountToken")]
675    app_account_token: Option<String>,
676    environment: Option<String>,
677    reason: Option<String>,
678    storefront: Option<StorefrontPayload>,
679    price: Option<String>,
680    #[serde(rename = "currencyCode")]
681    currency_code: Option<String>,
682    #[serde(rename = "appTransactionID")]
683    app_transaction_id: Option<String>,
684    offer: Option<TransactionOfferPayload>,
685    #[serde(rename = "advancedCommerceInfo")]
686    advanced_commerce_info: Option<TransactionAdvancedCommerceInfoPayload>,
687    #[serde(rename = "jsonRepresentationBase64")]
688    json_representation_base64: String,
689}
690
691impl TransactionPayload {
692    fn into_transaction_parts(
693        self,
694    ) -> Result<(TransactionData, Option<TransactionAdvancedCommerceInfo>), StoreKitError> {
695        let advanced_commerce_info = self
696            .advanced_commerce_info
697            .map(TransactionAdvancedCommerceInfoPayload::into_transaction_advanced_commerce_info);
698        Ok((
699            TransactionData {
700                id: self.id,
701                original_id: self.original_id,
702                web_order_line_item_id: self.web_order_line_item_id,
703                product_id: self.product_id,
704                subscription_group_id: self.subscription_group_id,
705                app_bundle_id: self.app_bundle_id,
706                purchase_date: self.purchase_date,
707                original_purchase_date: self.original_purchase_date,
708                expiration_date: self.expiration_date,
709                purchased_quantity: self.purchased_quantity,
710                is_upgraded: self.is_upgraded,
711                ownership_type: OwnershipType::from_raw(self.ownership_type),
712                signed_date: self.signed_date,
713                jws_representation: self.jws_representation,
714                verification_failure: self
715                    .verification_error
716                    .map(crate::error::VerificationFailure::from_payload),
717                revocation_date: self.revocation_date,
718                revocation_reason: self.revocation_reason.map(RevocationReason::from_raw),
719                product_type: self.product_type.map(ProductType::from_raw),
720                app_account_token: self.app_account_token,
721                environment: self.environment.map(AppStoreEnvironment::from_raw),
722                reason: self.reason.map(TransactionReason::from_raw),
723                storefront: self.storefront.map(StorefrontPayload::into_storefront),
724                price: self.price,
725                currency_code: self.currency_code,
726                app_transaction_id: self.app_transaction_id,
727                offer: self
728                    .offer
729                    .map(TransactionOfferPayload::into_transaction_offer),
730                json_representation: decode_base64(
731                    &self.json_representation_base64,
732                    "transaction JSON representation",
733                )?,
734            },
735            advanced_commerce_info,
736        ))
737    }
738}