Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions appstoreserverlibrary/models/AlternateProduct.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright (c) 2023 Apple Inc. Licensed under MIT License.

from typing import Optional
from attr import define
import attr

@define
class AlternateProduct:
"""
A switch-plan message and product ID you provide in a real-time response to your Get Retention Message endpoint.

https://developer.apple.com/documentation/retentionmessaging/alternateproduct
"""

messageIdentifier: Optional[str] = attr.ib(default=None)
"""
The message identifier of the text to display in the switch-plan retention message.

The message identifier needs to refer to a message that doesn't include an image and that
has a messageState of APPROVED; otherwise, the retention message fails.

https://developer.apple.com/documentation/retentionmessaging/messageidentifier
"""

productId: Optional[str] = attr.ib(default=None)
"""
The product identifier of the subscription the retention message suggests for your customer to switch to.

Use the product identifier in DecodedRealtimeRequestBody to determine the customer's current subscription.
Choose an alternative subscription from the same subscription group.

https://developer.apple.com/documentation/retentionmessaging/productid
"""
20 changes: 20 additions & 0 deletions appstoreserverlibrary/models/LibraryUtility.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from functools import lru_cache
from typing import Any, List, Type, TypeVar

import attr
from attr import Attribute, has, ib, fields
from cattr import override
from cattrs.gen import make_dict_structure_fn, make_dict_unstructure_fn, override
Expand Down Expand Up @@ -68,4 +69,23 @@ def _get_cattrs_converter(destination_class: Type[T]) -> cattrs.Converter:
cattrs_overrides[raw_field] = override(rename=matching_name)
c.register_structure_hook_factory(has, lambda cl: make_dict_structure_fn(cl, c, **cattrs_overrides))
c.register_unstructure_hook_factory(has, lambda cl: make_dict_unstructure_fn(cl, c, **cattrs_overrides))
return c


@lru_cache(maxsize=None)
def _get_retention_response_converter() -> cattrs.Converter:
"""
Special converter for retention messaging responses that omits None/default values.
This is needed for RealtimeResponseBody where fields are mutually exclusive.
"""
c = cattrs.Converter()

# For any attrs class, configure to omit fields with default values
c.register_unstructure_hook_factory(
has,
lambda cls: make_dict_unstructure_fn(
cls, c, _cattrs_omit_if_default=True
)
)

return c
26 changes: 26 additions & 0 deletions appstoreserverlibrary/models/Message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright (c) 2023 Apple Inc. Licensed under MIT License.

from typing import Optional
from attr import define
import attr

@define
class Message:
"""
A message identifier you provide in a real-time response to your Get Retention Message endpoint.

https://developer.apple.com/documentation/retentionmessaging/message
"""

messageIdentifier: Optional[str] = attr.ib(default=None)
"""
The identifier of the message to display to the customer.

The message identifier needs to refer to a message that has a messageState of APPROVED;
otherwise, the retention message fails. If the message includes an image, the image also
needs to have an imageState of APPROVED.

For more information about setting up messages, see Upload Message.

https://developer.apple.com/documentation/retentionmessaging/messageidentifier
"""
56 changes: 56 additions & 0 deletions appstoreserverlibrary/models/PromotionalOffer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright (c) 2023 Apple Inc. Licensed under MIT License.

from typing import Optional
from attr import define
import attr
from .PromotionalOfferSignatureV1 import PromotionalOfferSignatureV1

@define
class PromotionalOffer:
"""
A promotional offer and message you provide in a real-time response to your Get Retention Message endpoint.

https://developer.apple.com/documentation/retentionmessaging/promotionaloffer
"""

messageIdentifier: Optional[str] = attr.ib(default=None)
"""
The identifier of the message to display to the customer, along with the promotional offer.

The message identifier needs to refer to a message that doesn't have an image, and that has
a messageState of APPROVED; otherwise, the retention message fails.

https://developer.apple.com/documentation/retentionmessaging/messageidentifier
"""

promotionalOfferSignatureV2: Optional[str] = attr.ib(default=None)
"""
The promotional offer signature in V2 format. This field is mutually exclusive with
promotionalOfferSignatureV1 field.

For new implementations, consider using this V2 signature, which is easier to generate.
To generate this signature, use the PromotionalOfferV2SignatureCreator class:

Example:
from appstoreserverlibrary.jws_signature_creator import PromotionalOfferV2SignatureCreator

creator = PromotionalOfferV2SignatureCreator(signing_key, key_id, issuer_id, bundle_id)
signature = creator.create_signature(
product_id="com.example.subscription",
offer_identifier="intro_offer",
transaction_id="optional_transaction_id"
)

https://developer.apple.com/documentation/retentionmessaging/promotionaloffersignaturev2
"""

promotionalOfferSignatureV1: Optional[PromotionalOfferSignatureV1] = attr.ib(default=None)
"""
The promotional offer signature in V1 format. This field is mutually exclusive with the
promotionalOfferSignatureV2 field.

To generate this signature, use the PromotionalOfferSignatureCreator class.
See PromotionalOfferSignatureV1 for implementation details.

https://developer.apple.com/documentation/retentionmessaging/promotionaloffersignaturev1
"""
71 changes: 71 additions & 0 deletions appstoreserverlibrary/models/PromotionalOfferSignatureV1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright (c) 2023 Apple Inc. Licensed under MIT License.

from typing import Optional
from attr import define
import attr

@define
class PromotionalOfferSignatureV1:
"""
The promotional offer signature you generate using an earlier signature version.

To generate this signature, use the PromotionalOfferSignatureCreator class:

Example:
from appstoreserverlibrary.promotional_offer import PromotionalOfferSignatureCreator
import uuid
import time

creator = PromotionalOfferSignatureCreator(signing_key, key_id, bundle_id)
signature = creator.create_signature(
product_identifier="com.example.subscription",
subscription_offer_id="intro_offer",
application_username="user123",
nonce=uuid.uuid4(),
timestamp=int(time.time() * 1000)
)

https://developer.apple.com/documentation/retentionmessaging/promotionaloffersignaturev1
"""

encodedSignature: Optional[str] = attr.ib(default=None)
"""
The Base64-encoded cryptographic signature you generate using the offer parameters.

https://developer.apple.com/documentation/retentionmessaging/encodedsignature
"""

productId: Optional[str] = attr.ib(default=None)
"""
The subscription's product identifier.

https://developer.apple.com/documentation/retentionmessaging/productid
"""

nonce: Optional[str] = attr.ib(default=None)
"""
A one-time-use UUID antireplay value you generate. Use lowercase.

https://developer.apple.com/documentation/retentionmessaging/nonce
"""

offerId: Optional[str] = attr.ib(default=None)
"""
The promotional offer's identifier.

https://developer.apple.com/documentation/retentionmessaging/offerid
"""

timestamp: Optional[int] = attr.ib(default=None)
"""
The UNIX time, in milliseconds, that you generate the signature.

https://developer.apple.com/documentation/retentionmessaging/timestamp
"""

keyIdentifier: Optional[str] = attr.ib(default=None)
"""
Your private key identifier from App Store Connect.

https://developer.apple.com/documentation/retentionmessaging/keyidentifier
"""
61 changes: 61 additions & 0 deletions appstoreserverlibrary/models/RealtimeResponseBody.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright (c) 2023 Apple Inc. Licensed under MIT License.

from typing import Optional, Dict, Any
from attr import define
import attr
from .Message import Message
from .AlternateProduct import AlternateProduct
from .PromotionalOffer import PromotionalOffer

@define
class RealtimeResponseBody:
"""
A response you provide to choose, in real time, a retention message the system displays to the customer.

Note: The fields in RealtimeResponseBody are mutually exclusive. Choose the type of retention message
to display, and respond using only the field that represents that message type.

https://developer.apple.com/documentation/retentionmessaging/realtimeresponsebody
"""

message: Optional[Message] = attr.ib(default=None)
"""
A retention message that's text-based and can include an optional image.
If you supply this field, don't include the other fields.

https://developer.apple.com/documentation/retentionmessaging/message
"""

alternateProduct: Optional[AlternateProduct] = attr.ib(default=None)
"""
A retention message with a switch-plan option.
If you supply this field, don't include the other fields.

https://developer.apple.com/documentation/retentionmessaging/alternateproduct
"""

promotionalOffer: Optional[PromotionalOffer] = attr.ib(default=None)
"""
A retention message that includes a promotional offer.
If you supply this field, don't include the other fields.

https://developer.apple.com/documentation/retentionmessaging/promotionaloffer
"""

def to_json_dict(self) -> Dict[str, Any]:
"""
Convert to a dictionary suitable for JSON serialization.
Omits None values to maintain mutual exclusivity of fields.

Example:
response = RealtimeResponseBody(
message=Message(messageIdentifier="msg123")
)
json_data = json.dumps(response.to_json_dict())
# Result: {"message": {"messageIdentifier": "msg123"}}

:return: Dictionary representation suitable for JSON encoding
"""
from .LibraryUtility import _get_retention_response_converter
converter = _get_retention_response_converter()
return converter.unstructure(self)
Loading