From 8652c9a0fec2531ade782c4fd073bdb0e3402356 Mon Sep 17 00:00:00 2001 From: Riyaz Panjwani Date: Fri, 1 May 2026 17:23:51 -0700 Subject: [PATCH] Updates to AdvancedCommerce objects and adding support for monthly subs with a 12-month commitment --- .../AbstractAdvancedCommerceBaseItem.py | 4 +- .../models/AbstractAdvancedCommerceItem.py | 6 +- .../models/AdvancedCommerceDescriptors.py | 6 +- .../models/AdvancedCommerceInfo.py | 29 ++++++ .../models/AdvancedCommerceOffer.py | 4 +- .../AdvancedCommercePriceIncreaseInfo.py | 35 +++++++ ...AdvancedCommercePriceIncreaseInfoStatus.py | 13 +++ .../models/AdvancedCommerceRefund.py | 46 +++++++++ .../models/AdvancedCommerceRenewalInfo.py | 52 ++++++++++ .../models/AdvancedCommerceRenewalItem.py | 45 +++++++++ .../AdvancedCommerceRequestRefundRequest.py | 4 +- ...ceSubscriptionChangeMetadataDescriptors.py | 6 +- ...dCommerceSubscriptionChangeMetadataItem.py | 10 +- ...mmerceSubscriptionChangeMetadataRequest.py | 4 +- ...ancedCommerceSubscriptionMigrateRequest.py | 6 +- ...cedCommerceSubscriptionModifyChangeItem.py | 4 +- ...edCommerceSubscriptionModifyDescriptors.py | 6 +- ...dCommerceSubscriptionModifyInAppRequest.py | 6 +- ...ncedCommerceSubscriptionPriceChangeItem.py | 4 +- .../models/AdvancedCommerceTransactionInfo.py | 62 ++++++++++++ .../models/AdvancedCommerceTransactionItem.py | 50 ++++++++++ .../models/AlternateProduct.py | 15 ++- .../models/BillingPlanType.py | 12 +++ ...ationUtils.py => HelperValidationUtils.py} | 26 ++--- .../models/JWSRenewalInfoDecodedPayload.py | 27 ++++- .../models/JWSTransactionDecodedPayload.py | 25 +++++ .../models/NotificationTypeV2.py | 3 + .../models/RealtimeResponseBody.py | 8 ++ .../models/RenewalBillingPlanType.py | 12 +++ .../models/RenewalCommitmentInfo.py | 51 ++++++++++ .../models/TransactionCommitmentInfo.py | 34 +++++++ tests/resources/models/signedRenewalInfo.json | 36 ++++++- tests/resources/models/signedTransaction.json | 38 ++++++- tests/test_advanced_commerce_models.py | 99 +++++++++++++++---- tests/test_decoded_payloads.py | 76 ++++++++++++++ tests/test_retention_messaging.py | 32 +++++- 36 files changed, 824 insertions(+), 72 deletions(-) create mode 100644 appstoreserverlibrary/models/AdvancedCommerceInfo.py create mode 100644 appstoreserverlibrary/models/AdvancedCommercePriceIncreaseInfo.py create mode 100644 appstoreserverlibrary/models/AdvancedCommercePriceIncreaseInfoStatus.py create mode 100644 appstoreserverlibrary/models/AdvancedCommerceRefund.py create mode 100644 appstoreserverlibrary/models/AdvancedCommerceRenewalInfo.py create mode 100644 appstoreserverlibrary/models/AdvancedCommerceRenewalItem.py create mode 100644 appstoreserverlibrary/models/AdvancedCommerceTransactionInfo.py create mode 100644 appstoreserverlibrary/models/AdvancedCommerceTransactionItem.py create mode 100644 appstoreserverlibrary/models/BillingPlanType.py rename appstoreserverlibrary/models/{AdvancedCommerceValidationUtils.py => HelperValidationUtils.py} (72%) create mode 100644 appstoreserverlibrary/models/RenewalBillingPlanType.py create mode 100644 appstoreserverlibrary/models/RenewalCommitmentInfo.py create mode 100644 appstoreserverlibrary/models/TransactionCommitmentInfo.py diff --git a/appstoreserverlibrary/models/AbstractAdvancedCommerceBaseItem.py b/appstoreserverlibrary/models/AbstractAdvancedCommerceBaseItem.py index b6b4dd50..87278533 100644 --- a/appstoreserverlibrary/models/AbstractAdvancedCommerceBaseItem.py +++ b/appstoreserverlibrary/models/AbstractAdvancedCommerceBaseItem.py @@ -5,12 +5,12 @@ from attr import define import attr -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils from .LibraryUtility import AttrsRawValueAware @define class AbstractAdvancedCommerceBaseItem(AttrsRawValueAware, ABC): - SKU: str = attr.ib(validator=AdvancedCommerceValidationUtils.sku_validator) + SKU: str = attr.ib(validator=HelperValidationUtils.sku_validator) """ The product identifier of an in-app purchase product you manage in your own system. diff --git a/appstoreserverlibrary/models/AbstractAdvancedCommerceItem.py b/appstoreserverlibrary/models/AbstractAdvancedCommerceItem.py index 9013bad1..aefb8ef3 100644 --- a/appstoreserverlibrary/models/AbstractAdvancedCommerceItem.py +++ b/appstoreserverlibrary/models/AbstractAdvancedCommerceItem.py @@ -4,18 +4,18 @@ import attr from .AbstractAdvancedCommerceBaseItem import AbstractAdvancedCommerceBaseItem -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils @define class AbstractAdvancedCommerceItem(AbstractAdvancedCommerceBaseItem): - description: str = attr.ib(validator=AdvancedCommerceValidationUtils.description_validator) + description: str = attr.ib(validator=HelperValidationUtils.description_validator) """ A string you provide that describes a SKU. https://developer.apple.com/documentation/advancedcommerceapi/description """ - displayName: str = attr.ib(validator=AdvancedCommerceValidationUtils.display_name_validator) + displayName: str = attr.ib(validator=HelperValidationUtils.display_name_validator) """ A string with a product name that you can localize and is suitable for display to customers. diff --git a/appstoreserverlibrary/models/AdvancedCommerceDescriptors.py b/appstoreserverlibrary/models/AdvancedCommerceDescriptors.py index 9ff9354f..ff5a5eb4 100644 --- a/appstoreserverlibrary/models/AdvancedCommerceDescriptors.py +++ b/appstoreserverlibrary/models/AdvancedCommerceDescriptors.py @@ -3,7 +3,7 @@ from attr import define import attr -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils @define class AdvancedCommerceDescriptors: @@ -12,14 +12,14 @@ class AdvancedCommerceDescriptors: https://developer.apple.com/documentation/advancedcommerceapi/descriptors """ - description: str = attr.ib(validator=AdvancedCommerceValidationUtils.description_validator) + description: str = attr.ib(validator=HelperValidationUtils.description_validator) """ A string you provide that describes a SKU. https://developer.apple.com/documentation/advancedcommerceapi/description """ - displayName: str = attr.ib(validator=AdvancedCommerceValidationUtils.display_name_validator) + displayName: str = attr.ib(validator=HelperValidationUtils.display_name_validator) """ A string with a product name that you can localize and is suitable for display to customers. diff --git a/appstoreserverlibrary/models/AdvancedCommerceInfo.py b/appstoreserverlibrary/models/AdvancedCommerceInfo.py new file mode 100644 index 00000000..e09d945f --- /dev/null +++ b/appstoreserverlibrary/models/AdvancedCommerceInfo.py @@ -0,0 +1,29 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from typing import Optional +from uuid import UUID + +from attr import define +import attr + +@define +class AdvancedCommerceInfo: + """ + A response object you provide to present an offer or switch-plan recommendation message. + + https://developer.apple.com/documentation/retentionmessaging/advancedcommerceinfo + """ + + messageIdentifier: Optional[UUID] = attr.ib(default=None) + """ + The identifier of the message to display to the customer, along with the offer or switch-plan recommendation provided in advancedCommerceData. + + https://developer.apple.com/documentation/retentionmessaging/messageidentifier + """ + + advancedCommerceData: Optional[str] = attr.ib(default=None) + """ + A Base64-encoded JSON object which contains a JWS describing an offer or switch-plan recommendation. + + https://developer.apple.com/documentation/retentionmessaging/advancedcommercedata + """ diff --git a/appstoreserverlibrary/models/AdvancedCommerceOffer.py b/appstoreserverlibrary/models/AdvancedCommerceOffer.py index 50f7d092..0b18fba3 100644 --- a/appstoreserverlibrary/models/AdvancedCommerceOffer.py +++ b/appstoreserverlibrary/models/AdvancedCommerceOffer.py @@ -6,7 +6,7 @@ from .AdvancedCommerceOfferPeriod import AdvancedCommerceOfferPeriod from .AdvancedCommerceOfferReason import AdvancedCommerceOfferReason -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils from .LibraryUtility import AttrsRawValueAware @define @@ -17,7 +17,7 @@ class AdvancedCommerceOffer(AttrsRawValueAware): https://developer.apple.com/documentation/advancedcommerceapi/offer """ - periodCount: int = attr.ib(validator=AdvancedCommerceValidationUtils.period_count_validator) + periodCount: int = attr.ib(validator=HelperValidationUtils.period_count_validator) """ The number of periods the offer is active. """ diff --git a/appstoreserverlibrary/models/AdvancedCommercePriceIncreaseInfo.py b/appstoreserverlibrary/models/AdvancedCommercePriceIncreaseInfo.py new file mode 100644 index 00000000..e301786e --- /dev/null +++ b/appstoreserverlibrary/models/AdvancedCommercePriceIncreaseInfo.py @@ -0,0 +1,35 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from typing import List, Optional + +from attr import define +import attr + +from .AdvancedCommercePriceIncreaseInfoStatus import AdvancedCommercePriceIncreaseInfoStatus +from .LibraryUtility import AttrsRawValueAware + +@define +class AdvancedCommercePriceIncreaseInfo(AttrsRawValueAware): + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercepriceincreaseinfo + """ + + dependentSKUs: Optional[List[str]] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercepriceincreaseinfodependentsku + """ + + price: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercepriceincreaseinfoprice + """ + + status: Optional[AdvancedCommercePriceIncreaseInfoStatus] = AdvancedCommercePriceIncreaseInfoStatus.create_main_attr('rawStatus') + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercepriceincreaseinfostatus + """ + + rawStatus: Optional[str] = AdvancedCommercePriceIncreaseInfoStatus.create_raw_attr('status') + """ + See status + """ diff --git a/appstoreserverlibrary/models/AdvancedCommercePriceIncreaseInfoStatus.py b/appstoreserverlibrary/models/AdvancedCommercePriceIncreaseInfoStatus.py new file mode 100644 index 00000000..dc55e831 --- /dev/null +++ b/appstoreserverlibrary/models/AdvancedCommercePriceIncreaseInfoStatus.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from enum import Enum + +from .LibraryUtility import AppStoreServerLibraryEnumMeta + +class AdvancedCommercePriceIncreaseInfoStatus(str, Enum, metaclass=AppStoreServerLibraryEnumMeta): + """ + https://developer.apple.com/documentation/appstoreservernotifications/advancedcommercepriceincreaseinfostatus + """ + SCHEDULED = "SCHEDULED" + PENDING = "PENDING" + ACCEPTED = "ACCEPTED" diff --git a/appstoreserverlibrary/models/AdvancedCommerceRefund.py b/appstoreserverlibrary/models/AdvancedCommerceRefund.py new file mode 100644 index 00000000..c30e7e8a --- /dev/null +++ b/appstoreserverlibrary/models/AdvancedCommerceRefund.py @@ -0,0 +1,46 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from typing import Optional + +from attr import define +import attr + +from .AdvancedCommerceRefundReason import AdvancedCommerceRefundReason +from .AdvancedCommerceRefundType import AdvancedCommerceRefundType +from .LibraryUtility import AttrsRawValueAware + +@define +class AdvancedCommerceRefund(AttrsRawValueAware): + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerefund + """ + + refundAmount: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerefundamount + """ + + refundDate: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerefunddate + """ + + refundReason: Optional[AdvancedCommerceRefundReason] = AdvancedCommerceRefundReason.create_main_attr('rawRefundReason') + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerefundreason + """ + + rawRefundReason: Optional[str] = AdvancedCommerceRefundReason.create_raw_attr('refundReason') + """ + See refundReason + """ + + refundType: Optional[AdvancedCommerceRefundType] = AdvancedCommerceRefundType.create_main_attr('rawRefundType') + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerefundtype + """ + + rawRefundType: Optional[str] = AdvancedCommerceRefundType.create_raw_attr('refundType') + """ + See refundType + """ diff --git a/appstoreserverlibrary/models/AdvancedCommerceRenewalInfo.py b/appstoreserverlibrary/models/AdvancedCommerceRenewalInfo.py new file mode 100644 index 00000000..591d96b9 --- /dev/null +++ b/appstoreserverlibrary/models/AdvancedCommerceRenewalInfo.py @@ -0,0 +1,52 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from typing import List, Optional + +from attr import define +import attr + +from .AdvancedCommerceDescriptors import AdvancedCommerceDescriptors +from .AdvancedCommercePeriod import AdvancedCommercePeriod +from .AdvancedCommerceRenewalItem import AdvancedCommerceRenewalItem +from .LibraryUtility import AttrsRawValueAware + +@define +class AdvancedCommerceRenewalInfo(AttrsRawValueAware): + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerenewalinfo + """ + + consistencyToken: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommerceconsistencytoken + """ + + descriptors: Optional[AdvancedCommerceDescriptors] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercedescriptors + """ + + items: Optional[List[AdvancedCommerceRenewalItem]] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerenewalitem + """ + + period: Optional[AdvancedCommercePeriod] = AdvancedCommercePeriod.create_main_attr('rawPeriod') + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommerceperiod + """ + + rawPeriod: Optional[str] = AdvancedCommercePeriod.create_raw_attr('period') + """ + See period + """ + + requestReferenceId: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerequestreferenceid + """ + + taxCode: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetaxcode + """ diff --git a/appstoreserverlibrary/models/AdvancedCommerceRenewalItem.py b/appstoreserverlibrary/models/AdvancedCommerceRenewalItem.py new file mode 100644 index 00000000..be435f8d --- /dev/null +++ b/appstoreserverlibrary/models/AdvancedCommerceRenewalItem.py @@ -0,0 +1,45 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from typing import Optional + +from attr import define +import attr + +from .AdvancedCommerceOffer import AdvancedCommerceOffer +from .AdvancedCommercePriceIncreaseInfo import AdvancedCommercePriceIncreaseInfo + +@define +class AdvancedCommerceRenewalItem: + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerenewalitem + """ + + SKU: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercesku + """ + + description: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercedescription + """ + + displayName: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercedisplayname + """ + + offer: Optional[AdvancedCommerceOffer] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommerceoffer + """ + + price: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommerceprice + """ + + priceIncreaseInfo: Optional[AdvancedCommercePriceIncreaseInfo] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercepriceincreaseinfo + """ diff --git a/appstoreserverlibrary/models/AdvancedCommerceRequestRefundRequest.py b/appstoreserverlibrary/models/AdvancedCommerceRequestRefundRequest.py index 6236d38b..d2fb55cc 100644 --- a/appstoreserverlibrary/models/AdvancedCommerceRequestRefundRequest.py +++ b/appstoreserverlibrary/models/AdvancedCommerceRequestRefundRequest.py @@ -7,7 +7,7 @@ from .AdvancedCommerceRequest import AdvancedCommerceRequest from .AdvancedCommerceRequestRefundItem import AdvancedCommerceRequestRefundItem -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils @define class AdvancedCommerceRequestRefundRequest(AdvancedCommerceRequest): @@ -17,7 +17,7 @@ class AdvancedCommerceRequestRefundRequest(AdvancedCommerceRequest): https://developer.apple.com/documentation/advancedcommerceapi/requestrefundrequest """ - items: List[AdvancedCommerceRequestRefundItem] = attr.ib(validator=AdvancedCommerceValidationUtils.items_validator) + items: List[AdvancedCommerceRequestRefundItem] = attr.ib(validator=HelperValidationUtils.items_validator) """ https://developer.apple.com/documentation/advancedcommerceapi/requestrefunditem """ diff --git a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionChangeMetadataDescriptors.py b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionChangeMetadataDescriptors.py index 1f960d4d..99e0c782 100644 --- a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionChangeMetadataDescriptors.py +++ b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionChangeMetadataDescriptors.py @@ -5,7 +5,7 @@ import attr from .AdvancedCommerceEffective import AdvancedCommerceEffective -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils @define class AdvancedCommerceSubscriptionChangeMetadataDescriptors(): @@ -29,7 +29,7 @@ class AdvancedCommerceSubscriptionChangeMetadataDescriptors(): description: Optional[str] = attr.ib( default=None, - validator=attr.validators.optional(AdvancedCommerceValidationUtils.description_validator) + validator=attr.validators.optional(HelperValidationUtils.description_validator) ) """ The new description for the subscription. @@ -39,7 +39,7 @@ class AdvancedCommerceSubscriptionChangeMetadataDescriptors(): displayName: Optional[str] = attr.ib( default=None, - validator=attr.validators.optional(AdvancedCommerceValidationUtils.display_name_validator) + validator=attr.validators.optional(HelperValidationUtils.display_name_validator) ) """ The new display name for the subscription. diff --git a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionChangeMetadataItem.py b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionChangeMetadataItem.py index 2c5a829b..5d7e83d6 100644 --- a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionChangeMetadataItem.py +++ b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionChangeMetadataItem.py @@ -5,7 +5,7 @@ import attr from .AdvancedCommerceEffective import AdvancedCommerceEffective -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils @define class AdvancedCommerceSubscriptionChangeMetadataItem(): @@ -15,7 +15,7 @@ class AdvancedCommerceSubscriptionChangeMetadataItem(): https://developer.apple.com/documentation/advancedcommerceapi/subscriptionchangemetadataitem """ - currentSKU: str = attr.ib(validator=AdvancedCommerceValidationUtils.sku_validator) + currentSKU: str = attr.ib(validator=HelperValidationUtils.sku_validator) """ The original SKU of the item. """ @@ -32,21 +32,21 @@ class AdvancedCommerceSubscriptionChangeMetadataItem(): See effective """ - description: Optional[str] = attr.ib(default=None, validator=attr.validators.optional(AdvancedCommerceValidationUtils.description_validator)) + description: Optional[str] = attr.ib(default=None, validator=attr.validators.optional(HelperValidationUtils.description_validator)) """ The new description for the item. https://developer.apple.com/documentation/advancedcommerceapi/description """ - displayName: Optional[str] = attr.ib(default=None, validator=attr.validators.optional(AdvancedCommerceValidationUtils.display_name_validator)) + displayName: Optional[str] = attr.ib(default=None, validator=attr.validators.optional(HelperValidationUtils.display_name_validator)) """ The new display name for the item. https://developer.apple.com/documentation/advancedcommerceapi/displayname """ - SKU: Optional[str] = attr.ib(default=None, validator=attr.validators.optional(AdvancedCommerceValidationUtils.sku_validator)) + SKU: Optional[str] = attr.ib(default=None, validator=attr.validators.optional(HelperValidationUtils.sku_validator)) """ The new SKU of the item. diff --git a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionChangeMetadataRequest.py b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionChangeMetadataRequest.py index 19d8c1dc..69fb57fc 100644 --- a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionChangeMetadataRequest.py +++ b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionChangeMetadataRequest.py @@ -8,7 +8,7 @@ from .AdvancedCommerceRequest import AdvancedCommerceRequest from .AdvancedCommerceSubscriptionChangeMetadataDescriptors import AdvancedCommerceSubscriptionChangeMetadataDescriptors from .AdvancedCommerceSubscriptionChangeMetadataItem import AdvancedCommerceSubscriptionChangeMetadataItem -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils @define class AdvancedCommerceSubscriptionChangeMetadataRequest(AdvancedCommerceRequest): @@ -23,7 +23,7 @@ class AdvancedCommerceSubscriptionChangeMetadataRequest(AdvancedCommerceRequest) https://developer.apple.com/documentation/advancedcommerceapi/subscriptionchangemetadatadescriptors """ - items: Optional[List[AdvancedCommerceSubscriptionChangeMetadataItem]] = attr.ib(default=None, validator=attr.validators.optional(AdvancedCommerceValidationUtils.items_validator)) + items: Optional[List[AdvancedCommerceSubscriptionChangeMetadataItem]] = attr.ib(default=None, validator=attr.validators.optional(HelperValidationUtils.items_validator)) """ https://developer.apple.com/documentation/advancedcommerceapi/subscriptionchangemetadataitem """ diff --git a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionMigrateRequest.py b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionMigrateRequest.py index aafb3622..da54738c 100644 --- a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionMigrateRequest.py +++ b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionMigrateRequest.py @@ -4,7 +4,7 @@ import attr from attr import define from .AdvancedCommerceRequest import AdvancedCommerceRequest -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils from .AdvancedCommerceSubscriptionMigrateDescriptors import AdvancedCommerceSubscriptionMigrateDescriptors from .AdvancedCommerceSubscriptionMigrateItem import AdvancedCommerceSubscriptionMigrateItem from .AdvancedCommerceSubscriptionMigrateRenewalItem import AdvancedCommerceSubscriptionMigrateRenewalItem @@ -22,7 +22,7 @@ class AdvancedCommerceSubscriptionMigrateRequest(AdvancedCommerceRequest): https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmigratedescriptors """ - items: List[AdvancedCommerceSubscriptionMigrateItem] = attr.ib(validator=AdvancedCommerceValidationUtils.items_validator) + items: List[AdvancedCommerceSubscriptionMigrateItem] = attr.ib(validator=HelperValidationUtils.items_validator) """ An array of one or more SKUs, along with descriptions and display names, that are included in the subscription. @@ -41,7 +41,7 @@ class AdvancedCommerceSubscriptionMigrateRequest(AdvancedCommerceRequest): https://developer.apple.com/documentation/advancedcommerceapi/taxcode """ - renewalItems: Optional[List[AdvancedCommerceSubscriptionMigrateRenewalItem]] = attr.ib(default=None, validator=attr.validators.optional(AdvancedCommerceValidationUtils.items_validator)) + renewalItems: Optional[List[AdvancedCommerceSubscriptionMigrateRenewalItem]] = attr.ib(default=None, validator=attr.validators.optional(HelperValidationUtils.items_validator)) """ An optional array of subscription items that represents the items that renew at the next renewal period, if they differ from items. diff --git a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionModifyChangeItem.py b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionModifyChangeItem.py index dbc5228e..532ce30b 100644 --- a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionModifyChangeItem.py +++ b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionModifyChangeItem.py @@ -7,7 +7,7 @@ from .AdvancedCommerceEffective import AdvancedCommerceEffective from .AdvancedCommerceOffer import AdvancedCommerceOffer from .AdvancedCommerceReason import AdvancedCommerceReason -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils @define @@ -18,7 +18,7 @@ class AdvancedCommerceSubscriptionModifyChangeItem(AbstractAdvancedCommerceItem) https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmodifychangeitem """ - currentSKU: str = attr.ib(validator=AdvancedCommerceValidationUtils.sku_validator) + currentSKU: str = attr.ib(validator=HelperValidationUtils.sku_validator) """ https://developer.apple.com/documentation/advancedcommerceapi/sku """ diff --git a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionModifyDescriptors.py b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionModifyDescriptors.py index d44d4990..b46edde0 100644 --- a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionModifyDescriptors.py +++ b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionModifyDescriptors.py @@ -4,7 +4,7 @@ import attr from attr import define from .AdvancedCommerceEffective import AdvancedCommerceEffective -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils @define class AdvancedCommerceSubscriptionModifyDescriptors(): @@ -24,12 +24,12 @@ class AdvancedCommerceSubscriptionModifyDescriptors(): See effective """ - description: Optional[str] = attr.ib(default=None, validator=attr.validators.optional(AdvancedCommerceValidationUtils.description_validator)) + description: Optional[str] = attr.ib(default=None, validator=attr.validators.optional(HelperValidationUtils.description_validator)) """ https://developer.apple.com/documentation/advancedcommerceapi/description """ - displayName: Optional[str] = attr.ib(default=None, validator=attr.validators.optional(AdvancedCommerceValidationUtils.display_name_validator) + displayName: Optional[str] = attr.ib(default=None, validator=attr.validators.optional(HelperValidationUtils.display_name_validator) ) """ https://developer.apple.com/documentation/advancedcommerceapi/displayname diff --git a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionModifyInAppRequest.py b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionModifyInAppRequest.py index bec1d218..e1ac2611 100644 --- a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionModifyInAppRequest.py +++ b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionModifyInAppRequest.py @@ -4,7 +4,7 @@ import attr from attr import define from .AbstractAdvancedCommerceInAppRequest import AbstractAdvancedCommerceInAppRequest -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils from .AdvancedCommerceSubscriptionModifyAddItem import AdvancedCommerceSubscriptionModifyAddItem from .AdvancedCommerceSubscriptionModifyChangeItem import AdvancedCommerceSubscriptionModifyChangeItem from .AdvancedCommerceSubscriptionModifyDescriptors import AdvancedCommerceSubscriptionModifyDescriptors @@ -34,12 +34,12 @@ class AdvancedCommerceSubscriptionModifyInAppRequest(AbstractAdvancedCommerceInA https://developer.apple.com/documentation/advancedcommerceapi/retainbillingcycle """ - addItems: Optional[List[AdvancedCommerceSubscriptionModifyAddItem]] = attr.ib(default=None, validator=attr.validators.optional(AdvancedCommerceValidationUtils.items_validator)) + addItems: Optional[List[AdvancedCommerceSubscriptionModifyAddItem]] = attr.ib(default=None, validator=attr.validators.optional(HelperValidationUtils.items_validator)) """ https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmodifyadditem """ - changeItems: Optional[List[AdvancedCommerceSubscriptionModifyChangeItem]] = attr.ib(default=None, validator=attr.validators.optional(AdvancedCommerceValidationUtils.items_validator)) + changeItems: Optional[List[AdvancedCommerceSubscriptionModifyChangeItem]] = attr.ib(default=None, validator=attr.validators.optional(HelperValidationUtils.items_validator)) """ https://developer.apple.com/documentation/advancedcommerceapi/subscriptionmodifychangeitem """ diff --git a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionPriceChangeItem.py b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionPriceChangeItem.py index cc2212e2..77889374 100644 --- a/appstoreserverlibrary/models/AdvancedCommerceSubscriptionPriceChangeItem.py +++ b/appstoreserverlibrary/models/AdvancedCommerceSubscriptionPriceChangeItem.py @@ -4,7 +4,7 @@ from attr import define from typing import List, Optional from .AbstractAdvancedCommerceBaseItem import AbstractAdvancedCommerceBaseItem -from .AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from .HelperValidationUtils import HelperValidationUtils @define class AdvancedCommerceSubscriptionPriceChangeItem(AbstractAdvancedCommerceBaseItem): @@ -19,7 +19,7 @@ class AdvancedCommerceSubscriptionPriceChangeItem(AbstractAdvancedCommerceBaseIt https://developer.apple.com/documentation/advancedcommerceapi/price """ - dependentSKUs: Optional[List[str]] = attr.ib(default=None, validator=attr.validators.optional(AdvancedCommerceValidationUtils.dependent_skus_validator)) + dependentSKUs: Optional[List[str]] = attr.ib(default=None, validator=attr.validators.optional(HelperValidationUtils.dependent_skus_validator)) """ https://developer.apple.com/documentation/advancedcommerceapi/dependentsku """ \ No newline at end of file diff --git a/appstoreserverlibrary/models/AdvancedCommerceTransactionInfo.py b/appstoreserverlibrary/models/AdvancedCommerceTransactionInfo.py new file mode 100644 index 00000000..5f628d4b --- /dev/null +++ b/appstoreserverlibrary/models/AdvancedCommerceTransactionInfo.py @@ -0,0 +1,62 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from typing import List, Optional + +from attr import define +import attr + +from .AdvancedCommerceDescriptors import AdvancedCommerceDescriptors +from .AdvancedCommercePeriod import AdvancedCommercePeriod +from .AdvancedCommerceTransactionItem import AdvancedCommerceTransactionItem +from .LibraryUtility import AttrsRawValueAware + +@define +class AdvancedCommerceTransactionInfo(AttrsRawValueAware): + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetransactioninfo + """ + + descriptors: Optional[AdvancedCommerceDescriptors] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercedescriptors + """ + + estimatedTax: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommerceestimatedtax + """ + + items: Optional[List[AdvancedCommerceTransactionItem]] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetransactionitem + """ + + period: Optional[AdvancedCommercePeriod] = AdvancedCommercePeriod.create_main_attr('rawPeriod') + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommerceperiod + """ + + rawPeriod: Optional[str] = AdvancedCommercePeriod.create_raw_attr('period') + """ + See period + """ + + requestReferenceId: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerequestreferenceid + """ + + taxCode: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetaxcode + """ + + taxExclusivePrice: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetaxexclusiveprice + """ + + taxRate: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetaxrate + """ diff --git a/appstoreserverlibrary/models/AdvancedCommerceTransactionItem.py b/appstoreserverlibrary/models/AdvancedCommerceTransactionItem.py new file mode 100644 index 00000000..97a64b23 --- /dev/null +++ b/appstoreserverlibrary/models/AdvancedCommerceTransactionItem.py @@ -0,0 +1,50 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from typing import List, Optional + +from attr import define +import attr + +from .AdvancedCommerceOffer import AdvancedCommerceOffer +from .AdvancedCommerceRefund import AdvancedCommerceRefund + +@define +class AdvancedCommerceTransactionItem: + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetransactionitem + """ + + SKU: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercesku + """ + + description: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercedescription + """ + + displayName: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercedisplayname + """ + + offer: Optional[AdvancedCommerceOffer] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommerceoffer + """ + + price: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommerceprice + """ + + refunds: Optional[List[AdvancedCommerceRefund]] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerefunds + """ + + revocationDate: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/revocationdate + """ diff --git a/appstoreserverlibrary/models/AlternateProduct.py b/appstoreserverlibrary/models/AlternateProduct.py index b8b4dd6f..d2c67c68 100644 --- a/appstoreserverlibrary/models/AlternateProduct.py +++ b/appstoreserverlibrary/models/AlternateProduct.py @@ -5,8 +5,11 @@ from attr import define import attr +from .BillingPlanType import BillingPlanType +from .LibraryUtility import AttrsRawValueAware + @define -class AlternateProduct: +class AlternateProduct(AttrsRawValueAware): """ A switch-plan message and product ID you provide in a real-time response to your Get Retention Message endpoint. @@ -26,3 +29,13 @@ class AlternateProduct: https://developer.apple.com/documentation/retentionmessaging/productid """ + + billingPlanType: Optional[BillingPlanType] = BillingPlanType.create_main_attr('rawBillingPlanType') + """ + https://developer.apple.com/documentation/retentionmessaging/billingplantype + """ + + rawBillingPlanType: Optional[str] = BillingPlanType.create_raw_attr('billingPlanType') + """ + See billingPlanType + """ diff --git a/appstoreserverlibrary/models/BillingPlanType.py b/appstoreserverlibrary/models/BillingPlanType.py new file mode 100644 index 00000000..82ab1052 --- /dev/null +++ b/appstoreserverlibrary/models/BillingPlanType.py @@ -0,0 +1,12 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from enum import Enum + +from .LibraryUtility import AppStoreServerLibraryEnumMeta + +class BillingPlanType(str, Enum, metaclass=AppStoreServerLibraryEnumMeta): + """ + https://developer.apple.com/documentation/appstoreserverapi/billingplantype + """ + BILLED_UPFRONT = "BILLED_UPFRONT" + MONTHLY = "MONTHLY" diff --git a/appstoreserverlibrary/models/AdvancedCommerceValidationUtils.py b/appstoreserverlibrary/models/HelperValidationUtils.py similarity index 72% rename from appstoreserverlibrary/models/AdvancedCommerceValidationUtils.py rename to appstoreserverlibrary/models/HelperValidationUtils.py index 5b1ea638..3469fbad 100644 --- a/appstoreserverlibrary/models/AdvancedCommerceValidationUtils.py +++ b/appstoreserverlibrary/models/HelperValidationUtils.py @@ -5,7 +5,7 @@ T = TypeVar('T') -class AdvancedCommerceValidationUtils: +class HelperValidationUtils: MAXIMUM_DESCRIPTION_LENGTH = 45 MAXIMUM_DISPLAY_NAME_LENGTH = 30 MAXIMUM_SKU_LENGTH = 128 @@ -20,10 +20,10 @@ def description_validator(instance, attribute, value): Raises: ValueError: If description exceeds maximum length """ - if len(value) > AdvancedCommerceValidationUtils.MAXIMUM_DESCRIPTION_LENGTH: + if len(value) > HelperValidationUtils.MAXIMUM_DESCRIPTION_LENGTH: raise ValueError( f"Description length cannot exceed " - f"{AdvancedCommerceValidationUtils.MAXIMUM_DESCRIPTION_LENGTH} characters" + f"{HelperValidationUtils.MAXIMUM_DESCRIPTION_LENGTH} characters" ) @staticmethod @@ -34,10 +34,10 @@ def display_name_validator(instance, attribute, value): Raises: ValueError: If display name exceeds maximum length """ - if len(value) > AdvancedCommerceValidationUtils.MAXIMUM_DISPLAY_NAME_LENGTH: + if len(value) > HelperValidationUtils.MAXIMUM_DISPLAY_NAME_LENGTH: raise ValueError( f"Display name length cannot exceed " - f"{AdvancedCommerceValidationUtils.MAXIMUM_DISPLAY_NAME_LENGTH} characters" + f"{HelperValidationUtils.MAXIMUM_DISPLAY_NAME_LENGTH} characters" ) @staticmethod @@ -48,10 +48,10 @@ def sku_validator(instance, attribute, value): Raises: ValueError: If SKU exceeds maximum length """ - if len(value) > AdvancedCommerceValidationUtils.MAXIMUM_SKU_LENGTH: + if len(value) > HelperValidationUtils.MAXIMUM_SKU_LENGTH: raise ValueError( f"SKU length cannot exceed " - f"{AdvancedCommerceValidationUtils.MAXIMUM_SKU_LENGTH} characters" + f"{HelperValidationUtils.MAXIMUM_SKU_LENGTH} characters" ) @staticmethod @@ -62,12 +62,12 @@ def period_count_validator(instance, attribute, value): Raises: ValueError: If period_count is out of range """ - if (value < AdvancedCommerceValidationUtils.MIN_PERIOD or - value > AdvancedCommerceValidationUtils.MAX_PERIOD): + if (value < HelperValidationUtils.MIN_PERIOD or + value > HelperValidationUtils.MAX_PERIOD): raise ValueError( f"Period count must be between " - f"{AdvancedCommerceValidationUtils.MIN_PERIOD} and " - f"{AdvancedCommerceValidationUtils.MAX_PERIOD}" + f"{HelperValidationUtils.MIN_PERIOD} and " + f"{HelperValidationUtils.MAX_PERIOD}" ) @staticmethod @@ -94,8 +94,8 @@ def dependent_skus_validator(instance, attribute, value): ValueError: If any SKU exceeds maximum length """ for sku in value: - if len(sku) > AdvancedCommerceValidationUtils.MAXIMUM_SKU_LENGTH: + if len(sku) > HelperValidationUtils.MAXIMUM_SKU_LENGTH: raise ValueError( f"SKU length cannot exceed " - f"{AdvancedCommerceValidationUtils.MAXIMUM_SKU_LENGTH} characters" + f"{HelperValidationUtils.MAXIMUM_SKU_LENGTH} characters" ) diff --git a/appstoreserverlibrary/models/JWSRenewalInfoDecodedPayload.py b/appstoreserverlibrary/models/JWSRenewalInfoDecodedPayload.py index 5d537022..a9215bab 100644 --- a/appstoreserverlibrary/models/JWSRenewalInfoDecodedPayload.py +++ b/appstoreserverlibrary/models/JWSRenewalInfoDecodedPayload.py @@ -11,6 +11,9 @@ from .OfferType import OfferType from .PriceIncreaseStatus import PriceIncreaseStatus from .OfferDiscountType import OfferDiscountType +from .RenewalBillingPlanType import RenewalBillingPlanType +from .AdvancedCommerceRenewalInfo import AdvancedCommerceRenewalInfo +from .RenewalCommitmentInfo import RenewalCommitmentInfo @define class JWSRenewalInfoDecodedPayload(AttrsRawValueAware): @@ -193,6 +196,28 @@ class JWSRenewalInfoDecodedPayload(AttrsRawValueAware): offerPeriod: Optional[str] = attr.ib(default=None) """ The duration of the offer. - + https://developer.apple.com/documentation/appstoreserverapi/offerPeriod + """ + + advancedCommerceInfo: Optional[AdvancedCommerceRenewalInfo] = attr.ib(default=None) + """ + Renewal information that is present only for Advanced Commerce SKUs. + + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercerenewalinfo + """ + + commitmentInfo: Optional[RenewalCommitmentInfo] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/renewalcommitmentinfo + """ + + renewalBillingPlanType: Optional[RenewalBillingPlanType] = RenewalBillingPlanType.create_main_attr('rawRenewalBillingPlanType') + """ + https://developer.apple.com/documentation/appstoreserverapi/renewalbillingplantype + """ + + rawRenewalBillingPlanType: Optional[str] = RenewalBillingPlanType.create_raw_attr('renewalBillingPlanType') + """ + See renewalBillingPlanType """ \ No newline at end of file diff --git a/appstoreserverlibrary/models/JWSTransactionDecodedPayload.py b/appstoreserverlibrary/models/JWSTransactionDecodedPayload.py index a9cb3dbf..6ccc3e58 100644 --- a/appstoreserverlibrary/models/JWSTransactionDecodedPayload.py +++ b/appstoreserverlibrary/models/JWSTransactionDecodedPayload.py @@ -12,6 +12,9 @@ from .RevocationReason import RevocationReason from .RevocationType import RevocationType from .TransactionReason import TransactionReason +from .BillingPlanType import BillingPlanType +from .AdvancedCommerceTransactionInfo import AdvancedCommerceTransactionInfo +from .TransactionCommitmentInfo import TransactionCommitmentInfo from .Type import Type @@ -271,3 +274,25 @@ class JWSTransactionDecodedPayload(AttrsRawValueAware): https://developer.apple.com/documentation/appstoreservernotifications/revocationpercentage """ + + advancedCommerceInfo: Optional[AdvancedCommerceTransactionInfo] = attr.ib(default=None) + """ + Transaction information that is present only for Advanced Commerce SKUs. + + https://developer.apple.com/documentation/appstoreserverapi/advancedcommercetransactioninfo + """ + + billingPlanType: Optional[BillingPlanType] = BillingPlanType.create_main_attr('rawBillingPlanType') + """ + https://developer.apple.com/documentation/appstoreserverapi/billingplantype + """ + + rawBillingPlanType: Optional[str] = BillingPlanType.create_raw_attr('billingPlanType') + """ + See billingPlanType + """ + + commitmentInfo: Optional[TransactionCommitmentInfo] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/transactioncommitmentinfo + """ diff --git a/appstoreserverlibrary/models/NotificationTypeV2.py b/appstoreserverlibrary/models/NotificationTypeV2.py index 78266768..e4a80b5e 100644 --- a/appstoreserverlibrary/models/NotificationTypeV2.py +++ b/appstoreserverlibrary/models/NotificationTypeV2.py @@ -30,3 +30,6 @@ class NotificationTypeV2(str, Enum, metaclass=AppStoreServerLibraryEnumMeta): EXTERNAL_PURCHASE_TOKEN = "EXTERNAL_PURCHASE_TOKEN" ONE_TIME_CHARGE = "ONE_TIME_CHARGE" RESCIND_CONSENT = "RESCIND_CONSENT" + METADATA_UPDATE = "METADATA_UPDATE" + MIGRATION = "MIGRATION" + PRICE_CHANGE = "PRICE_CHANGE" diff --git a/appstoreserverlibrary/models/RealtimeResponseBody.py b/appstoreserverlibrary/models/RealtimeResponseBody.py index 0ebc785d..15420e20 100644 --- a/appstoreserverlibrary/models/RealtimeResponseBody.py +++ b/appstoreserverlibrary/models/RealtimeResponseBody.py @@ -8,6 +8,7 @@ from .Message import Message from .AlternateProduct import AlternateProduct from .PromotionalOffer import PromotionalOffer +from .AdvancedCommerceInfo import AdvancedCommerceInfo @define class RealtimeResponseBody: @@ -37,3 +38,10 @@ class RealtimeResponseBody: https://developer.apple.com/documentation/retentionmessaging/promotionaloffer """ + + advancedCommerceInfo: Optional[AdvancedCommerceInfo] = attr.ib(default=None) + """ + A retention offer or switch plan option. + + https://developer.apple.com/documentation/retentionmessaging/advancedcommerceinfo + """ diff --git a/appstoreserverlibrary/models/RenewalBillingPlanType.py b/appstoreserverlibrary/models/RenewalBillingPlanType.py new file mode 100644 index 00000000..8c955ae5 --- /dev/null +++ b/appstoreserverlibrary/models/RenewalBillingPlanType.py @@ -0,0 +1,12 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from enum import Enum + +from .LibraryUtility import AppStoreServerLibraryEnumMeta + +class RenewalBillingPlanType(str, Enum, metaclass=AppStoreServerLibraryEnumMeta): + """ + https://developer.apple.com/documentation/appstoreserverapi/renewalbillingplantype + """ + BILLED_UPFRONT = "BILLED_UPFRONT" + MONTHLY = "MONTHLY" diff --git a/appstoreserverlibrary/models/RenewalCommitmentInfo.py b/appstoreserverlibrary/models/RenewalCommitmentInfo.py new file mode 100644 index 00000000..cc830de1 --- /dev/null +++ b/appstoreserverlibrary/models/RenewalCommitmentInfo.py @@ -0,0 +1,51 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from typing import Optional + +from attr import define +import attr + +from .AutoRenewStatus import AutoRenewStatus +from .RenewalBillingPlanType import RenewalBillingPlanType +from .LibraryUtility import AttrsRawValueAware + +@define +class RenewalCommitmentInfo(AttrsRawValueAware): + """ + https://developer.apple.com/documentation/appstoreserverapi/renewalcommitmentinfo + """ + + commitmentAutoRenewProductId: Optional[str] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/commitmentautorenewproductid + """ + + commitmentAutoRenewStatus: Optional[AutoRenewStatus] = AutoRenewStatus.create_main_attr('rawCommitmentAutoRenewStatus') + """ + https://developer.apple.com/documentation/appstoreserverapi/commitmentautorenewstatus + """ + + rawCommitmentAutoRenewStatus: Optional[int] = AutoRenewStatus.create_raw_attr('commitmentAutoRenewStatus') + """ + See commitmentAutoRenewStatus + """ + + commitmentRenewalBillingPlanType: Optional[RenewalBillingPlanType] = RenewalBillingPlanType.create_main_attr('rawCommitmentRenewalBillingPlanType') + """ + https://developer.apple.com/documentation/appstoreserverapi/commitmentrenewalbillingplantype + """ + + rawCommitmentRenewalBillingPlanType: Optional[str] = RenewalBillingPlanType.create_raw_attr('commitmentRenewalBillingPlanType') + """ + See commitmentRenewalBillingPlanType + """ + + commitmentRenewalDate: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/commitmentrenewaldate + """ + + commitmentRenewalPrice: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/commitmentrenewalprice + """ diff --git a/appstoreserverlibrary/models/TransactionCommitmentInfo.py b/appstoreserverlibrary/models/TransactionCommitmentInfo.py new file mode 100644 index 00000000..13bd9a0b --- /dev/null +++ b/appstoreserverlibrary/models/TransactionCommitmentInfo.py @@ -0,0 +1,34 @@ +# Copyright (c) 2026 Apple Inc. Licensed under MIT License. + +from typing import Optional + +from attr import define +import attr + +from .HelperValidationUtils import HelperValidationUtils + +@define +class TransactionCommitmentInfo: + """ + https://developer.apple.com/documentation/appstoreserverapi/transactioncommitmentinfo + """ + + billingPeriodNumber: Optional[int] = attr.ib(default=None, validator=attr.validators.optional(HelperValidationUtils.period_count_validator)) + """ + https://developer.apple.com/documentation/appstoreserverapi/billingperiodnumber + """ + + commitmentExpiresDate: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/commitmentexpiresdate + """ + + commitmentPrice: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/commitmentprice + """ + + totalBillingPeriods: Optional[int] = attr.ib(default=None) + """ + https://developer.apple.com/documentation/appstoreserverapi/totalbillingperiods + """ diff --git a/tests/resources/models/signedRenewalInfo.json b/tests/resources/models/signedRenewalInfo.json index d4bf60be..1cbce07b 100644 --- a/tests/resources/models/signedRenewalInfo.json +++ b/tests/resources/models/signedRenewalInfo.json @@ -22,5 +22,39 @@ ], "appTransactionId": "71134", "offerPeriod": "P1Y", - "appAccountToken": "7e3fb20b-4cdb-47cc-936d-99d65f608138" + "appAccountToken": "7e3fb20b-4cdb-47cc-936d-99d65f608138", + "advancedCommerceInfo": { + "consistencyToken": "token-abc-123", + "descriptors": { + "description": "Premium Plan", + "displayName": "Premium" + }, + "items": [ + { + "SKU": "com.example.sku.premium", + "description": "Premium feature", + "displayName": "Premium Feature", + "price": 9990, + "priceIncreaseInfo": { + "dependentSKUs": [ + "com.example.sku.1", + "com.example.sku.2" + ], + "price": 12990, + "status": "PENDING" + } + } + ], + "period": "P1M", + "requestReferenceId": "ref-12345", + "taxCode": "TAX_CODE_1" + }, + "commitmentInfo": { + "commitmentAutoRenewProductId": "com.example.product.commitment", + "commitmentAutoRenewStatus": 1, + "commitmentRenewalBillingPlanType": "MONTHLY", + "commitmentRenewalDate": 1698149500000, + "commitmentRenewalPrice": 9990 + }, + "renewalBillingPlanType": "MONTHLY" } diff --git a/tests/resources/models/signedTransaction.json b/tests/resources/models/signedTransaction.json index f81bac8c..3d405b64 100644 --- a/tests/resources/models/signedTransaction.json +++ b/tests/resources/models/signedTransaction.json @@ -26,5 +26,41 @@ "currency": "USD", "offerDiscountType": "PAY_AS_YOU_GO", "appTransactionId": "71134", - "offerPeriod": "P1Y" + "offerPeriod": "P1Y", + "advancedCommerceInfo": { + "descriptors": { + "description": "Premium Plan", + "displayName": "Premium" + }, + "estimatedTax": 1500, + "items": [ + { + "SKU": "com.example.sku.premium", + "description": "Premium feature", + "displayName": "Premium Feature", + "price": 9990, + "refunds": [ + { + "refundAmount": 5000, + "refundDate": 1698149100000, + "refundReason": "FULFILLMENT_ISSUE", + "refundType": "PRORATED" + } + ], + "revocationDate": 1698149200000 + } + ], + "period": "P1M", + "requestReferenceId": "ref-12345", + "taxCode": "TAX_CODE_1", + "taxExclusivePrice": 8490, + "taxRate": "0.15" + }, + "billingPlanType": "MONTHLY", + "commitmentInfo": { + "billingPeriodNumber": 3, + "commitmentExpiresDate": 1698150000000, + "commitmentPrice": 119880, + "totalBillingPeriods": 12 + } } \ No newline at end of file diff --git a/tests/test_advanced_commerce_models.py b/tests/test_advanced_commerce_models.py index 660bf7f0..88ec27cd 100644 --- a/tests/test_advanced_commerce_models.py +++ b/tests/test_advanced_commerce_models.py @@ -11,9 +11,14 @@ from appstoreserverlibrary.models.AdvancedCommerceOneTimeChargeCreateRequest import AdvancedCommerceOneTimeChargeCreateRequest from appstoreserverlibrary.models.AdvancedCommerceOneTimeChargeItem import AdvancedCommerceOneTimeChargeItem from appstoreserverlibrary.models.AdvancedCommercePeriod import AdvancedCommercePeriod +from appstoreserverlibrary.models.AdvancedCommercePriceIncreaseInfo import AdvancedCommercePriceIncreaseInfo +from appstoreserverlibrary.models.AdvancedCommercePriceIncreaseInfoStatus import AdvancedCommercePriceIncreaseInfoStatus from appstoreserverlibrary.models.AdvancedCommerceReason import AdvancedCommerceReason +from appstoreserverlibrary.models.AdvancedCommerceRefund import AdvancedCommerceRefund from appstoreserverlibrary.models.AdvancedCommerceRefundReason import AdvancedCommerceRefundReason from appstoreserverlibrary.models.AdvancedCommerceRefundType import AdvancedCommerceRefundType +from appstoreserverlibrary.models.AdvancedCommerceRenewalInfo import AdvancedCommerceRenewalInfo +from appstoreserverlibrary.models.AdvancedCommerceRenewalItem import AdvancedCommerceRenewalItem from appstoreserverlibrary.models.AdvancedCommerceRequestInfo import AdvancedCommerceRequestInfo from appstoreserverlibrary.models.AdvancedCommerceRequestRefundItem import AdvancedCommerceRequestRefundItem from appstoreserverlibrary.models.AdvancedCommerceRequestRefundRequest import AdvancedCommerceRequestRefundRequest @@ -44,7 +49,14 @@ from appstoreserverlibrary.models.AdvancedCommerceSubscriptionReactivateItem import AdvancedCommerceSubscriptionReactivateItem from appstoreserverlibrary.models.AdvancedCommerceSubscriptionRevokeRequest import AdvancedCommerceSubscriptionRevokeRequest from appstoreserverlibrary.models.AdvancedCommerceSubscriptionRevokeResponse import AdvancedCommerceSubscriptionRevokeResponse -from appstoreserverlibrary.models.AdvancedCommerceValidationUtils import AdvancedCommerceValidationUtils +from appstoreserverlibrary.models.AdvancedCommerceTransactionInfo import AdvancedCommerceTransactionInfo +from appstoreserverlibrary.models.AdvancedCommerceTransactionItem import AdvancedCommerceTransactionItem +from appstoreserverlibrary.models.HelperValidationUtils import HelperValidationUtils +from appstoreserverlibrary.models.AutoRenewStatus import AutoRenewStatus +from appstoreserverlibrary.models.BillingPlanType import BillingPlanType +from appstoreserverlibrary.models.RenewalBillingPlanType import RenewalBillingPlanType +from appstoreserverlibrary.models.RenewalCommitmentInfo import RenewalCommitmentInfo +from appstoreserverlibrary.models.TransactionCommitmentInfo import TransactionCommitmentInfo from appstoreserverlibrary.models.LibraryUtility import _get_cattrs_converter from tests.util import read_data_from_file @@ -143,47 +155,47 @@ def test_advanced_commerce_effective(self): def test_validation_utils_description(self): valid_description = "Valid description" - AdvancedCommerceValidationUtils.description_validator(None, None, valid_description) + HelperValidationUtils.description_validator(None, None, valid_description) max_length_description = "A" * 45 - AdvancedCommerceValidationUtils.description_validator(None, None, max_length_description) + HelperValidationUtils.description_validator(None, None, max_length_description) too_long_description = "A" * 46 with self.assertRaises(ValueError): - AdvancedCommerceValidationUtils.description_validator(None, None, too_long_description) + HelperValidationUtils.description_validator(None, None, too_long_description) def test_validation_utils_display_name(self): valid_display_name = "Valid Name" - AdvancedCommerceValidationUtils.display_name_validator(None, None, valid_display_name) + HelperValidationUtils.display_name_validator(None, None, valid_display_name) max_length_display_name = "A" * 30 - AdvancedCommerceValidationUtils.display_name_validator(None, None, max_length_display_name) + HelperValidationUtils.display_name_validator(None, None, max_length_display_name) too_long_display_name = "A" * 31 with self.assertRaises(ValueError): - AdvancedCommerceValidationUtils.display_name_validator(None, None, too_long_display_name) + HelperValidationUtils.display_name_validator(None, None, too_long_display_name) def test_validation_utils_sku(self): valid_sku = "valid.sku.123" - AdvancedCommerceValidationUtils.sku_validator(None, None, valid_sku) + HelperValidationUtils.sku_validator(None, None, valid_sku) max_length_sku = "A" * 128 - AdvancedCommerceValidationUtils.sku_validator(None, None, max_length_sku) + HelperValidationUtils.sku_validator(None, None, max_length_sku) too_long_sku = "A" * 129 with self.assertRaises(ValueError): - AdvancedCommerceValidationUtils.sku_validator(None, None, too_long_sku) + HelperValidationUtils.sku_validator(None, None, too_long_sku) def test_validation_utils_period_count(self): - AdvancedCommerceValidationUtils.period_count_validator(None, None, 1) - AdvancedCommerceValidationUtils.period_count_validator(None, None, 6) - AdvancedCommerceValidationUtils.period_count_validator(None, None, 12) + HelperValidationUtils.period_count_validator(None, None, 1) + HelperValidationUtils.period_count_validator(None, None, 6) + HelperValidationUtils.period_count_validator(None, None, 12) with self.assertRaises(ValueError): - AdvancedCommerceValidationUtils.period_count_validator(None, None, 0) + HelperValidationUtils.period_count_validator(None, None, 0) with self.assertRaises(ValueError): - AdvancedCommerceValidationUtils.period_count_validator(None, None, 13) + HelperValidationUtils.period_count_validator(None, None, 13) def test_validation_utils_items(self): valid_list = [ @@ -194,17 +206,17 @@ def test_validation_utils_items(self): price=1000 ) ] - AdvancedCommerceValidationUtils.items_validator(None, None, valid_list) + HelperValidationUtils.items_validator(None, None, valid_list) with self.assertRaises(ValueError): - AdvancedCommerceValidationUtils.items_validator(None, None, None) + HelperValidationUtils.items_validator(None, None, None) with self.assertRaises(ValueError): - AdvancedCommerceValidationUtils.items_validator(None, None, []) + HelperValidationUtils.items_validator(None, None, []) list_with_none = [None] with self.assertRaises(ValueError): - AdvancedCommerceValidationUtils.items_validator(None, None, list_with_none) + HelperValidationUtils.items_validator(None, None, list_with_none) def test_advanced_commerce_descriptors_deserialization(self): json_data = read_data_from_file('tests/resources/models/advancedCommerceDescriptors.json') @@ -609,3 +621,52 @@ def test_advanced_commerce_subscription_migrate_response_deserialization(self): self.assertEqual("signed_renewal_info_value", response.signedRenewalInfo) self.assertEqual("signed_transaction_info_value", response.signedTransactionInfo) + + def test_advanced_commerce_price_increase_info_status(self): + self.assertEqual("SCHEDULED", AdvancedCommercePriceIncreaseInfoStatus.SCHEDULED.value) + self.assertEqual("PENDING", AdvancedCommercePriceIncreaseInfoStatus.PENDING.value) + self.assertEqual("ACCEPTED", AdvancedCommercePriceIncreaseInfoStatus.ACCEPTED.value) + + self.assertEqual(AdvancedCommercePriceIncreaseInfoStatus.SCHEDULED, + AdvancedCommercePriceIncreaseInfoStatus("SCHEDULED")) + self.assertEqual(AdvancedCommercePriceIncreaseInfoStatus.PENDING, + AdvancedCommercePriceIncreaseInfoStatus("PENDING")) + self.assertEqual(AdvancedCommercePriceIncreaseInfoStatus.ACCEPTED, + AdvancedCommercePriceIncreaseInfoStatus("ACCEPTED")) + self.assertFalse("INVALID" in AdvancedCommercePriceIncreaseInfoStatus) + + self.assertEqual("SCHEDULED", AdvancedCommercePriceIncreaseInfoStatus.SCHEDULED.value) + + def test_billing_plan_type(self): + self.assertEqual("BILLED_UPFRONT", BillingPlanType.BILLED_UPFRONT.value) + self.assertEqual("MONTHLY", BillingPlanType.MONTHLY.value) + + self.assertEqual(BillingPlanType.BILLED_UPFRONT, + BillingPlanType("BILLED_UPFRONT")) + self.assertEqual(BillingPlanType.MONTHLY, + BillingPlanType("MONTHLY")) + self.assertFalse("INVALID" in BillingPlanType) + + self.assertEqual("MONTHLY", BillingPlanType.MONTHLY.value) + + def test_renewal_billing_plan_type(self): + self.assertEqual("BILLED_UPFRONT", RenewalBillingPlanType.BILLED_UPFRONT.value) + self.assertEqual("MONTHLY", RenewalBillingPlanType.MONTHLY.value) + + self.assertEqual(RenewalBillingPlanType.BILLED_UPFRONT, + RenewalBillingPlanType("BILLED_UPFRONT")) + self.assertEqual(RenewalBillingPlanType.MONTHLY, + RenewalBillingPlanType("MONTHLY")) + self.assertFalse("INVALID" in RenewalBillingPlanType) + + self.assertEqual("BILLED_UPFRONT", RenewalBillingPlanType.BILLED_UPFRONT.value) + + def test_transaction_commitment_info_billing_period_number_validation(self): + info = TransactionCommitmentInfo(billingPeriodNumber=1) + self.assertEqual(1, info.billingPeriodNumber) + + info = TransactionCommitmentInfo(billingPeriodNumber=12) + self.assertEqual(12, info.billingPeriodNumber) + + info = TransactionCommitmentInfo(billingPeriodNumber=None) + self.assertIsNone(info.billingPeriodNumber) diff --git a/tests/test_decoded_payloads.py b/tests/test_decoded_payloads.py index f5644613..20232480 100644 --- a/tests/test_decoded_payloads.py +++ b/tests/test_decoded_payloads.py @@ -4,6 +4,7 @@ import unittest from uuid import UUID from appstoreserverlibrary.models.AutoRenewStatus import AutoRenewStatus +from appstoreserverlibrary.models.BillingPlanType import BillingPlanType from appstoreserverlibrary.models.ConsumptionRequestReason import ConsumptionRequestReason from appstoreserverlibrary.models.Environment import Environment from appstoreserverlibrary.models.ExpirationIntent import ExpirationIntent @@ -13,12 +14,17 @@ from appstoreserverlibrary.models.OfferType import OfferType from appstoreserverlibrary.models.PriceIncreaseStatus import PriceIncreaseStatus from appstoreserverlibrary.models.PurchasePlatform import PurchasePlatform +from appstoreserverlibrary.models.RenewalBillingPlanType import RenewalBillingPlanType from appstoreserverlibrary.models.RevocationReason import RevocationReason from appstoreserverlibrary.models.RevocationType import RevocationType from appstoreserverlibrary.models.Status import Status from appstoreserverlibrary.models.Subtype import Subtype from appstoreserverlibrary.models.TransactionReason import TransactionReason from appstoreserverlibrary.models.Type import Type +from appstoreserverlibrary.models.AdvancedCommercePeriod import AdvancedCommercePeriod +from appstoreserverlibrary.models.AdvancedCommercePriceIncreaseInfoStatus import AdvancedCommercePriceIncreaseInfoStatus +from appstoreserverlibrary.models.AdvancedCommerceRefundReason import AdvancedCommerceRefundReason +from appstoreserverlibrary.models.AdvancedCommerceRefundType import AdvancedCommerceRefundType from tests.util import create_signed_data_from_json, get_default_signed_data_verifier, get_signed_data_verifier @@ -87,6 +93,43 @@ def test_transaction_decoding(self): self.assertEqual("PAY_AS_YOU_GO", transaction.rawOfferDiscountType) self.assertEqual("71134", transaction.appTransactionId) self.assertEqual("P1Y", transaction.offerPeriod) + self.assertIsNotNone(transaction.advancedCommerceInfo) + info = transaction.advancedCommerceInfo + self.assertIsNotNone(info.descriptors) + self.assertEqual("Premium Plan", info.descriptors.description) + self.assertEqual("Premium", info.descriptors.displayName) + self.assertEqual(1500, info.estimatedTax) + self.assertEqual(AdvancedCommercePeriod.P1M, info.period) + self.assertEqual("P1M", info.rawPeriod) + self.assertEqual("ref-12345", info.requestReferenceId) + self.assertEqual("TAX_CODE_1", info.taxCode) + self.assertEqual(8490, info.taxExclusivePrice) + self.assertEqual("0.15", info.taxRate) + self.assertIsNotNone(info.items) + self.assertEqual(1, len(info.items)) + acItem = info.items[0] + self.assertEqual("com.example.sku.premium", acItem.SKU) + self.assertEqual("Premium feature", acItem.description) + self.assertEqual("Premium Feature", acItem.displayName) + self.assertEqual(9990, acItem.price) + self.assertEqual(1698149200000, acItem.revocationDate) + self.assertIsNotNone(acItem.refunds) + self.assertEqual(1, len(acItem.refunds)) + refund = acItem.refunds[0] + self.assertEqual(5000, refund.refundAmount) + self.assertEqual(1698149100000, refund.refundDate) + self.assertEqual(AdvancedCommerceRefundReason.FULFILLMENT_ISSUE, refund.refundReason) + self.assertEqual("FULFILLMENT_ISSUE", refund.rawRefundReason) + self.assertEqual(AdvancedCommerceRefundType.PRORATED, refund.refundType) + self.assertEqual("PRORATED", refund.rawRefundType) + self.assertEqual(BillingPlanType.MONTHLY, transaction.billingPlanType) + self.assertEqual("MONTHLY", transaction.rawBillingPlanType) + self.assertIsNotNone(transaction.commitmentInfo) + commitment = transaction.commitmentInfo + self.assertEqual(3, commitment.billingPeriodNumber) + self.assertEqual(1698150000000, commitment.commitmentExpiresDate) + self.assertEqual(119880, commitment.commitmentPrice) + self.assertEqual(12, commitment.totalBillingPeriods) def test_transaction_with_revocation_decoding(self): signed_transaction = create_signed_data_from_json('tests/resources/models/signedTransactionWithRevocation.json') @@ -170,6 +213,39 @@ def test_renewal_info_decoding(self): self.assertEqual("71134", renewal_info.appTransactionId) self.assertEqual("P1Y", renewal_info.offerPeriod) self.assertEqual("7e3fb20b-4cdb-47cc-936d-99d65f608138", renewal_info.appAccountToken) + self.assertIsNotNone(renewal_info.advancedCommerceInfo) + acInfo = renewal_info.advancedCommerceInfo + self.assertEqual("token-abc-123", acInfo.consistencyToken) + self.assertIsNotNone(acInfo.descriptors) + self.assertEqual("Premium Plan", acInfo.descriptors.description) + self.assertEqual("Premium", acInfo.descriptors.displayName) + self.assertEqual(AdvancedCommercePeriod.P1M, acInfo.period) + self.assertEqual("P1M", acInfo.rawPeriod) + self.assertEqual("ref-12345", acInfo.requestReferenceId) + self.assertEqual("TAX_CODE_1", acInfo.taxCode) + self.assertIsNotNone(acInfo.items) + self.assertEqual(1, len(acInfo.items)) + item = acInfo.items[0] + self.assertEqual("com.example.sku.premium", item.SKU) + self.assertEqual("Premium feature", item.description) + self.assertEqual("Premium Feature", item.displayName) + self.assertEqual(9990, item.price) + self.assertIsNotNone(item.priceIncreaseInfo) + self.assertEqual(["com.example.sku.1", "com.example.sku.2"], item.priceIncreaseInfo.dependentSKUs) + self.assertEqual(12990, item.priceIncreaseInfo.price) + self.assertEqual(AdvancedCommercePriceIncreaseInfoStatus.PENDING, item.priceIncreaseInfo.status) + self.assertEqual("PENDING", item.priceIncreaseInfo.rawStatus) + self.assertIsNotNone(renewal_info.commitmentInfo) + commitment = renewal_info.commitmentInfo + self.assertEqual("com.example.product.commitment", commitment.commitmentAutoRenewProductId) + self.assertEqual(AutoRenewStatus.ON, commitment.commitmentAutoRenewStatus) + self.assertEqual(1, commitment.rawCommitmentAutoRenewStatus) + self.assertEqual(RenewalBillingPlanType.MONTHLY, commitment.commitmentRenewalBillingPlanType) + self.assertEqual("MONTHLY", commitment.rawCommitmentRenewalBillingPlanType) + self.assertEqual(1698149500000, commitment.commitmentRenewalDate) + self.assertEqual(9990, commitment.commitmentRenewalPrice) + self.assertEqual(RenewalBillingPlanType.MONTHLY, renewal_info.renewalBillingPlanType) + self.assertEqual("MONTHLY", renewal_info.rawRenewalBillingPlanType) def test_notification_decoding(self): signed_notification = create_signed_data_from_json('tests/resources/models/signedNotification.json') diff --git a/tests/test_retention_messaging.py b/tests/test_retention_messaging.py index bfd42ae2..45b66f67 100644 --- a/tests/test_retention_messaging.py +++ b/tests/test_retention_messaging.py @@ -4,6 +4,8 @@ from uuid import UUID from appstoreserverlibrary.models.AlternateProduct import AlternateProduct +from appstoreserverlibrary.models.AdvancedCommerceInfo import AdvancedCommerceInfo +from appstoreserverlibrary.models.BillingPlanType import BillingPlanType from appstoreserverlibrary.models.DecodedRealtimeRequestBody import DecodedRealtimeRequestBody from appstoreserverlibrary.models.Environment import Environment from appstoreserverlibrary.models.Message import Message @@ -44,7 +46,7 @@ def test_realtime_response_body_with_alternate_product(self): # Create a RealtimeResponseBody with an AlternateProduct message_id = UUID('b2c3d4e5-f6a7-8901-b2c3-d4e5f6a78901') product_id = 'com.example.alternate.product' - alternate_product = AlternateProduct(messageIdentifier=message_id, productId=product_id) + alternate_product = AlternateProduct(messageIdentifier=message_id, productId=product_id, billingPlanType=BillingPlanType.MONTHLY) response_body = RealtimeResponseBody(alternateProduct=alternate_product) # Serialize to dict @@ -57,6 +59,7 @@ def test_realtime_response_body_with_alternate_product(self): self.assertIn('productId', json_dict['alternateProduct']) self.assertEqual('b2c3d4e5-f6a7-8901-b2c3-d4e5f6a78901', json_dict['alternateProduct']['messageIdentifier']) self.assertEqual('com.example.alternate.product', json_dict['alternateProduct']['productId']) + self.assertEqual('MONTHLY', json_dict['alternateProduct']['billingPlanType']) self.assertNotIn('message', json_dict) self.assertNotIn('promotionalOffer', json_dict) @@ -68,6 +71,8 @@ def test_realtime_response_body_with_alternate_product(self): self.assertIsNotNone(deserialized.alternateProduct) self.assertEqual(message_id, deserialized.alternateProduct.messageIdentifier) self.assertEqual(product_id, deserialized.alternateProduct.productId) + self.assertEqual(BillingPlanType.MONTHLY, deserialized.alternateProduct.billingPlanType) + self.assertEqual("MONTHLY", deserialized.alternateProduct.rawBillingPlanType) self.assertIsNone(deserialized.promotionalOffer) def test_realtime_response_body_with_promotional_offer_v2(self): @@ -169,3 +174,28 @@ def test_realtime_response_body_with_promotional_offer_v1(self): self.assertEqual('keyId123', deserialized_v1.keyId) self.assertEqual(app_account_token, deserialized_v1.appAccountToken) self.assertEqual('base64encodedSignature', deserialized_v1.encodedSignature) + + def test_realtime_response_body_with_advanced_commerce_info(self): + message_id = UUID('a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890') + ac_data = 'eyJhbGciOiJFUzI1NiJ9.base64data' + ac_info = AdvancedCommerceInfo(messageIdentifier=message_id, advancedCommerceData=ac_data) + response_body = RealtimeResponseBody(advancedCommerceInfo=ac_info) + + c = _get_cattrs_converter(RealtimeResponseBody) + json_dict = c.unstructure(response_body) + + self.assertIn('advancedCommerceInfo', json_dict) + self.assertEqual('a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890', json_dict['advancedCommerceInfo']['messageIdentifier']) + self.assertEqual(ac_data, json_dict['advancedCommerceInfo']['advancedCommerceData']) + self.assertNotIn('message', json_dict) + self.assertNotIn('alternateProduct', json_dict) + self.assertNotIn('promotionalOffer', json_dict) + + deserialized = c.structure(json_dict, RealtimeResponseBody) + + self.assertIsNone(deserialized.message) + self.assertIsNone(deserialized.alternateProduct) + self.assertIsNone(deserialized.promotionalOffer) + self.assertIsNotNone(deserialized.advancedCommerceInfo) + self.assertEqual(message_id, deserialized.advancedCommerceInfo.messageIdentifier) + self.assertEqual(ac_data, deserialized.advancedCommerceInfo.advancedCommerceData)