|
|
@ -207,13 +207,14 @@ class SubscriptionInvoiceItem(Hashable): |
|
|
|
.. versionadded:: 2.1 |
|
|
|
""" |
|
|
|
|
|
|
|
__slots__ = ('id', 'quantity', 'amount', 'proration', 'plan_id', 'plan_price', 'discounts', 'metadata') |
|
|
|
__slots__ = ('id', 'quantity', 'amount', 'proration', 'sku_id', 'plan_id', 'plan_price', 'discounts', 'metadata') |
|
|
|
|
|
|
|
def __init__(self, data: SubscriptionInvoiceItemPayload) -> None: |
|
|
|
self.id: int = int(data['id']) |
|
|
|
self.quantity: int = data['quantity'] |
|
|
|
self.amount: int = data['amount'] |
|
|
|
self.proration: bool = data.get('proration', False) |
|
|
|
self.sku_id: Optional[int] = _get_as_snowflake(data, 'sku_id') # Seems to always be null |
|
|
|
self.plan_id: int = int(data['subscription_plan_id']) |
|
|
|
self.plan_price: int = data['subscription_plan_price'] |
|
|
|
self.discounts: List[SubscriptionDiscount] = [SubscriptionDiscount(d) for d in data['discounts']] |
|
|
@ -282,10 +283,6 @@ class SubscriptionInvoice(Hashable): |
|
|
|
When the current billing period started. |
|
|
|
current_period_end: :class:`datetime.datetime` |
|
|
|
When the current billing period ends. |
|
|
|
applied_discount_ids: List[:class:`int`] |
|
|
|
The IDs of the discounts applied to the invoice. |
|
|
|
|
|
|
|
.. versionadded:: 2.1 |
|
|
|
""" |
|
|
|
|
|
|
|
__slots__ = ( |
|
|
@ -301,8 +298,6 @@ class SubscriptionInvoice(Hashable): |
|
|
|
'items', |
|
|
|
'current_period_start', |
|
|
|
'current_period_end', |
|
|
|
'applied_discount_ids', |
|
|
|
'applied_user_discounts', |
|
|
|
) |
|
|
|
|
|
|
|
def __init__( |
|
|
@ -327,12 +322,6 @@ class SubscriptionInvoice(Hashable): |
|
|
|
self.current_period_start: datetime = parse_time(data['subscription_period_start']) # type: ignore # Should always be a datetime |
|
|
|
self.current_period_end: datetime = parse_time(data['subscription_period_end']) # type: ignore # Should always be a datetime |
|
|
|
|
|
|
|
# These fields are unknown |
|
|
|
self.applied_discount_ids: List[int] = [int(id) for id in data.get('applied_discount_ids', [])] |
|
|
|
self.applied_user_discounts: Dict[int, Optional[Any]] = { |
|
|
|
int(k): v for k, v in data.get('applied_user_discounts', {}).items() |
|
|
|
} |
|
|
|
|
|
|
|
def __repr__(self) -> str: |
|
|
|
return f'<SubscriptionInvoice id={self.id} status={self.status!r} total={self.total}>' |
|
|
|
|
|
|
@ -489,6 +478,14 @@ class Subscription(Hashable): |
|
|
|
The mutations to the subscription that will occur after renewal. |
|
|
|
trial_id: Optional[:class:`int`] |
|
|
|
The ID of the trial the subscription is from, if applicable. |
|
|
|
discount_id: Optional[:class:`int`] |
|
|
|
The ID of the discount the subscription has active. |
|
|
|
|
|
|
|
.. versionadded:: 2.1 |
|
|
|
discount_expires_at: Optional[:class:`datetime.datetime`] |
|
|
|
When the discount expires, if applicable. |
|
|
|
|
|
|
|
.. versionadded:: 2.1 |
|
|
|
payment_source_id: Optional[:class:`int`] |
|
|
|
The ID of the payment source the subscription is paid with, if applicable. |
|
|
|
payment_gateway_plan_id: Optional[:class:`str`] |
|
|
@ -546,9 +543,11 @@ class Subscription(Hashable): |
|
|
|
'current_period_end', |
|
|
|
'trial_ends_at', |
|
|
|
'streak_started_at', |
|
|
|
'ended_at', |
|
|
|
'use_storekit_resubscribe', |
|
|
|
'metadata', |
|
|
|
'ended_at', |
|
|
|
'discount_id', |
|
|
|
'discount_expires_at', |
|
|
|
'latest_invoice', |
|
|
|
) |
|
|
|
|
|
|
@ -596,9 +595,12 @@ class Subscription(Hashable): |
|
|
|
self.streak_started_at: Optional[datetime] = parse_time(data.get('streak_started_at')) |
|
|
|
self.use_storekit_resubscribe: bool = data.get('use_storekit_resubscribe', False) |
|
|
|
|
|
|
|
# Some metadata is exposed; I don't love this implementation |
|
|
|
metadata = data.get('metadata') or {} |
|
|
|
self.ended_at: Optional[datetime] = parse_time(metadata.get('ended_at', None)) |
|
|
|
self.metadata: Metadata = Metadata(metadata) |
|
|
|
self.ended_at: Optional[datetime] = parse_time(metadata.get('ended_at', None)) |
|
|
|
self.discount_id = _get_as_snowflake(metadata, 'active_discount_id') |
|
|
|
self.discount_expires_at = parse_time(metadata.get('active_discount_expires_at', None)) |
|
|
|
|
|
|
|
self.latest_invoice: Optional[SubscriptionInvoice] = ( |
|
|
|
SubscriptionInvoice(self, data=data['latest_invoice'], state=self._state) if 'latest_invoice' in data else None # type: ignore # ??? |
|
|
@ -616,22 +618,35 @@ class Subscription(Hashable): |
|
|
|
@property |
|
|
|
def guild(self) -> Optional[Guild]: |
|
|
|
"""Optional[:class:`Guild`]: The guild the subscription's entitlements apply to, if applicable.""" |
|
|
|
return self._state._get_guild(self.metadata.guild_id) |
|
|
|
# I think guild_id is deprecated |
|
|
|
return self._state._get_guild(self.metadata.guild_id or self.metadata.application_subscription_guild_id) |
|
|
|
|
|
|
|
@property |
|
|
|
def grace_period(self) -> timedelta: |
|
|
|
""":class:`datetime.timedelta`: How many days past the renewal date the user has available to pay outstanding invoices. |
|
|
|
def grace_period(self) -> Optional[timedelta]: |
|
|
|
"""Optional[:class:`datetime.timedelta`]: How many days past the renewal date the user has available to pay outstanding invoices. |
|
|
|
|
|
|
|
.. note:: |
|
|
|
|
|
|
|
This is a static value and does not change based on the subscription's status. |
|
|
|
For that, see :attr:`remaining`. |
|
|
|
This is only available for past-due subscriptions. |
|
|
|
|
|
|
|
.. versionchanged:: 2.1 |
|
|
|
|
|
|
|
This is now a :class:`datetime.timedelta` instead of an :class:`int`. |
|
|
|
This is now a Optional[:class:`datetime.timedelta`] instead of an :class:`int`. |
|
|
|
""" |
|
|
|
if self.grace_period_expires_at: |
|
|
|
return self.grace_period_expires_at - utcnow() |
|
|
|
|
|
|
|
@property |
|
|
|
def grace_period_expires_at(self) -> Optional[datetime]: |
|
|
|
"""Optional[:class:`datetime.datetime`]: When the grace period expires. |
|
|
|
|
|
|
|
.. versionadded:: 2.1 |
|
|
|
""" |
|
|
|
return timedelta(days=7 if self.payment_source_id else 3) |
|
|
|
if self.payment_gateway == PaymentGateway.apple: |
|
|
|
return self.metadata.apple_grace_period_expires_date or self.metadata.grace_period_expires_date |
|
|
|
if self.payment_gateway == PaymentGateway.google: |
|
|
|
return self.metadata.google_grace_period_expires_date or self.metadata.grace_period_expires_date |
|
|
|
return self.metadata.grace_period_expires_date |
|
|
|
|
|
|
|
@property |
|
|
|
def remaining(self) -> timedelta: |
|
|
@ -639,12 +654,14 @@ class Subscription(Hashable): |
|
|
|
if self.status in (SubscriptionStatus.active, SubscriptionStatus.cancelled): |
|
|
|
return self.current_period_end - utcnow() |
|
|
|
elif self.status == SubscriptionStatus.past_due: |
|
|
|
if self.payment_gateway == PaymentGateway.google and self.metadata.google_grace_period_expires_date: |
|
|
|
return self.metadata.google_grace_period_expires_date - utcnow() |
|
|
|
return (self.current_period_start + self.grace_period) - utcnow() |
|
|
|
# Grace expiry is now always provided |
|
|
|
return self.grace_period_expires_at - utcnow() # type: ignore |
|
|
|
elif self.status == SubscriptionStatus.account_hold: |
|
|
|
# Max hold time is 30 days |
|
|
|
return (self.current_period_start + timedelta(days=30)) - utcnow() |
|
|
|
elif self.status == SubscriptionStatus.billing_retry: |
|
|
|
# Max retry time is 7 days |
|
|
|
return (self.current_period_start + timedelta(days=7)) - utcnow() |
|
|
|
return timedelta() |
|
|
|
|
|
|
|
@property |
|
|
@ -657,6 +674,16 @@ class Subscription(Hashable): |
|
|
|
return self.remaining |
|
|
|
return self.trial_ends_at - utcnow() |
|
|
|
|
|
|
|
@property |
|
|
|
def discount_remaining(self) -> timedelta: |
|
|
|
""":class:`datetime.timedelta`: The remaining time until the active discount on the subscription ends.""" |
|
|
|
if not self.discount_id: |
|
|
|
return timedelta() |
|
|
|
if not self.discount_expires_at: |
|
|
|
# Infinite discount? |
|
|
|
return self.remaining |
|
|
|
return self.discount_expires_at - utcnow() |
|
|
|
|
|
|
|
def is_active(self) -> bool: |
|
|
|
""":class:`bool`: Indicates if the subscription is currently active and providing perks.""" |
|
|
|
return self.remaining > timedelta() |
|
|
@ -750,6 +777,7 @@ class Subscription(Hashable): |
|
|
|
currency: str = MISSING, |
|
|
|
apply_entitlements: bool = MISSING, |
|
|
|
renewal: bool = MISSING, |
|
|
|
discount_offer: Snowflake = MISSING, |
|
|
|
) -> SubscriptionInvoice: |
|
|
|
"""|coro| |
|
|
|
|
|
|
@ -769,6 +797,8 @@ class Subscription(Hashable): |
|
|
|
Whether to apply entitlements (credits) to the previewed invoice. |
|
|
|
renewal: :class:`bool` |
|
|
|
Whether the previewed invoice should be a renewal. |
|
|
|
discount_offer: :class:`.DiscountOffer` |
|
|
|
The discount offer to apply to the previewed invoice. |
|
|
|
|
|
|
|
Raises |
|
|
|
------ |
|
|
@ -791,6 +821,8 @@ class Subscription(Hashable): |
|
|
|
payload['apply_entitlements'] = apply_entitlements |
|
|
|
if renewal is not MISSING: |
|
|
|
payload['renewal'] = renewal |
|
|
|
if discount_offer is not MISSING: |
|
|
|
payload['user_discount_offer_id'] = discount_offer.id |
|
|
|
|
|
|
|
if payload: |
|
|
|
data = await self._state.http.preview_subscription_update(self.id, **payload) |
|
|
|