From 07ece2f90233e67efbcd686418075df8841c729d Mon Sep 17 00:00:00 2001 From: David Grayston Date: Thu, 15 Oct 2026 17:56:12 +0100 Subject: [PATCH 1/4] feat: Add PayPal payer details, view_subscription URLs and new enum values --- CHANGELOG.md | 9 ++++++ .../CustomerPortalSessionSubscriptionUrl.py | 2 ++ paddle_billing/Entities/Shared/ErrorCode.py | 4 +++ .../Entities/Shared/MethodDetails.py | 3 ++ .../Entities/Shared/TransactionPaypal.py | 16 ++++++++++ paddle_billing/Entities/Shared/__init__.py | 1 + .../History/SubscriptionHistoryReason.py | 1 + .../SubscriptionManagementUrls.py | 2 ++ .../Entities/Shared/ErrorCode.py | 4 +++ .../Entities/Shared/MethodDetails.py | 3 ++ .../Entities/Shared/TransactionPaypal.py | 16 ++++++++++ .../Notifications/Entities/Shared/__init__.py | 1 + .../response/full_entity_multiple.json | 6 ++-- .../response/full_entity_single.json | 3 +- .../test_CustomerPortalSessionsClient.py | 8 +++++ .../_fixtures/response/list_default.json | 18 +++++++---- .../_fixtures/response/list_default.json | 6 ++-- .../payload/transaction.completed.json | 6 ++-- .../_fixtures/payload/transaction.paid.json | 6 ++-- .../payload/transaction.past_due.json | 3 +- .../payload/transaction.payment_failed.json | 3 +- .../_fixtures/response/full_entity.json | 3 +- .../response/full_entity_with_includes.json | 3 +- .../_fixtures/response/list_default.json | 12 ++++--- .../response/preview_charge_full_entity.json | 3 +- .../response/preview_update_full_entity.json | 3 +- .../Subscriptions/test_SubscriptionsClient.py | 17 ++++++++++ .../_fixtures/response/full_entity.json | 26 +++++++++++++-- .../_fixtures/response/list_default.json | 6 ++-- .../response/list_paginated_page_one.json | 3 +- .../response/list_paginated_page_two.json | 3 +- .../Transactions/test_TransactionsClient.py | 25 +++++++++++++++ .../Notifications/test_NotificationEvent.py | 32 +++++++++++++++++++ .../entity/transaction.completed.json | 6 ++-- .../notification/entity/transaction.paid.json | 12 ++++--- .../entity/transaction.past_due.json | 3 +- .../entity/transaction.payment_failed.json | 3 +- .../entity/transaction.revised.json | 6 ++-- 38 files changed, 246 insertions(+), 41 deletions(-) create mode 100644 paddle_billing/Entities/Shared/TransactionPaypal.py create mode 100644 paddle_billing/Notifications/Entities/Shared/TransactionPaypal.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b0d71ba..23be55fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ 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 + +- Added `paypal` to transaction payment `method_details`, showing the PayPal account used for a payment attempt +- Added `view_subscription` to subscription `management_urls` and customer portal session subscription `urls` +- Added `blocked_by_policy`, `declined_not_retryable`, `preferred_network_not_supported`, and `prepaid_card_not_supported` to `ErrorCode` +- Added `eu_withdrawal` to `SubscriptionHistoryReason` + ## 1.15.0 - 2026-07-28 ### Added diff --git a/paddle_billing/Entities/CustomerPortalSessions/CustomerPortalSessionSubscriptionUrl.py b/paddle_billing/Entities/CustomerPortalSessions/CustomerPortalSessionSubscriptionUrl.py index 0492d1da..2b164d0b 100644 --- a/paddle_billing/Entities/CustomerPortalSessions/CustomerPortalSessionSubscriptionUrl.py +++ b/paddle_billing/Entities/CustomerPortalSessions/CustomerPortalSessionSubscriptionUrl.py @@ -8,6 +8,7 @@ class CustomerPortalSessionSubscriptionUrl: id: str cancel_subscription: str update_subscription_payment_method: str + view_subscription: str @staticmethod def from_dict(data: dict[str, Any]) -> CustomerPortalSessionSubscriptionUrl: @@ -15,4 +16,5 @@ def from_dict(data: dict[str, Any]) -> CustomerPortalSessionSubscriptionUrl: id=data["id"], cancel_subscription=data["cancel_subscription"], update_subscription_payment_method=data["update_subscription_payment_method"], + view_subscription=data["view_subscription"], ) diff --git a/paddle_billing/Entities/Shared/ErrorCode.py b/paddle_billing/Entities/Shared/ErrorCode.py index bd3b4a5b..f6159c3e 100644 --- a/paddle_billing/Entities/Shared/ErrorCode.py +++ b/paddle_billing/Entities/Shared/ErrorCode.py @@ -5,15 +5,19 @@ class ErrorCode(PaddleStrEnum, metaclass=PaddleStrEnumMeta): AlreadyCanceled: "ErrorCode" = "already_canceled" AlreadyRefunded: "ErrorCode" = "already_refunded" AuthenticationFailed: "ErrorCode" = "authentication_failed" + BlockedByPolicy: "ErrorCode" = "blocked_by_policy" BlockedCard: "ErrorCode" = "blocked_card" Canceled: "ErrorCode" = "canceled" Declined: "ErrorCode" = "declined" + DeclinedNotRetryable: "ErrorCode" = "declined_not_retryable" ExpiredCard: "ErrorCode" = "expired_card" Fraud: "ErrorCode" = "fraud" InvalidAmount: "ErrorCode" = "invalid_amount" InvalidPaymentDetails: "ErrorCode" = "invalid_payment_details" IssuerUnavailable: "ErrorCode" = "issuer_unavailable" NotEnoughBalance: "ErrorCode" = "not_enough_balance" + PreferredNetworkNotSupported: "ErrorCode" = "preferred_network_not_supported" + PrepaidCardNotSupported: "ErrorCode" = "prepaid_card_not_supported" PspError: "ErrorCode" = "psp_error" RedactedPaymentMethod: "ErrorCode" = "redacted_payment_method" SystemError: "ErrorCode" = "system_error" diff --git a/paddle_billing/Entities/Shared/MethodDetails.py b/paddle_billing/Entities/Shared/MethodDetails.py index 15443e9a..74fc1dd5 100644 --- a/paddle_billing/Entities/Shared/MethodDetails.py +++ b/paddle_billing/Entities/Shared/MethodDetails.py @@ -6,6 +6,7 @@ from paddle_billing.Entities.Shared.PaymentMethodType import PaymentMethodType from paddle_billing.Entities.Shared.PaymentMethodUnderlyingDetails import PaymentMethodUnderlyingDetails from paddle_billing.Entities.Shared.SouthKoreaLocalCard import SouthKoreaLocalCard +from paddle_billing.Entities.Shared.TransactionPaypal import TransactionPaypal @dataclass @@ -14,6 +15,7 @@ class MethodDetails: card: Card | None underlying_details: PaymentMethodUnderlyingDetails | None # deprecated south_korea_local_card: SouthKoreaLocalCard | None + paypal: TransactionPaypal | None @staticmethod def from_dict(data: dict[str, Any]) -> MethodDetails: @@ -30,4 +32,5 @@ def from_dict(data: dict[str, Any]) -> MethodDetails: if data.get("south_korea_local_card") else None ), + TransactionPaypal.from_dict(data["paypal"]) if data.get("paypal") else None, ) diff --git a/paddle_billing/Entities/Shared/TransactionPaypal.py b/paddle_billing/Entities/Shared/TransactionPaypal.py new file mode 100644 index 00000000..1c76306c --- /dev/null +++ b/paddle_billing/Entities/Shared/TransactionPaypal.py @@ -0,0 +1,16 @@ +from __future__ import annotations +from dataclasses import dataclass +from typing import Any + + +@dataclass +class TransactionPaypal: + email: str + reference: str | None + + @staticmethod + def from_dict(data: dict[str, Any]) -> TransactionPaypal: + return TransactionPaypal( + email=data["email"], + reference=data.get("reference"), + ) diff --git a/paddle_billing/Entities/Shared/__init__.py b/paddle_billing/Entities/Shared/__init__.py index 98645eba..3f7d5789 100644 --- a/paddle_billing/Entities/Shared/__init__.py +++ b/paddle_billing/Entities/Shared/__init__.py @@ -55,6 +55,7 @@ from paddle_billing.Entities.Shared.TimePeriod import TimePeriod from paddle_billing.Entities.Shared.Totals import Totals from paddle_billing.Entities.Shared.TransactionOrigin import TransactionOrigin +from paddle_billing.Entities.Shared.TransactionPaypal import TransactionPaypal from paddle_billing.Entities.Shared.TransactionPaymentAttempt import TransactionPaymentAttempt from paddle_billing.Entities.Shared.TransactionPayoutTotals import TransactionPayoutTotals from paddle_billing.Entities.Shared.TransactionPayoutTotalsAdjusted import TransactionPayoutTotalsAdjusted diff --git a/paddle_billing/Entities/Subscriptions/History/SubscriptionHistoryReason.py b/paddle_billing/Entities/Subscriptions/History/SubscriptionHistoryReason.py index aaf85360..3d226873 100644 --- a/paddle_billing/Entities/Subscriptions/History/SubscriptionHistoryReason.py +++ b/paddle_billing/Entities/Subscriptions/History/SubscriptionHistoryReason.py @@ -8,3 +8,4 @@ class SubscriptionHistoryReason(PaddleStrEnum, metaclass=PaddleStrEnumMeta): SellerRequest: "SubscriptionHistoryReason" = "seller_request" CustomerRequest: "SubscriptionHistoryReason" = "customer_request" Chargeback: "SubscriptionHistoryReason" = "chargeback" + EuWithdrawal: "SubscriptionHistoryReason" = "eu_withdrawal" diff --git a/paddle_billing/Entities/Subscriptions/SubscriptionManagementUrls.py b/paddle_billing/Entities/Subscriptions/SubscriptionManagementUrls.py index 04e61829..f90d6933 100644 --- a/paddle_billing/Entities/Subscriptions/SubscriptionManagementUrls.py +++ b/paddle_billing/Entities/Subscriptions/SubscriptionManagementUrls.py @@ -7,10 +7,12 @@ class SubscriptionManagementUrls: update_payment_method: str | None cancel: str + view_subscription: str @staticmethod def from_dict(data: dict[str, Any]) -> SubscriptionManagementUrls: return SubscriptionManagementUrls( update_payment_method=data.get("update_payment_method"), cancel=data["cancel"], + view_subscription=data["view_subscription"], ) diff --git a/paddle_billing/Notifications/Entities/Shared/ErrorCode.py b/paddle_billing/Notifications/Entities/Shared/ErrorCode.py index bd3b4a5b..f6159c3e 100644 --- a/paddle_billing/Notifications/Entities/Shared/ErrorCode.py +++ b/paddle_billing/Notifications/Entities/Shared/ErrorCode.py @@ -5,15 +5,19 @@ class ErrorCode(PaddleStrEnum, metaclass=PaddleStrEnumMeta): AlreadyCanceled: "ErrorCode" = "already_canceled" AlreadyRefunded: "ErrorCode" = "already_refunded" AuthenticationFailed: "ErrorCode" = "authentication_failed" + BlockedByPolicy: "ErrorCode" = "blocked_by_policy" BlockedCard: "ErrorCode" = "blocked_card" Canceled: "ErrorCode" = "canceled" Declined: "ErrorCode" = "declined" + DeclinedNotRetryable: "ErrorCode" = "declined_not_retryable" ExpiredCard: "ErrorCode" = "expired_card" Fraud: "ErrorCode" = "fraud" InvalidAmount: "ErrorCode" = "invalid_amount" InvalidPaymentDetails: "ErrorCode" = "invalid_payment_details" IssuerUnavailable: "ErrorCode" = "issuer_unavailable" NotEnoughBalance: "ErrorCode" = "not_enough_balance" + PreferredNetworkNotSupported: "ErrorCode" = "preferred_network_not_supported" + PrepaidCardNotSupported: "ErrorCode" = "prepaid_card_not_supported" PspError: "ErrorCode" = "psp_error" RedactedPaymentMethod: "ErrorCode" = "redacted_payment_method" SystemError: "ErrorCode" = "system_error" diff --git a/paddle_billing/Notifications/Entities/Shared/MethodDetails.py b/paddle_billing/Notifications/Entities/Shared/MethodDetails.py index e2bbca6d..eb137aa9 100644 --- a/paddle_billing/Notifications/Entities/Shared/MethodDetails.py +++ b/paddle_billing/Notifications/Entities/Shared/MethodDetails.py @@ -6,6 +6,7 @@ from paddle_billing.Notifications.Entities.Shared.PaymentMethodType import PaymentMethodType from paddle_billing.Notifications.Entities.Shared.PaymentMethodUnderlyingDetails import PaymentMethodUnderlyingDetails from paddle_billing.Notifications.Entities.Shared.SouthKoreaLocalCard import SouthKoreaLocalCard +from paddle_billing.Notifications.Entities.Shared.TransactionPaypal import TransactionPaypal @dataclass @@ -14,6 +15,7 @@ class MethodDetails: card: Card | None underlying_details: PaymentMethodUnderlyingDetails | None # deprecated south_korea_local_card: SouthKoreaLocalCard | None + paypal: TransactionPaypal | None @staticmethod def from_dict(data: dict[str, Any]) -> MethodDetails: @@ -30,4 +32,5 @@ def from_dict(data: dict[str, Any]) -> MethodDetails: if data.get("south_korea_local_card") else None ), + TransactionPaypal.from_dict(data["paypal"]) if data.get("paypal") else None, ) diff --git a/paddle_billing/Notifications/Entities/Shared/TransactionPaypal.py b/paddle_billing/Notifications/Entities/Shared/TransactionPaypal.py new file mode 100644 index 00000000..1c76306c --- /dev/null +++ b/paddle_billing/Notifications/Entities/Shared/TransactionPaypal.py @@ -0,0 +1,16 @@ +from __future__ import annotations +from dataclasses import dataclass +from typing import Any + + +@dataclass +class TransactionPaypal: + email: str + reference: str | None + + @staticmethod + def from_dict(data: dict[str, Any]) -> TransactionPaypal: + return TransactionPaypal( + email=data["email"], + reference=data.get("reference"), + ) diff --git a/paddle_billing/Notifications/Entities/Shared/__init__.py b/paddle_billing/Notifications/Entities/Shared/__init__.py index 23cd0a7c..f492e22d 100644 --- a/paddle_billing/Notifications/Entities/Shared/__init__.py +++ b/paddle_billing/Notifications/Entities/Shared/__init__.py @@ -47,6 +47,7 @@ from paddle_billing.Notifications.Entities.Shared.TimePeriod import TimePeriod from paddle_billing.Notifications.Entities.Shared.Totals import Totals from paddle_billing.Notifications.Entities.Shared.TransactionOrigin import TransactionOrigin +from paddle_billing.Notifications.Entities.Shared.TransactionPaypal import TransactionPaypal from paddle_billing.Notifications.Entities.Shared.TransactionPaymentAttempt import TransactionPaymentAttempt from paddle_billing.Notifications.Entities.Shared.TransactionPayoutTotals import TransactionPayoutTotals from paddle_billing.Notifications.Entities.Shared.TransactionPayoutTotalsAdjusted import TransactionPayoutTotalsAdjusted diff --git a/tests/Functional/Resources/CustomerPortalSessions/_fixtures/response/full_entity_multiple.json b/tests/Functional/Resources/CustomerPortalSessions/_fixtures/response/full_entity_multiple.json index 9168698a..23ea11cb 100644 --- a/tests/Functional/Resources/CustomerPortalSessions/_fixtures/response/full_entity_multiple.json +++ b/tests/Functional/Resources/CustomerPortalSessions/_fixtures/response/full_entity_multiple.json @@ -10,12 +10,14 @@ { "id": "sub_01h04vsc0qhwtsbsxh3422wjs4", "cancel_subscription": "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=cancel_subscription&subscription_id=sub_01h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g", - "update_subscription_payment_method": "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=update_subscription_payment_method&subscription_id=sub_01h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g" + "update_subscription_payment_method": "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=update_subscription_payment_method&subscription_id=sub_01h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g", + "view_subscription": "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=view_subscription&subscription_id=sub_01h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g" }, { "id": "sub_02h04vsc0qhwtsbsxh3422wjs4", "cancel_subscription": "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=cancel_subscription&subscription_id=sub_02h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g", - "update_subscription_payment_method": "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=update_subscription_payment_method&subscription_id=sub_02h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g" + "update_subscription_payment_method": "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=update_subscription_payment_method&subscription_id=sub_02h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g", + "view_subscription": "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=view_subscription&subscription_id=sub_02h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g" } ] }, diff --git a/tests/Functional/Resources/CustomerPortalSessions/_fixtures/response/full_entity_single.json b/tests/Functional/Resources/CustomerPortalSessions/_fixtures/response/full_entity_single.json index 5718df22..33a9b41c 100644 --- a/tests/Functional/Resources/CustomerPortalSessions/_fixtures/response/full_entity_single.json +++ b/tests/Functional/Resources/CustomerPortalSessions/_fixtures/response/full_entity_single.json @@ -10,7 +10,8 @@ { "id": "sub_01h04vsc0qhwtsbsxh3422wjs4", "cancel_subscription": "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=cancel_subscription&subscription_id=sub_01h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g", - "update_subscription_payment_method": "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=update_subscription_payment_method&subscription_id=sub_01h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g" + "update_subscription_payment_method": "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=update_subscription_payment_method&subscription_id=sub_01h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g", + "view_subscription": "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=view_subscription&subscription_id=sub_01h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g" } ] }, diff --git a/tests/Functional/Resources/CustomerPortalSessions/test_CustomerPortalSessionsClient.py b/tests/Functional/Resources/CustomerPortalSessions/test_CustomerPortalSessionsClient.py index 7b8ea7b8..4a64beda 100644 --- a/tests/Functional/Resources/CustomerPortalSessions/test_CustomerPortalSessionsClient.py +++ b/tests/Functional/Resources/CustomerPortalSessions/test_CustomerPortalSessionsClient.py @@ -129,6 +129,10 @@ def test_create_returns_expected_response( subscription1.update_subscription_payment_method == "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=update_subscription_payment_method&subscription_id=sub_01h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g" ) + assert ( + subscription1.view_subscription + == "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=view_subscription&subscription_id=sub_01h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g" + ) subscription2 = urls.subscriptions[1] assert isinstance(subscription2, CustomerPortalSessionSubscriptionUrl) @@ -141,3 +145,7 @@ def test_create_returns_expected_response( subscription2.update_subscription_payment_method == "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=update_subscription_payment_method&subscription_id=sub_02h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g" ) + assert ( + subscription2.view_subscription + == "https://customer-portal.paddle.com/cpl_01j7zbyqs3vah3aafp4jf62qaw?action=view_subscription&subscription_id=sub_02h04vsc0qhwtsbsxh3422wjs4&token=pga_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjdG1fMDFncm5uNHp0YTVhMW1mMDJqanplN3kyeXMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3Mjc2NzkyMzh9._oO12IejzdKmyKTwb7BLjmiILkx4_cSyGjXraOBUI_g" + ) diff --git a/tests/Functional/Resources/Events/_fixtures/response/list_default.json b/tests/Functional/Resources/Events/_fixtures/response/list_default.json index f5f3fc4e..9015fd83 100644 --- a/tests/Functional/Resources/Events/_fixtures/response/list_default.json +++ b/tests/Functional/Resources/Events/_fixtures/response/list_default.json @@ -539,7 +539,8 @@ "expiry_month": 1, "cardholder_name": "Michael" }, - "type": "card" + "type": "card", + "paypal": null }, "payment_attempt_id": "afa07161-601d-4a65-8c1a-6993ae1ae027", "payment_method_id": "paymtd_01hkm9xwqpbbpr1ksmvg3sx3v1", @@ -873,7 +874,8 @@ "expiry_month": 1, "cardholder_name": "Michael" }, - "type": "card" + "type": "card", + "paypal": null }, "payment_attempt_id": "afa07161-601d-4a65-8c1a-6993ae1ae027", "payment_method_id": "paymtd_01hkm9xwqpbbpr1ksmvg3sx3v1", @@ -1207,7 +1209,8 @@ "expiry_month": 1, "cardholder_name": "Michael" }, - "type": "card" + "type": "card", + "paypal": null }, "payment_attempt_id": "afa07161-601d-4a65-8c1a-6993ae1ae027", "payment_method_id": "paymtd_01hkm9xwqpbbpr1ksmvg3sx3v1", @@ -2558,7 +2561,8 @@ "expiry_month": 1, "cardholder_name": "Michael McGovern" }, - "type": "card" + "type": "card", + "paypal": null }, "payment_attempt_id": "0bec49bd-dd4d-45e3-a91e-46992b09c5e2", "payment_method_id": "paymtd_01hkm9xwqpbbpr1ksmvg3sx3v1", @@ -2578,7 +2582,8 @@ "expiry_month": 1, "cardholder_name": "Michael McGovern" }, - "type": "card" + "type": "card", + "paypal": null }, "payment_attempt_id": "0bec49bd-dd4d-45e3-a91e-46992b09c5e2", "payment_method_id": null, @@ -2927,7 +2932,8 @@ "expiry_month": 1, "cardholder_name": "Michael McGovern" }, - "type": "card" + "type": "card", + "paypal": null }, "payment_attempt_id": "0bec49bd-dd4d-45e3-a91e-46992b09c5e2", "payment_method_id": "paymtd_01hkm9xwqpbbpr1ksmvg3sx3v1", diff --git a/tests/Functional/Resources/Notifications/_fixtures/response/list_default.json b/tests/Functional/Resources/Notifications/_fixtures/response/list_default.json index 7ff04561..3c82423e 100644 --- a/tests/Functional/Resources/Notifications/_fixtures/response/list_default.json +++ b/tests/Functional/Resources/Notifications/_fixtures/response/list_default.json @@ -908,7 +908,8 @@ "expiry_month": 1, "cardholder_name": "Michael McGovern" }, - "type": "card" + "type": "card", + "paypal": null }, "payment_method_id": "paymtd_01hv8x1tpjfnttxddw73xnqx6s", "payment_attempt_id": "937640dd-e3dc-40df-a16c-bb75aafd8f71", @@ -928,7 +929,8 @@ "expiry_month": 1, "cardholder_name": "Michael McGovern" }, - "type": "card" + "type": "card", + "paypal": null }, "payment_method_id": "paymtd_01hv8wx2mka7dfsqjjsxh1ne7z", "payment_attempt_id": "8f72cfa6-26b4-4a57-91dc-8f2708f7822d", diff --git a/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.completed.json b/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.completed.json index 9fbf06ff..06dd6b4a 100644 --- a/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.completed.json +++ b/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.completed.json @@ -281,7 +281,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8x1tpjfnttxddw73xnqx6s", "payment_attempt_id": "937640dd-e3dc-40df-a16c-bb75aafd8f71", @@ -303,7 +304,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8wx2mka7dfsqjjsxh1ne7z", "payment_attempt_id": "8f72cfa6-26b4-4a57-91dc-8f2708f7822d", diff --git a/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.paid.json b/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.paid.json index a88bd851..ae234e0a 100644 --- a/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.paid.json +++ b/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.paid.json @@ -266,7 +266,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8x1tpjfnttxddw73xnqx6s", "payment_attempt_id": "937640dd-e3dc-40df-a16c-bb75aafd8f71", @@ -288,7 +289,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8wx2mka7dfsqjjsxh1ne7z", "payment_attempt_id": "8f72cfa6-26b4-4a57-91dc-8f2708f7822d", diff --git a/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.past_due.json b/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.past_due.json index 9cb2e267..ef6795e6 100644 --- a/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.past_due.json +++ b/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.past_due.json @@ -230,7 +230,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8x1tpjfnttxddw73xnqx6s", "payment_attempt_id": "ff8123f6-9cfc-4f04-9984-75e4ad04b169", diff --git a/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.payment_failed.json b/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.payment_failed.json index 4ee30aa5..c4aef315 100644 --- a/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.payment_failed.json +++ b/tests/Functional/Resources/Simulations/_fixtures/payload/transaction.payment_failed.json @@ -266,7 +266,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8wx2mka7dfsqjjsxh1ne7z", "payment_attempt_id": "8f72cfa6-26b4-4a57-91dc-8f2708f7822d", diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/response/full_entity.json b/tests/Functional/Resources/Subscriptions/_fixtures/response/full_entity.json index 5c16c2f3..416a4bad 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/response/full_entity.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/response/full_entity.json @@ -148,7 +148,8 @@ "custom_data": null, "management_urls": { "update_payment_method": "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth/update-payment-method", - "cancel": "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth/cancel" + "cancel": "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth/cancel", + "view_subscription": "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth" }, "discount": null, "import_meta": null, diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/response/full_entity_with_includes.json b/tests/Functional/Resources/Subscriptions/_fixtures/response/full_entity_with_includes.json index a3978710..bd6d353e 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/response/full_entity_with_includes.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/response/full_entity_with_includes.json @@ -254,7 +254,8 @@ "custom_data": null, "management_urls": { "update_payment_method": "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth/update-payment-method", - "cancel": "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth/cancel" + "cancel": "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth/cancel", + "view_subscription": "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth" }, "discount": null, "import_meta": null diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/response/list_default.json b/tests/Functional/Resources/Subscriptions/_fixtures/response/list_default.json index e3760958..553ba673 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/response/list_default.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/response/list_default.json @@ -98,7 +98,8 @@ "custom_data": null, "management_urls": { "update_payment_method": "https://sandbox-buyer-portal.paddle.com/subscriptions/sub_01hp4894h0dpdrfk9dx775gny7/update-payment-method", - "cancel": "https://sandbox-buyer-portal.paddle.com/subscriptions/sub_01hp4894h0dpdrfk9dx775gny7/cancel" + "cancel": "https://sandbox-buyer-portal.paddle.com/subscriptions/sub_01hp4894h0dpdrfk9dx775gny7/cancel", + "view_subscription": "https://sandbox-buyer-portal.paddle.com/subscriptions/sub_01hp4894h0dpdrfk9dx775gny7" }, "discount": null, "import_meta": null @@ -236,7 +237,8 @@ "custom_data": null, "management_urls": { "update_payment_method": "https://sandbox-buyer-portal.paddle.com/subscriptions/sub_01hp46z4qkjs68yfsnv0hs2sd2/update-payment-method", - "cancel": "https://sandbox-buyer-portal.paddle.com/subscriptions/sub_01hp46z4qkjs68yfsnv0hs2sd2/cancel" + "cancel": "https://sandbox-buyer-portal.paddle.com/subscriptions/sub_01hp46z4qkjs68yfsnv0hs2sd2/cancel", + "view_subscription": "https://sandbox-buyer-portal.paddle.com/subscriptions/sub_01hp46z4qkjs68yfsnv0hs2sd2" }, "discount": null, "import_meta": null @@ -327,7 +329,8 @@ "custom_data": null, "management_urls": { "update_payment_method": null, - "cancel": "https://sandbox-buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth/cancel" + "cancel": "https://sandbox-buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth/cancel", + "view_subscription": "https://sandbox-buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth" }, "discount": { "id": "dsc_01hv6scyf7qdnzcdq01t2y8dx4", @@ -477,7 +480,8 @@ "custom_data": null, "management_urls": { "update_payment_method": "https://buyer-portal.paddle.com/subscriptions/sub_01hn0epy6nc46wt9hw92pp2kmt/update-payment-method", - "cancel": "https://buyer-portal.paddle.com/subscriptions/sub_01hn0epy6nc46wt9hw92pp2kmt/cancel" + "cancel": "https://buyer-portal.paddle.com/subscriptions/sub_01hn0epy6nc46wt9hw92pp2kmt/cancel", + "view_subscription": "https://buyer-portal.paddle.com/subscriptions/sub_01hn0epy6nc46wt9hw92pp2kmt" }, "discount": { "id": "dsc_01hv6scyf7qdnzcdq01t2y8dx4", diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_charge_full_entity.json b/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_charge_full_entity.json index 4743da9a..4fbdb00b 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_charge_full_entity.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_charge_full_entity.json @@ -621,7 +621,8 @@ "custom_data": null, "management_urls": { "update_payment_method": "https://buyer-portal.paddle.com/subscriptions/sub_01hp46z4qkjs68yfsnv0hs2sd2/update-payment-method", - "cancel": "https://buyer-portal.paddle.com/subscriptions/sub_01hp46z4qkjs68yfsnv0hs2sd2/cancel" + "cancel": "https://buyer-portal.paddle.com/subscriptions/sub_01hp46z4qkjs68yfsnv0hs2sd2/cancel", + "view_subscription": "https://buyer-portal.paddle.com/subscriptions/sub_01hp46z4qkjs68yfsnv0hs2sd2" }, "discount": null, "update_summary": { diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_update_full_entity.json b/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_update_full_entity.json index a238b889..4c893d8e 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_update_full_entity.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_update_full_entity.json @@ -679,7 +679,8 @@ "custom_data": null, "management_urls": { "update_payment_method": "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth/update-payment-method", - "cancel": "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth/cancel" + "cancel": "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth/cancel", + "view_subscription": "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth" }, "discount": null, "update_summary": { diff --git a/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py b/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py index a20c40ff..41cd6e77 100644 --- a/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py +++ b/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py @@ -801,6 +801,23 @@ def test_get_subscription_returns_transaction_line_item_proration( assert isinstance(response, Subscription) + def test_get_subscription_returns_management_urls( + self, + test_client, + mock_requests, + ): + expected_url = f"{test_client.base_url}/subscriptions/sub_01h7zcgmdc6tmwtjehp3sh7azf" + mock_requests.get( + expected_url, status_code=200, text=ReadsFixtures.read_raw_json_fixture("response/full_entity") + ) + + response = test_client.client.subscriptions.get("sub_01h7zcgmdc6tmwtjehp3sh7azf") + + assert ( + response.management_urls.view_subscription + == "https://buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth" + ) + def test_get_subscription_returns_consent_requirements( self, test_client, diff --git a/tests/Functional/Resources/Transactions/_fixtures/response/full_entity.json b/tests/Functional/Resources/Transactions/_fixtures/response/full_entity.json index 75aab177..6b331aa2 100644 --- a/tests/Functional/Resources/Transactions/_fixtures/response/full_entity.json +++ b/tests/Functional/Resources/Transactions/_fixtures/response/full_entity.json @@ -180,7 +180,8 @@ "expiry_year": 2024, "cardholder_name": "Joe Bloggs" }, - "south_korea_local_card": null + "south_korea_local_card": null, + "paypal": null }, "created_at": "2023-08-18T21:13:07.18821Z", "captured_at": "2023-08-18T21:13:09.477933Z" @@ -201,10 +202,31 @@ "expiry_year": 2024, "cardholder_name": "Joe Bloggs" }, - "south_korea_local_card": null + "south_korea_local_card": null, + "paypal": null }, "created_at": "2023-08-18T21:13:07.18821Z", "captured_at": "2023-08-18T21:13:09.477933Z" + }, + { + "payment_attempt_id": "6a1f4c2e-9b3d-4e8a-a7c5-2d0f1b3e4c5a", + "payment_method_id": null, + "stored_payment_method_id": "7c2e9a41-3f5b-4d6e-8a1c-0b9d2e4f6a8c", + "amount": "40000", + "status": "error", + "error_code": "blocked_by_policy", + "method_details": { + "type": "paypal", + "card": null, + "underlying_details": null, + "south_korea_local_card": null, + "paypal": { + "email": "john.doe@example.com", + "reference": null + } + }, + "created_at": "2023-08-18T21:12:01.18821Z", + "captured_at": null } ], "checkout": { diff --git a/tests/Functional/Resources/Transactions/_fixtures/response/list_default.json b/tests/Functional/Resources/Transactions/_fixtures/response/list_default.json index 522d50f0..9b6b2709 100644 --- a/tests/Functional/Resources/Transactions/_fixtures/response/list_default.json +++ b/tests/Functional/Resources/Transactions/_fixtures/response/list_default.json @@ -985,7 +985,8 @@ "expiry_year": 2024, "cardholder_name": "Joe Bloggs" }, - "south_korea_local_card": null + "south_korea_local_card": null, + "paypal": null }, "created_at": "2023-08-18T21:13:07.18821Z", "captured_at": "2023-08-18T21:13:09.477933Z" @@ -1006,7 +1007,8 @@ "expiry_year": 2024, "cardholder_name": "Joe Bloggs" }, - "south_korea_local_card": null + "south_korea_local_card": null, + "paypal": null }, "created_at": "2023-08-18T21:13:07.18821Z", "captured_at": "2023-08-18T21:13:09.477933Z" diff --git a/tests/Functional/Resources/Transactions/_fixtures/response/list_paginated_page_one.json b/tests/Functional/Resources/Transactions/_fixtures/response/list_paginated_page_one.json index 07bc1df9..e1ce4c47 100644 --- a/tests/Functional/Resources/Transactions/_fixtures/response/list_paginated_page_one.json +++ b/tests/Functional/Resources/Transactions/_fixtures/response/list_paginated_page_one.json @@ -985,7 +985,8 @@ "expiry_year": 2024, "cardholder_name": "Joe Bloggs" }, - "south_korea_local_card": null + "south_korea_local_card": null, + "paypal": null }, "created_at": "2023-08-18T21:13:07.18821Z", "captured_at": "2023-08-18T21:13:09.477933Z" diff --git a/tests/Functional/Resources/Transactions/_fixtures/response/list_paginated_page_two.json b/tests/Functional/Resources/Transactions/_fixtures/response/list_paginated_page_two.json index 4af1d483..9f970a9b 100644 --- a/tests/Functional/Resources/Transactions/_fixtures/response/list_paginated_page_two.json +++ b/tests/Functional/Resources/Transactions/_fixtures/response/list_paginated_page_two.json @@ -985,7 +985,8 @@ "expiry_year": 2024, "cardholder_name": "Joe Bloggs" }, - "south_korea_local_card": null + "south_korea_local_card": null, + "paypal": null }, "created_at": "2023-08-18T21:13:07.18821Z", "captured_at": "2023-08-18T21:13:09.477933Z" diff --git a/tests/Functional/Resources/Transactions/test_TransactionsClient.py b/tests/Functional/Resources/Transactions/test_TransactionsClient.py index 0f1b81a0..86740f2b 100644 --- a/tests/Functional/Resources/Transactions/test_TransactionsClient.py +++ b/tests/Functional/Resources/Transactions/test_TransactionsClient.py @@ -18,6 +18,7 @@ CurrencyCode, CustomData, Duration, + ErrorCode, Interval, Money, PaymentMethodType, @@ -26,6 +27,7 @@ TransactionStatus, TaxMode, Disposition, + TransactionPaypal, ) from paddle_billing.Resources.Transactions.Operations import ( @@ -910,6 +912,29 @@ def test_get_transaction_with_and_without_payment_method_id( payment_without_payment_method_id = response.payments[1] assert payment_without_payment_method_id.payment_method_id is None + def test_get_transaction_returns_paypal_method_details( + self, + test_client, + mock_requests, + ): + mock_requests.get( + f"{test_client.base_url}/transactions/txn_01hen7bxc1p8ep4yk7n5jbzk9r", + status_code=200, + text=ReadsFixtures.read_raw_json_fixture("response/full_entity"), + ) + + response = test_client.client.transactions.get("txn_01hen7bxc1p8ep4yk7n5jbzk9r") + + card_payment = response.payments[0] + assert card_payment.method_details.paypal is None + + paypal_payment = response.payments[2] + assert paypal_payment.error_code == ErrorCode.BlockedByPolicy + assert paypal_payment.method_details.type == PaymentMethodType.Paypal + assert isinstance(paypal_payment.method_details.paypal, TransactionPaypal) + assert paypal_payment.method_details.paypal.email == "john.doe@example.com" + assert paypal_payment.method_details.paypal.reference is None + @mark.parametrize( "operation, expected_request_body, expected_response_status, expected_response_body, expected_url", [ diff --git a/tests/Unit/Entities/Notifications/test_NotificationEvent.py b/tests/Unit/Entities/Notifications/test_NotificationEvent.py index f54d19a0..bd69f221 100644 --- a/tests/Unit/Entities/Notifications/test_NotificationEvent.py +++ b/tests/Unit/Entities/Notifications/test_NotificationEvent.py @@ -5,8 +5,10 @@ from paddle_billing.Json import PayloadEncoder from paddle_billing.Entities.Notifications import NotificationEvent +from paddle_billing.Notifications.Entities.Shared import ErrorCode, PaymentMethodType, TransactionPaypal from paddle_billing.Notifications.Entities.Subscription import Subscription from paddle_billing.Notifications.Entities.SubscriptionCreated import SubscriptionCreated +from paddle_billing.Notifications.Entities.Transaction import Transaction from paddle_billing.Notifications.Entities.UndefinedEntity import UndefinedEntity from tests.Utils.ReadsFixture import ReadsFixtures @@ -282,3 +284,33 @@ def test_subscription_notification_event_with_discount(self): assert isinstance(notification_event.data, SubscriptionCreated) assert notification_event.data.discount.starts_at.isoformat() == "2024-04-12T10:18:47.635628+00:00" assert notification_event.data.discount.ends_at.isoformat() == "2024-05-12T10:18:47.635628+00:00" + + def test_transaction_notification_event_with_paypal_method_details(self): + data = loads(ReadsFixtures.read_raw_json_fixture("notification/entity/transaction.payment_failed")) + payment = data["payments"][0] + payment["error_code"] = "prepaid_card_not_supported" + payment["method_details"] = { + "type": "paypal", + "card": None, + "underlying_details": None, + "south_korea_local_card": None, + "paypal": {"email": "john.doe@example.com", "reference": "B-5X123456AB123456C"}, + } + + notification_event = NotificationEvent.from_dict( + { + "data": data, + "notification_id": "ntf_01h8bkrfe7w1vwf8xmytwn51e7", + "event_type": "transaction.payment_failed", + "event_id": "evt_01h8bzakzx3hm2fmen703n5q45", + "occurred_at": "2023-08-21T11:57:47.390028Z", + } + ) + + assert isinstance(notification_event.data, Transaction) + payment = notification_event.data.payments[0] + assert payment.error_code == ErrorCode.PrepaidCardNotSupported + assert payment.method_details.type == PaymentMethodType.Paypal + assert isinstance(payment.method_details.paypal, TransactionPaypal) + assert payment.method_details.paypal.email == "john.doe@example.com" + assert payment.method_details.paypal.reference == "B-5X123456AB123456C" diff --git a/tests/Unit/Entities/_fixtures/notification/entity/transaction.completed.json b/tests/Unit/Entities/_fixtures/notification/entity/transaction.completed.json index eaaad968..846a9bca 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/transaction.completed.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/transaction.completed.json @@ -281,7 +281,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8x1tpjfnttxddw73xnqx6s", "payment_attempt_id": "937640dd-e3dc-40df-a16c-bb75aafd8f71", @@ -303,7 +304,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8wx2mka7dfsqjjsxh1ne7z", "payment_attempt_id": "8f72cfa6-26b4-4a57-91dc-8f2708f7822d", diff --git a/tests/Unit/Entities/_fixtures/notification/entity/transaction.paid.json b/tests/Unit/Entities/_fixtures/notification/entity/transaction.paid.json index 4bd5d335..a8ad62d1 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/transaction.paid.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/transaction.paid.json @@ -266,7 +266,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8x1tpjfnttxddw73xnqx6s", "payment_attempt_id": "937640dd-e3dc-40df-a16c-bb75aafd8f71", @@ -288,7 +289,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8wx2mka7dfsqjjsxh1ne7z", "payment_attempt_id": "8f72cfa6-26b4-4a57-91dc-8f2708f7822d", @@ -308,7 +310,8 @@ "korea_local": { "type": "kdbbank" } - } + }, + "paypal": null }, "payment_method_id": "paymtd_01hv8wx2mka7dfsqjjsxh1ne7z", "payment_attempt_id": "8f72cfa6-26b4-4a57-91dc-8f2708f7822d", @@ -327,7 +330,8 @@ "type": "kakaobank", "last4": "4242" }, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8wx2mka7dfsqjjsxh1ne8a", "payment_attempt_id": "9a82cfa6-26b4-4a57-91dc-8f2708f7822e", diff --git a/tests/Unit/Entities/_fixtures/notification/entity/transaction.past_due.json b/tests/Unit/Entities/_fixtures/notification/entity/transaction.past_due.json index be2b13b9..eb27f0bf 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/transaction.past_due.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/transaction.past_due.json @@ -230,7 +230,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8x1tpjfnttxddw73xnqx6s", "payment_attempt_id": "ff8123f6-9cfc-4f04-9984-75e4ad04b169", diff --git a/tests/Unit/Entities/_fixtures/notification/entity/transaction.payment_failed.json b/tests/Unit/Entities/_fixtures/notification/entity/transaction.payment_failed.json index 583956e4..992af9e8 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/transaction.payment_failed.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/transaction.payment_failed.json @@ -266,7 +266,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8wx2mka7dfsqjjsxh1ne7z", "payment_attempt_id": "8f72cfa6-26b4-4a57-91dc-8f2708f7822d", diff --git a/tests/Unit/Entities/_fixtures/notification/entity/transaction.revised.json b/tests/Unit/Entities/_fixtures/notification/entity/transaction.revised.json index bcdccd22..92b61075 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/transaction.revised.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/transaction.revised.json @@ -294,7 +294,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8x1tpjfnttxddw73xnqx6s", "payment_attempt_id": "937640dd-e3dc-40df-a16c-bb75aafd8f71", @@ -316,7 +317,8 @@ }, "type": "card", "south_korea_local_card": null, - "underlying_details": null + "underlying_details": null, + "paypal": null }, "payment_method_id": "paymtd_01hv8wx2mka7dfsqjjsxh1ne7z", "payment_attempt_id": "8f72cfa6-26b4-4a57-91dc-8f2708f7822d", From b03e5a0be99a71900e54dd311969de9abaf74d88 Mon Sep 17 00:00:00 2001 From: David Grayston Date: Thu, 15 Oct 2026 18:30:45 +0100 Subject: [PATCH 2/4] feat: Add ids filter to list checkout domains and exposed_at to api_key events --- CHANGELOG.md | 2 ++ .../Notifications/Entities/ApiKey.py | 2 ++ .../Entities/Simulations/ApiKey.py | 6 +++++ .../Operations/ListCheckoutDomains.py | 5 ++++ .../test_CheckoutDomainsClient.py | 6 +++++ .../_fixtures/payload/api_key.created.json | 3 ++- .../_fixtures/payload/api_key.expired.json | 3 ++- .../_fixtures/payload/api_key.expiring.json | 3 ++- .../_fixtures/payload/api_key.revoked.json | 3 ++- .../_fixtures/payload/api_key.updated.json | 3 ++- .../Notifications/test_NotificationEvent.py | 23 +++++++++++++++++++ .../notification/entity/api_key.created.json | 3 ++- .../notification/entity/api_key.expired.json | 3 ++- .../notification/entity/api_key.expiring.json | 3 ++- .../notification/entity/api_key.revoked.json | 3 ++- .../notification/entity/api_key.updated.json | 3 ++- 16 files changed, 64 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23be55fb..4dc6651b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx - Added `view_subscription` to subscription `management_urls` and customer portal session subscription `urls` - Added `blocked_by_policy`, `declined_not_retryable`, `preferred_network_not_supported`, and `prepaid_card_not_supported` to `ErrorCode` - Added `eu_withdrawal` to `SubscriptionHistoryReason` +- Added `ids` filter to list checkout domains operation +- Added `exposed_at` to api_key events ## 1.15.0 - 2026-07-28 diff --git a/paddle_billing/Notifications/Entities/ApiKey.py b/paddle_billing/Notifications/Entities/ApiKey.py index 84b67578..ac34e268 100644 --- a/paddle_billing/Notifications/Entities/ApiKey.py +++ b/paddle_billing/Notifications/Entities/ApiKey.py @@ -20,6 +20,7 @@ class ApiKey(Entity): created_at: datetime updated_at: datetime rotatable: bool | None = None + exposed_at: datetime | None = None @staticmethod def from_dict(data: dict[str, Any]) -> ApiKey: @@ -35,4 +36,5 @@ def from_dict(data: dict[str, Any]) -> ApiKey: created_at=datetime.fromisoformat(data["created_at"]), updated_at=datetime.fromisoformat(data["updated_at"]), rotatable=data.get("rotatable"), + exposed_at=datetime.fromisoformat(data["exposed_at"]) if data.get("exposed_at") else None, ) diff --git a/paddle_billing/Notifications/Entities/Simulations/ApiKey.py b/paddle_billing/Notifications/Entities/Simulations/ApiKey.py index 6cbf7a19..79363fc8 100644 --- a/paddle_billing/Notifications/Entities/Simulations/ApiKey.py +++ b/paddle_billing/Notifications/Entities/Simulations/ApiKey.py @@ -21,6 +21,7 @@ class ApiKey(SimulationEntity): created_at: datetime | Undefined = Undefined() updated_at: datetime | Undefined = Undefined() rotatable: bool | None | Undefined = Undefined() + exposed_at: datetime | None | Undefined = Undefined() @staticmethod def from_dict(data: dict[str, Any]) -> ApiKey: @@ -48,4 +49,9 @@ def from_dict(data: dict[str, Any]) -> ApiKey: created_at=datetime.fromisoformat(data["created_at"]) if data.get("created_at") else Undefined(), updated_at=datetime.fromisoformat(data["updated_at"]) if data.get("updated_at") else Undefined(), rotatable=data.get("rotatable", Undefined()), + exposed_at=( + datetime.fromisoformat(data["exposed_at"]) + if data.get("exposed_at") + else data.get("exposed_at", Undefined()) + ), ) diff --git a/paddle_billing/Resources/CheckoutDomains/Operations/ListCheckoutDomains.py b/paddle_billing/Resources/CheckoutDomains/Operations/ListCheckoutDomains.py index 83d7a141..4cb6e21a 100644 --- a/paddle_billing/Resources/CheckoutDomains/Operations/ListCheckoutDomains.py +++ b/paddle_billing/Resources/CheckoutDomains/Operations/ListCheckoutDomains.py @@ -14,13 +14,16 @@ def __init__( pager: Pager | None = None, domain: str | None = None, statuses: list[CheckoutDomainStatus] | None = None, + ids: list[str] | None = None, ): self.pager = pager self.domain = domain self.statuses = statuses if statuses is not None else [] + self.ids = ids if ids is not None else [] # Validation for field_name, field_value, field_type in [ + ("ids", self.ids, str), ("statuses", self.statuses, CheckoutDomainStatus), ]: invalid_items = [item for item in field_value if not isinstance(item, field_type)] @@ -33,6 +36,8 @@ def get_parameters(self) -> dict[str, str]: parameters = {} if self.pager: parameters.update(self.pager.get_parameters()) + if self.ids: + parameters["id"] = ",".join(self.ids) if self.domain: parameters["domain"] = self.domain if self.statuses: diff --git a/tests/Functional/Resources/CheckoutDomains/test_CheckoutDomainsClient.py b/tests/Functional/Resources/CheckoutDomains/test_CheckoutDomainsClient.py index 74609364..7257722e 100644 --- a/tests/Functional/Resources/CheckoutDomains/test_CheckoutDomainsClient.py +++ b/tests/Functional/Resources/CheckoutDomains/test_CheckoutDomainsClient.py @@ -26,6 +26,11 @@ class TestCheckoutDomainsClient: "/checkout-domains?after=chedom_01hv8h6jj90jjz0d71m6hj4r9z&order_by=id[asc]&per_page=50", ), (ListCheckoutDomains(domain="example.com"), 200, "/checkout-domains?domain=example.com"), + ( + ListCheckoutDomains(ids=["chedom_01hv8h6jj90jjz0d71m6hj4r9z", "chedom_01hv8h6jj90jjz0d71m6hj4r9y"]), + 200, + "/checkout-domains?id=chedom_01hv8h6jj90jjz0d71m6hj4r9z,chedom_01hv8h6jj90jjz0d71m6hj4r9y", + ), ( ListCheckoutDomains(statuses=[CheckoutDomainStatus.Approved]), 200, @@ -42,6 +47,7 @@ class TestCheckoutDomainsClient: "List checkout domains with default pagination", "List paginated checkout domains after specified id", "List checkout domains filtered by domain", + "List checkout domains filtered by ids", "List checkout domains filtered by status", "List checkout domains filtered by multiple statuses", ], diff --git a/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.created.json b/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.created.json index 99dd97c2..84b94e90 100644 --- a/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.created.json +++ b/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.created.json @@ -19,5 +19,6 @@ "last_used_at": null, "created_at": "2025-03-26T06:58:38.517522Z", "updated_at": "2025-03-26T06:58:38.517522Z", - "rotatable": true + "rotatable": true, + "exposed_at": null } diff --git a/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.expired.json b/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.expired.json index 8c507183..94d843cb 100644 --- a/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.expired.json +++ b/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.expired.json @@ -15,5 +15,6 @@ "last_used_at": "2025-06-26T05:54:22.738201Z", "created_at": "2025-03-26T06:58:38.517522Z", "updated_at": "2025-03-26T06:58:38.517522Z", - "rotatable": null + "rotatable": null, + "exposed_at": null } diff --git a/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.expiring.json b/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.expiring.json index 069d5688..206e57e4 100644 --- a/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.expiring.json +++ b/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.expiring.json @@ -15,5 +15,6 @@ "last_used_at": "2025-06-21T05:46:53.238201Z", "created_at": "2025-03-26T06:58:38.517522Z", "updated_at": "2025-03-26T06:58:38.517522Z", - "rotatable": true + "rotatable": true, + "exposed_at": null } diff --git a/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.revoked.json b/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.revoked.json index c66725fb..1f25cfd0 100644 --- a/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.revoked.json +++ b/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.revoked.json @@ -15,5 +15,6 @@ "last_used_at": "2025-06-24T05:54:22.738201Z", "created_at": "2025-03-26T06:58:38.517522Z", "updated_at": "2025-06-24T12:58:38.746382Z", - "rotatable": true + "rotatable": true, + "exposed_at": "2025-04-01T06:58:38.517522Z" } diff --git a/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.updated.json b/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.updated.json index 6c8c4e48..38985857 100644 --- a/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.updated.json +++ b/tests/Functional/Resources/Simulations/_fixtures/payload/api_key.updated.json @@ -14,5 +14,6 @@ "last_used_at": "2025-04-21T11:35:42.428390Z", "created_at": "2025-03-26T06:58:38.517522Z", "updated_at": "2025-04-21T12:37:12.892738Z", - "rotatable": true + "rotatable": true, + "exposed_at": null } diff --git a/tests/Unit/Entities/Notifications/test_NotificationEvent.py b/tests/Unit/Entities/Notifications/test_NotificationEvent.py index bd69f221..a82be347 100644 --- a/tests/Unit/Entities/Notifications/test_NotificationEvent.py +++ b/tests/Unit/Entities/Notifications/test_NotificationEvent.py @@ -5,6 +5,7 @@ from paddle_billing.Json import PayloadEncoder from paddle_billing.Entities.Notifications import NotificationEvent +from paddle_billing.Notifications.Entities.ApiKey import ApiKey from paddle_billing.Notifications.Entities.Shared import ErrorCode, PaymentMethodType, TransactionPaypal from paddle_billing.Notifications.Entities.Subscription import Subscription from paddle_billing.Notifications.Entities.SubscriptionCreated import SubscriptionCreated @@ -314,3 +315,25 @@ def test_transaction_notification_event_with_paypal_method_details(self): assert isinstance(payment.method_details.paypal, TransactionPaypal) assert payment.method_details.paypal.email == "john.doe@example.com" assert payment.method_details.paypal.reference == "B-5X123456AB123456C" + + @mark.parametrize( + "event_type, expected_exposed_at", + [ + ("api_key.created", None), + ("api_key.revoked", "2025-04-01T06:58:38.517522+00:00"), + ], + ) + def test_api_key_notification_event_exposed_at(self, event_type, expected_exposed_at): + notification_event = NotificationEvent.from_dict( + { + "data": loads(ReadsFixtures.read_raw_json_fixture(f"notification/entity/{event_type}")), + "notification_id": "ntf_01h8bkrfe7w1vwf8xmytwn51e7", + "event_type": event_type, + "event_id": "evt_01h8bzakzx3hm2fmen703n5q45", + "occurred_at": "2023-08-21T11:57:47.390028Z", + } + ) + + assert isinstance(notification_event.data, ApiKey) + exposed_at = notification_event.data.exposed_at + assert (exposed_at.isoformat() if exposed_at else None) == expected_exposed_at diff --git a/tests/Unit/Entities/_fixtures/notification/entity/api_key.created.json b/tests/Unit/Entities/_fixtures/notification/entity/api_key.created.json index 99dd97c2..84b94e90 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/api_key.created.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/api_key.created.json @@ -19,5 +19,6 @@ "last_used_at": null, "created_at": "2025-03-26T06:58:38.517522Z", "updated_at": "2025-03-26T06:58:38.517522Z", - "rotatable": true + "rotatable": true, + "exposed_at": null } diff --git a/tests/Unit/Entities/_fixtures/notification/entity/api_key.expired.json b/tests/Unit/Entities/_fixtures/notification/entity/api_key.expired.json index 8c507183..94d843cb 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/api_key.expired.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/api_key.expired.json @@ -15,5 +15,6 @@ "last_used_at": "2025-06-26T05:54:22.738201Z", "created_at": "2025-03-26T06:58:38.517522Z", "updated_at": "2025-03-26T06:58:38.517522Z", - "rotatable": null + "rotatable": null, + "exposed_at": null } diff --git a/tests/Unit/Entities/_fixtures/notification/entity/api_key.expiring.json b/tests/Unit/Entities/_fixtures/notification/entity/api_key.expiring.json index 069d5688..206e57e4 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/api_key.expiring.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/api_key.expiring.json @@ -15,5 +15,6 @@ "last_used_at": "2025-06-21T05:46:53.238201Z", "created_at": "2025-03-26T06:58:38.517522Z", "updated_at": "2025-03-26T06:58:38.517522Z", - "rotatable": true + "rotatable": true, + "exposed_at": null } diff --git a/tests/Unit/Entities/_fixtures/notification/entity/api_key.revoked.json b/tests/Unit/Entities/_fixtures/notification/entity/api_key.revoked.json index c66725fb..1f25cfd0 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/api_key.revoked.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/api_key.revoked.json @@ -15,5 +15,6 @@ "last_used_at": "2025-06-24T05:54:22.738201Z", "created_at": "2025-03-26T06:58:38.517522Z", "updated_at": "2025-06-24T12:58:38.746382Z", - "rotatable": true + "rotatable": true, + "exposed_at": "2025-04-01T06:58:38.517522Z" } diff --git a/tests/Unit/Entities/_fixtures/notification/entity/api_key.updated.json b/tests/Unit/Entities/_fixtures/notification/entity/api_key.updated.json index 6c8c4e48..38985857 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/api_key.updated.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/api_key.updated.json @@ -14,5 +14,6 @@ "last_used_at": "2025-04-21T11:35:42.428390Z", "created_at": "2025-03-26T06:58:38.517522Z", "updated_at": "2025-04-21T12:37:12.892738Z", - "rotatable": true + "rotatable": true, + "exposed_at": null } From 929f5a35d2c40f6dde751d880a761a295ae24a97 Mon Sep 17 00:00:00 2001 From: David Grayston Date: Thu, 15 Oct 2026 18:32:37 +0100 Subject: [PATCH 3/4] fix: Make paypal and view_subscription optional for backwards compatibility --- .../CustomerPortalSessionSubscriptionUrl.py | 4 ++-- paddle_billing/Entities/Shared/MethodDetails.py | 2 +- .../Entities/Subscriptions/SubscriptionManagementUrls.py | 4 ++-- .../Notifications/Entities/Shared/MethodDetails.py | 2 +- .../Resources/Subscriptions/test_SubscriptionsClient.py | 8 ++++++++ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/paddle_billing/Entities/CustomerPortalSessions/CustomerPortalSessionSubscriptionUrl.py b/paddle_billing/Entities/CustomerPortalSessions/CustomerPortalSessionSubscriptionUrl.py index 2b164d0b..05d0ef98 100644 --- a/paddle_billing/Entities/CustomerPortalSessions/CustomerPortalSessionSubscriptionUrl.py +++ b/paddle_billing/Entities/CustomerPortalSessions/CustomerPortalSessionSubscriptionUrl.py @@ -8,7 +8,7 @@ class CustomerPortalSessionSubscriptionUrl: id: str cancel_subscription: str update_subscription_payment_method: str - view_subscription: str + view_subscription: str | None = None @staticmethod def from_dict(data: dict[str, Any]) -> CustomerPortalSessionSubscriptionUrl: @@ -16,5 +16,5 @@ def from_dict(data: dict[str, Any]) -> CustomerPortalSessionSubscriptionUrl: id=data["id"], cancel_subscription=data["cancel_subscription"], update_subscription_payment_method=data["update_subscription_payment_method"], - view_subscription=data["view_subscription"], + view_subscription=data.get("view_subscription"), ) diff --git a/paddle_billing/Entities/Shared/MethodDetails.py b/paddle_billing/Entities/Shared/MethodDetails.py index 74fc1dd5..17988c5d 100644 --- a/paddle_billing/Entities/Shared/MethodDetails.py +++ b/paddle_billing/Entities/Shared/MethodDetails.py @@ -15,7 +15,7 @@ class MethodDetails: card: Card | None underlying_details: PaymentMethodUnderlyingDetails | None # deprecated south_korea_local_card: SouthKoreaLocalCard | None - paypal: TransactionPaypal | None + paypal: TransactionPaypal | None = None @staticmethod def from_dict(data: dict[str, Any]) -> MethodDetails: diff --git a/paddle_billing/Entities/Subscriptions/SubscriptionManagementUrls.py b/paddle_billing/Entities/Subscriptions/SubscriptionManagementUrls.py index f90d6933..0a868f3b 100644 --- a/paddle_billing/Entities/Subscriptions/SubscriptionManagementUrls.py +++ b/paddle_billing/Entities/Subscriptions/SubscriptionManagementUrls.py @@ -7,12 +7,12 @@ class SubscriptionManagementUrls: update_payment_method: str | None cancel: str - view_subscription: str + view_subscription: str | None = None @staticmethod def from_dict(data: dict[str, Any]) -> SubscriptionManagementUrls: return SubscriptionManagementUrls( update_payment_method=data.get("update_payment_method"), cancel=data["cancel"], - view_subscription=data["view_subscription"], + view_subscription=data.get("view_subscription"), ) diff --git a/paddle_billing/Notifications/Entities/Shared/MethodDetails.py b/paddle_billing/Notifications/Entities/Shared/MethodDetails.py index eb137aa9..6de2bc44 100644 --- a/paddle_billing/Notifications/Entities/Shared/MethodDetails.py +++ b/paddle_billing/Notifications/Entities/Shared/MethodDetails.py @@ -15,7 +15,7 @@ class MethodDetails: card: Card | None underlying_details: PaymentMethodUnderlyingDetails | None # deprecated south_korea_local_card: SouthKoreaLocalCard | None - paypal: TransactionPaypal | None + paypal: TransactionPaypal | None = None @staticmethod def from_dict(data: dict[str, Any]) -> MethodDetails: diff --git a/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py b/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py index 41cd6e77..fc0ba2e5 100644 --- a/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py +++ b/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py @@ -1762,3 +1762,11 @@ def test_subscription_tolerates_missing_consent_requirements(self): subscription = Subscription.from_dict(data) assert subscription.consent_requirements == [] + + def test_subscription_tolerates_missing_view_subscription(self): + data = loads(ReadsFixtures.read_raw_json_fixture("response/full_entity"))["data"] + data["management_urls"].pop("view_subscription", None) + + subscription = Subscription.from_dict(data) + + assert subscription.management_urls.view_subscription is None From 5d191e4c414f8fbc4a3272da8cd0822d583a3139 Mon Sep 17 00:00:00 2001 From: David Grayston Date: Tue, 20 Oct 2026 01:10:49 +0100 Subject: [PATCH 4/4] docs: Add changelog links --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dc6651b..a7503db6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,8 @@ Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx ### Added -- Added `paypal` to transaction payment `method_details`, showing the PayPal account used for a payment attempt -- Added `view_subscription` to subscription `management_urls` and customer portal session subscription `urls` +- Added `paypal` to transaction payment `method_details`, showing the PayPal account used for a payment attempt. See [related changelog](https://developer.paddle.com/changelog/2026/paypal-transaction-payments?utm_source=dx&utm_medium=paddle-python-sdk) +- Added `view_subscription` to subscription `management_urls` and customer portal session subscription `urls`. See [related changelog](https://developer.paddle.com/changelog/2026/view-subscription-customer-portal-link?utm_source=dx&utm_medium=paddle-python-sdk) - Added `blocked_by_policy`, `declined_not_retryable`, `preferred_network_not_supported`, and `prepaid_card_not_supported` to `ErrorCode` - Added `eu_withdrawal` to `SubscriptionHistoryReason` - Added `ids` filter to list checkout domains operation