""" The MIT License (MIT) Copyright (c) 2021-present Dolfies Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from __future__ import annotations from datetime import datetime from typing import TYPE_CHECKING, List, Optional from .billing import PaymentSource from .enums import ( PaymentGateway, PaymentStatus, SubscriptionType, try_enum, ) from .flags import PaymentFlags from .mixins import Hashable from .store import SKU from .subscriptions import Subscription from .utils import _get_as_snowflake, parse_time if TYPE_CHECKING: from .entitlements import Entitlement from .state import ConnectionState from .types.payments import ( PartialPayment as PartialPaymentPayload, Payment as PaymentPayload, ) __all__ = ( 'Payment', 'EntitlementPayment', ) class Payment(Hashable): """Represents a payment to Discord. .. container:: operations .. describe:: x == y Checks if two payments are equal. .. describe:: x != y Checks if two payments are not equal. .. describe:: hash(x) Returns the payment's hash. .. describe:: str(x) Returns the payment's description. .. versionadded:: 2.0 Attributes ---------- id: :class:`int` The ID of the payment. amount: :class:`int` The amount of the payment. amount_refunded: :class:`int` The amount refunded from the payment, if any. tax: :class:`int` The amount of tax paid. tax_inclusive: :class:`bool` Whether the amount is inclusive of all taxes. currency: :class:`str` The currency the payment was made in. description: :class:`str` What the payment was for. status: :class:`PaymentStatus` The status of the payment. created_at: :class:`datetime.datetime` The time the payment was made. sku: Optional[:class:`SKU`] The SKU the payment was for, if applicable. sku_id: Optional[:class:`int`] The ID of the SKU the payment was for, if applicable. sku_price: Optional[:class:`int`] The price of the SKU the payment was for, if applicable. subscription_plan_id: Optional[:class:`int`] The ID of the subscription plan the payment was for, if applicable. subscription: Optional[:class:`Subscription`] The subscription the payment was for, if applicable. payment_source: Optional[:class:`PaymentSource`] The payment source the payment was made with. payment_gateway: Optional[:class:`PaymentGateway`] The payment gateway the payment was made with, if applicable. payment_gateway_payment_id: Optional[:class:`str`] The ID of the payment on the payment gateway, if any. invoice_url: Optional[:class:`str`] The URL to download the VAT invoice for this payment, if available. refund_invoices_urls: List[:class:`str`] A list of URLs to download VAT credit notices for refunds on this payment, if available. refund_disqualification_reasons: List[:class:`str`] A list of reasons why the payment cannot be refunded, if any. """ __slots__ = ( 'id', 'amount', 'amount_refunded', 'tax', 'tax_inclusive', 'currency', 'description', 'status', 'created_at', 'sku', 'sku_id', 'sku_price', 'subscription_plan_id', 'subscription', 'payment_source', 'payment_gateway', 'payment_gateway_payment_id', 'invoice_url', 'refund_invoices_urls', 'refund_disqualification_reasons', '_flags', '_state', ) def __init__(self, *, data: PaymentPayload, state: ConnectionState): self._state: ConnectionState = state self._update(data) def _update(self, data: PaymentPayload) -> None: state = self._state self.id: int = int(data['id']) self.amount: int = data['amount'] self.amount_refunded: int = data.get('amount_refunded') or 0 self.tax: int = data.get('tax') or 0 self.tax_inclusive: bool = data.get('tax_inclusive', True) self.currency: str = data.get('currency', 'usd') self.description: str = data['description'] self.status: PaymentStatus = try_enum(PaymentStatus, data['status']) self.created_at: datetime = parse_time(data['created_at']) self.sku: Optional[SKU] = SKU(data=data['sku'], state=state) if 'sku' in data else None self.sku_id: Optional[int] = _get_as_snowflake(data, 'sku_id') self.sku_price: Optional[int] = data.get('sku_price') self.subscription_plan_id: Optional[int] = _get_as_snowflake(data, 'sku_subscription_plan_id') self.payment_gateway: Optional[PaymentGateway] = ( try_enum(PaymentGateway, data['payment_gateway']) if 'payment_gateway' in data else None ) self.payment_gateway_payment_id: Optional[str] = data.get('payment_gateway_payment_id') self.invoice_url: Optional[str] = data.get('downloadable_invoice') self.refund_invoices_urls: List[str] = data.get('downloadable_refund_invoices', []) self.refund_disqualification_reasons: List[str] = data.get('premium_refund_disqualification_reasons', []) self._flags: int = data.get('flags', 0) # The subscription object does not include the payment source ID self.payment_source: Optional[PaymentSource] = ( PaymentSource(data=data['payment_source'], state=state) if 'payment_source' in data else None ) if 'subscription' in data and self.payment_source: data['subscription']['payment_source_id'] = self.payment_source.id # type: ignore self.subscription: Optional[Subscription] = ( Subscription(data=data['subscription'], state=state) if 'subscription' in data else None ) def __repr__(self) -> str: return f'' def __str__(self) -> str: return self.description def is_subscription(self) -> bool: """:class:`bool`: Whether the payment was for a subscription.""" return self.subscription is not None def is_premium_subscription(self) -> bool: """:class:`bool`: Whether the payment was for a Discord premium subscription.""" return self.subscription is not None and self.subscription.type == SubscriptionType.premium def is_premium_subscription_gift(self) -> bool: """:class:`bool`: Whether the payment was for a Discord premium subscription gift.""" return self.flags.gift and self.sku_id in self._state.premium_subscriptions_sku_ids.values() def is_purchased_externally(self) -> bool: """:class:`bool`: Whether the payment was made externally.""" return self.payment_gateway in (PaymentGateway.apple, PaymentGateway.google) @property def flags(self) -> PaymentFlags: """:class:`PaymentFlags`: Returns the payment's flags.""" return PaymentFlags._from_value(self._flags) async def void(self) -> None: """|coro| Void the payment. Only applicable for payments of status :attr:`PaymentStatus.pending`. Raises ------ HTTPException Voiding the payment failed. """ await self._state.http.void_payment(self.id) self.status = PaymentStatus.failed async def refund(self, reason: Optional[int] = None) -> None: """|coro| Refund the payment. Raises ------ HTTPException Refunding the payment failed. """ # reason here is an enum (0-8), but I was unable to find the enum values # Either way, it's optional and this endpoint isn't really used anyway await self._state.http.refund_payment(self.id, reason) self.status = PaymentStatus.refunded class EntitlementPayment(Hashable): """Represents a partial payment for an entitlement. .. container:: operations .. describe:: x == y Checks if two payments are equal. .. describe:: x != y Checks if two payments are not equal. .. describe:: hash(x) Returns the payment's hash. .. versionadded:: 2.0 Attributes ---------- entitlement: :class:`Entitlement` The entitlement the payment is for. id: :class:`int` The ID of the payment. amount: :class:`int` The amount of the payment. tax: :class:`int` The amount of tax paid. tax_inclusive: :class:`bool` Whether the amount is inclusive of all taxes. currency: :class:`str` The currency the payment was made in. """ __slots__ = ('entitlement', 'id', 'amount', 'tax', 'tax_inclusive', 'currency') def __init__(self, *, data: PartialPaymentPayload, entitlement: Entitlement): self.entitlement = entitlement self.id: int = int(data['id']) self.amount: int = data['amount'] self.tax: int = data.get('tax') or 0 self.tax_inclusive: bool = data.get('tax_inclusive', True) self.currency: str = data.get('currency', 'usd') def __repr__(self) -> str: return f''