Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Saved Payment Methods #67

Merged
merged 7 commits into from
Nov 14, 2024
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx&utm_medium=paddle-python-sdk) for information about changes to the Paddle Billing platform, the Paddle API, and other developer tools.

## [Unreleased]

### Added

- Support for saved payment methods, see [related changelog](https://developer.paddle.com/changelog/2024/saved-payment-methods?utm_source=dx&utm_medium=paddle-python-sdk)
- `Client.payment_methods.list`
- `Client.payment_methods.get`
- `Client.payment_methods.delete`
- `Client.customers.create_auth_token`

## 1.0.0 - 2024-11-11

### Changed
Expand Down
2 changes: 2 additions & 0 deletions paddle_billing/Client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from paddle_billing.Resources.Notifications.NotificationsClient import NotificationsClient
from paddle_billing.Resources.NotificationLogs.NotificationLogsClient import NotificationLogsClient
from paddle_billing.Resources.NotificationSettings.NotificationSettingsClient import NotificationSettingsClient
from paddle_billing.Resources.PaymentMethods.PaymentMethodsClient import PaymentMethodsClient
from paddle_billing.Resources.Prices.PricesClient import PricesClient
from paddle_billing.Resources.PricingPreviews.PricingPreviewsClient import PricingPreviewsClient
from paddle_billing.Resources.Products.ProductsClient import ProductsClient
Expand Down Expand Up @@ -90,6 +91,7 @@ def __init__(
self.notifications = NotificationsClient(self)
self.notification_logs = NotificationLogsClient(self)
self.notification_settings = NotificationSettingsClient(self)
self.payment_methods = PaymentMethodsClient(self)
self.prices = PricesClient(self)
self.pricing_previews = PricingPreviewsClient(self)
self.products = ProductsClient(self)
Expand Down
13 changes: 13 additions & 0 deletions paddle_billing/Entities/Collections/PaymentMethodCollection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from __future__ import annotations

from paddle_billing.Entities.Collections.Collection import Collection
from paddle_billing.Entities.Collections.Paginator import Paginator
from paddle_billing.Entities.PaymentMethod import PaymentMethod


class PaymentMethodCollection(Collection[PaymentMethod]):
@classmethod
def from_list(cls, items_data: list, paginator: Paginator | None = None) -> PaymentMethodCollection:
items: list[PaymentMethod] = [PaymentMethod.from_dict(item) for item in items_data]

return PaymentMethodCollection(items, paginator)
1 change: 1 addition & 0 deletions paddle_billing/Entities/Collections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from paddle_billing.Entities.Collections.NotificationLogCollection import NotificationLogCollection
from paddle_billing.Entities.Collections.NotificationSettingCollection import NotificationSettingCollection
from paddle_billing.Entities.Collections.Paginator import Paginator
from paddle_billing.Entities.Collections.PaymentMethodCollection import PaymentMethodCollection
from paddle_billing.Entities.Collections.PriceCollection import PriceCollection
from paddle_billing.Entities.Collections.ProductCollection import ProductCollection
from paddle_billing.Entities.Collections.ReportCollection import ReportCollection
Expand Down
18 changes: 18 additions & 0 deletions paddle_billing/Entities/CustomerAuthToken.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime

from paddle_billing.Entities.Entity import Entity


@dataclass
class CustomerAuthToken(Entity):
customer_auth_token: str
expires_at: datetime

@staticmethod
def from_dict(data: dict) -> CustomerAuthToken:
return CustomerAuthToken(
customer_auth_token=data["customer_auth_token"],
expires_at=datetime.fromisoformat(data["expires_at"]),
)
38 changes: 38 additions & 0 deletions paddle_billing/Entities/PaymentMethod.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime

from paddle_billing.Entities.Entity import Entity
from paddle_billing.Entities.Shared import (
Card,
Paypal,
SavedPaymentMethodOrigin,
SavedPaymentMethodType,
)


@dataclass
class PaymentMethod(Entity):
id: str
customer_id: str
address_id: str
type: SavedPaymentMethodType | None
card: Card | None
paypal: Paypal | None
origin: SavedPaymentMethodOrigin
saved_at: datetime
updated_at: datetime

@staticmethod
def from_dict(data: dict) -> PaymentMethod:
return PaymentMethod(
id=data["id"],
customer_id=data["customer_id"],
address_id=data["address_id"],
type=SavedPaymentMethodType(data["type"]),
card=Card.from_dict(data["card"]) if data.get("card") else None,
paypal=Paypal.from_dict(data["paypal"]) if data.get("paypal") else None,
origin=SavedPaymentMethodOrigin(data["origin"]),
saved_at=datetime.fromisoformat(data["saved_at"]),
updated_at=datetime.fromisoformat(data["updated_at"]),
)
15 changes: 15 additions & 0 deletions paddle_billing/Entities/Shared/Paypal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from __future__ import annotations
from dataclasses import dataclass


@dataclass
class Paypal:
email: str
reference: str

@staticmethod
def from_dict(data: dict) -> Paypal:
return Paypal(
email=data["email"],
reference=data["reference"],
)
6 changes: 6 additions & 0 deletions paddle_billing/Entities/Shared/SavedPaymentMethodOrigin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta


class SavedPaymentMethodOrigin(PaddleStrEnum, metaclass=PaddleStrEnumMeta):
SavedDuringPurchase: "SavedPaymentMethodOrigin" = "saved_during_purchase"
Subscription: "SavedPaymentMethodOrigin" = "subscription"
9 changes: 9 additions & 0 deletions paddle_billing/Entities/Shared/SavedPaymentMethodType.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta


class SavedPaymentMethodType(PaddleStrEnum, metaclass=PaddleStrEnumMeta):
Alipay: "SavedPaymentMethodType" = "alipay"
ApplePay: "SavedPaymentMethodType" = "apple_pay"
Card: "SavedPaymentMethodType" = "card"
GooglePay: "SavedPaymentMethodType" = "google_pay"
Paypal: "SavedPaymentMethodType" = "paypal"
3 changes: 3 additions & 0 deletions paddle_billing/Entities/Shared/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@
from paddle_billing.Entities.Shared.PaymentAttemptStatus import PaymentAttemptStatus
from paddle_billing.Entities.Shared.PaymentMethodType import PaymentMethodType
from paddle_billing.Entities.Shared.PayoutTotalsAdjustment import PayoutTotalsAdjustment
from paddle_billing.Entities.Shared.Paypal import Paypal
from paddle_billing.Entities.Shared.PriceQuantity import PriceQuantity
from paddle_billing.Entities.Shared.Proration import Proration
from paddle_billing.Entities.Shared.SavedPaymentMethodOrigin import SavedPaymentMethodOrigin
from paddle_billing.Entities.Shared.SavedPaymentMethodType import SavedPaymentMethodType
from paddle_billing.Entities.Shared.Status import Status
from paddle_billing.Entities.Shared.TransactionStatus import TransactionStatus
from paddle_billing.Entities.Shared.TaxCategory import TaxCategory
Expand Down
4 changes: 3 additions & 1 deletion paddle_billing/Notifications/Entities/Entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ def from_dict_for_event_type(data: dict, event_type: str) -> Entity | UndefinedE
def _resolve_event_class_name(event_type) -> str:
if event_type == "subscription.created":
return "SubscriptionCreated"
if event_type == "payment_method.deleted":
return "PaymentMethodDeleted"

event_entity = event_type.split(".")[0] or ""

return event_entity.lower().title()
return event_entity.lower().title().replace("_", "")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This replacement is needed to convert payment_methodPaymentMethod


def to_dict(self) -> dict:
return asdict(self)
32 changes: 32 additions & 0 deletions paddle_billing/Notifications/Entities/PaymentMethod.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime

from paddle_billing.Notifications.Entities.Entity import Entity
from paddle_billing.Notifications.Entities.Shared import (
SavedPaymentMethodOrigin,
SavedPaymentMethodType,
)


@dataclass
class PaymentMethod(Entity):
id: str
customer_id: str
address_id: str
type: SavedPaymentMethodType | None
origin: SavedPaymentMethodOrigin
saved_at: datetime
updated_at: datetime

@staticmethod
def from_dict(data: dict) -> PaymentMethod:
return PaymentMethod(
id=data["id"],
customer_id=data["customer_id"],
address_id=data["address_id"],
type=SavedPaymentMethodType(data["type"]),
origin=SavedPaymentMethodOrigin(data["origin"]),
saved_at=datetime.fromisoformat(data["saved_at"]),
updated_at=datetime.fromisoformat(data["updated_at"]),
)
35 changes: 35 additions & 0 deletions paddle_billing/Notifications/Entities/PaymentMethodDeleted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime

from paddle_billing.Notifications.Entities.Entity import Entity
from paddle_billing.Notifications.Entities.Shared import (
SavedPaymentMethodDeletionReason,
SavedPaymentMethodOrigin,
SavedPaymentMethodType,
)


@dataclass
class PaymentMethodDeleted(Entity):
id: str
customer_id: str
address_id: str
type: SavedPaymentMethodType | None
origin: SavedPaymentMethodOrigin
saved_at: datetime
updated_at: datetime
deletion_reason: SavedPaymentMethodDeletionReason
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PaymentMethodDeleted entity has additional deletion_reason property


@staticmethod
def from_dict(data: dict) -> PaymentMethodDeleted:
return PaymentMethodDeleted(
id=data["id"],
customer_id=data["customer_id"],
address_id=data["address_id"],
type=SavedPaymentMethodType(data["type"]),
origin=SavedPaymentMethodOrigin(data["origin"]),
saved_at=datetime.fromisoformat(data["saved_at"]),
updated_at=datetime.fromisoformat(data["updated_at"]),
deletion_reason=SavedPaymentMethodDeletionReason(data["deletion_reason"]),
)
15 changes: 15 additions & 0 deletions paddle_billing/Notifications/Entities/Shared/Paypal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from __future__ import annotations
from dataclasses import dataclass


@dataclass
class Paypal:
email: str
reference: str

@staticmethod
def from_dict(data: dict) -> Paypal:
return Paypal(
email=data["email"],
reference=data["reference"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta


class SavedPaymentMethodDeletionReason(PaddleStrEnum, metaclass=PaddleStrEnumMeta):
ReplacedByNewerVersion: "SavedPaymentMethodDeletionReason" = "replaced_by_newer_version"
Api: "SavedPaymentMethodDeletionReason" = "api"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta


class SavedPaymentMethodOrigin(PaddleStrEnum, metaclass=PaddleStrEnumMeta):
SavedDuringPurchase: "SavedPaymentMethodOrigin" = "saved_during_purchase"
Subscription: "SavedPaymentMethodOrigin" = "subscription"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta


class SavedPaymentMethodType(PaddleStrEnum, metaclass=PaddleStrEnumMeta):
Alipay: "SavedPaymentMethodType" = "alipay"
ApplePay: "SavedPaymentMethodType" = "apple_pay"
Card: "SavedPaymentMethodType" = "card"
GooglePay: "SavedPaymentMethodType" = "google_pay"
Paypal: "SavedPaymentMethodType" = "paypal"
6 changes: 6 additions & 0 deletions paddle_billing/Notifications/Entities/Shared/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@
from paddle_billing.Notifications.Entities.Shared.PaymentAttemptStatus import PaymentAttemptStatus
from paddle_billing.Notifications.Entities.Shared.PaymentMethodType import PaymentMethodType
from paddle_billing.Notifications.Entities.Shared.PayoutTotalsAdjustment import PayoutTotalsAdjustment
from paddle_billing.Notifications.Entities.Shared.Paypal import Paypal
from paddle_billing.Notifications.Entities.Shared.PriceQuantity import PriceQuantity
from paddle_billing.Notifications.Entities.Shared.Proration import Proration
from paddle_billing.Notifications.Entities.Shared.SavedPaymentMethodDeletionReason import (
SavedPaymentMethodDeletionReason,
)
from paddle_billing.Notifications.Entities.Shared.SavedPaymentMethodOrigin import SavedPaymentMethodOrigin
from paddle_billing.Notifications.Entities.Shared.SavedPaymentMethodType import SavedPaymentMethodType
from paddle_billing.Notifications.Entities.Shared.Status import Status
from paddle_billing.Notifications.Entities.Shared.TransactionStatus import TransactionStatus
from paddle_billing.Notifications.Entities.Shared.TaxCategory import TaxCategory
Expand Down
17 changes: 17 additions & 0 deletions paddle_billing/Notifications/Events/PaymentMethodDeleted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from datetime import datetime

from paddle_billing.Entities.Event import Event
from paddle_billing.Entities.Events import EventTypeName

from paddle_billing.Notifications.Entities.Customer import PaymentMethod


class PaymentMethodDeleted(Event):
def __init__(
self,
event_id: str,
event_type: EventTypeName,
occurred_at: datetime,
data: PaymentMethod,
):
super().__init__(event_id, event_type, occurred_at, data)
17 changes: 17 additions & 0 deletions paddle_billing/Notifications/Events/PaymentMethodSaved.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from datetime import datetime

from paddle_billing.Entities.Event import Event
from paddle_billing.Entities.Events import EventTypeName

from paddle_billing.Notifications.Entities.PaymentMethod import PaymentMethod


class PaymentMethodSaved(Event):
def __init__(
self,
event_id: str,
event_type: EventTypeName,
occurred_at: datetime,
data: PaymentMethod,
):
super().__init__(event_id, event_type, occurred_at, data)
13 changes: 12 additions & 1 deletion paddle_billing/Resources/Customers/CustomersClient.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from paddle_billing.ResponseParser import ResponseParser

from paddle_billing.Entities.Collections import Paginator, CreditBalanceCollection, CustomerCollection
from paddle_billing.Entities.Collections import (
Paginator,
CreditBalanceCollection,
CustomerCollection,
)
from paddle_billing.Entities.Customer import Customer
from paddle_billing.Entities.CustomerAuthToken import CustomerAuthToken
from paddle_billing.Entities.Shared import Status

from paddle_billing.Resources.Customers.Operations import (
Expand Down Expand Up @@ -62,3 +67,9 @@ def credit_balances(self, customer_id: str, operation: ListCreditBalances = None
parser = ResponseParser(self.response)

return CreditBalanceCollection.from_list(parser.get_data())

def create_auth_token(self, customer_id: str) -> CustomerAuthToken:
self.response = self.client.post_raw(f"/customers/{customer_id}/auth-token")
parser = ResponseParser(self.response)

return CustomerAuthToken.from_dict(parser.get_data())
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from paddle_billing.HasParameters import HasParameters
from paddle_billing.Exceptions.SdkExceptions.InvalidArgumentException import InvalidArgumentException
from paddle_billing.Resources.Shared.Operations import Pager


class ListPaymentMethods(HasParameters):
def __init__(
self,
pager: Pager | None = None,
address_ids: list[str] = None,
supports_checkout: bool = None,
):
self.pager = pager
self.address_ids = address_ids
self.supports_checkout = supports_checkout

# Validation
if address_ids is not None:
invalid_items = [id for id in address_ids if not isinstance(id, str)]
if invalid_items:
raise InvalidArgumentException.array_contains_invalid_types("ids", str.__name__, invalid_items)

def get_parameters(self) -> dict:
parameters = {}
if self.pager:
parameters.update(self.pager.get_parameters())
if self.address_ids:
parameters["address_id"] = ",".join(self.address_ids)
if self.supports_checkout is not None:
parameters["supports_checkout"] = "true" if self.supports_checkout else "false"

return parameters
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from paddle_billing.Resources.PaymentMethods.Operations.ListPaymentMethods import ListPaymentMethods
Loading