You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
299 lines
10 KiB
299 lines
10 KiB
"""
|
|
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,
|
|
RefundDisqualificationReason,
|
|
RefundReason,
|
|
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:`RefundDisqualificationReason`]
|
|
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[RefundDisqualificationReason] = [
|
|
try_enum(RefundDisqualificationReason, r) for r in 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'<Payment id={self.id} amount={self.amount} currency={self.currency} status={self.status}>'
|
|
|
|
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: RefundReason = RefundReason.other) -> None:
|
|
"""|coro|
|
|
|
|
Refund the payment. Refunds can only be made for payments less than 5 days old.
|
|
|
|
Parameters
|
|
----------
|
|
reason: :class:`RefundReason`
|
|
The reason for the refund.
|
|
|
|
.. versionadded:: 2.1
|
|
|
|
Raises
|
|
------
|
|
HTTPException
|
|
Refunding the payment failed.
|
|
"""
|
|
await self._state.http.refund_payment(self.id, int(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'<EntitlementPayment id={self.id} amount={self.amount} currency={self.currency}>'
|
|
|