Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion payments_py/x402/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
from .resolve_scheme import resolve_scheme
from .facilitator import NeverminedFacilitator
from .facilitator_api import FacilitatorAPI
from .delegation_api import DelegationAPI, PaymentMethodSummary
from .delegation_api import DelegationAPI, DelegationSummary, PaymentMethodSummary
from .a2a import X402A2AUtils, X402Metadata, PaymentStatus as X402PaymentStatus
from .token import X402TokenAPI, decode_access_token

Expand Down Expand Up @@ -125,6 +125,7 @@
"decode_access_token",
# Delegation API
"DelegationAPI",
"DelegationSummary",
"PaymentMethodSummary",
# High-level facilitator
"NeverminedFacilitator",
Expand Down
71 changes: 68 additions & 3 deletions payments_py/x402/delegation_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
Delegation API for managing card-delegation payment methods.

Provides access to the user's enrolled Stripe payment methods
for use with the nvm:card-delegation x402 scheme.
and delegations for use with the nvm:card-delegation x402 scheme.
"""

import requests
from typing import List
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, ConfigDict, Field
from payments_py.common.payments_error import PaymentsError
from payments_py.common.types import PaymentOptions
Expand Down Expand Up @@ -37,8 +37,40 @@ class PaymentMethodSummary(BaseModel):
)


class DelegationSummary(BaseModel):
"""
Summary of an existing card delegation.

Attributes:
id: Delegation UUID
card_id: Associated PaymentMethod entity UUID
spending_limit_cents: Maximum spending limit in cents
spent_cents: Amount already spent in cents
duration_secs: Duration of the delegation in seconds
currency: Currency code (e.g., 'usd')
status: Delegation status (e.g., 'active', 'expired')
created_at: ISO 8601 creation timestamp
expires_at: ISO 8601 expiration timestamp
"""

id: str
card_id: Optional[str] = Field(None, alias="cardId")
spending_limit_cents: Optional[int] = Field(None, alias="spendingLimitCents")
spent_cents: Optional[int] = Field(None, alias="spentCents")
duration_secs: Optional[int] = Field(None, alias="durationSecs")
currency: Optional[str] = None
status: Optional[str] = None
created_at: Optional[str] = Field(None, alias="createdAt")
expires_at: Optional[str] = Field(None, alias="expiresAt")

model_config = ConfigDict(
populate_by_name=True,
from_attributes=True,
)


class DelegationAPI(BasePaymentsAPI):
"""API for listing enrolled payment methods for card delegation."""
"""API for managing enrolled payment methods and delegations for card delegation."""

@classmethod
def get_instance(cls, options: PaymentOptions) -> "DelegationAPI":
Expand Down Expand Up @@ -77,3 +109,36 @@ def list_payment_methods(self) -> List[PaymentMethodSummary]:
raise PaymentsError.internal(
f"Network error while listing payment methods: {str(err)}"
) from err

def list_delegations(self) -> List[DelegationSummary]:
"""
List the user's existing card delegations.

Comment on lines +113 to +116
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

list_delegations() and DelegationSummary are new behavior, but there are existing unit tests for list_payment_methods() in this repo and no corresponding coverage for delegations. Please add unit tests that mock the GET call and assert successful parsing and HTTP error handling for list_delegations().

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Returns:
A list of delegation summaries

Raises:
PaymentsError: If the request fails
"""
url = f"{self.environment.backend}/api/v1/delegation"
options = self.get_backend_http_options("GET")

try:
response = requests.get(url, **options)
response.raise_for_status()
data = response.json()
return [DelegationSummary.model_validate(d) for d in data]
except requests.HTTPError as err:
try:
error_message = response.json().get(
"message", "Failed to list delegations"
)
except Exception:
error_message = "Failed to list delegations"
raise PaymentsError.internal(
f"{error_message} (HTTP {response.status_code})"
) from err
except Exception as err:
raise PaymentsError.internal(
f"Network error while listing delegations: {str(err)}"
) from err
23 changes: 17 additions & 6 deletions payments_py/x402/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,18 +271,29 @@ class CardDelegationConfig(BaseModel):
"""
Configuration for card delegation (fiat/Stripe) payments.

To reuse an existing delegation supply ``delegation_id``.
To reuse an existing card (PaymentMethod entity) supply ``card_id``.
When creating a brand-new delegation provide ``provider_payment_method_id``,
``spending_limit_cents``, and ``duration_secs``.

Comment on lines +274 to +278
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

CardDelegationConfig now documents that certain fields are required for “brand-new” delegations, but the model allows constructing/sending an empty delegationConfig (all fields optional). This makes it easy for callers to create invalid configs that only fail at the backend. Consider adding a Pydantic model_validator to enforce valid combinations (e.g., either delegation_id is set, or card_id is set, or provider_payment_method_id+spending_limit_cents+duration_secs are set), and clarify in the docstring which additional fields are required when using card_id.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Attributes:
provider_payment_method_id: Stripe payment method ID (e.g., 'pm_...')
spending_limit_cents: Maximum spending limit in cents
duration_secs: Duration of the delegation in seconds
card_id: PaymentMethod entity UUID -- preferred way to reference an enrolled card
delegation_id: Existing delegation UUID to reuse instead of creating a new one
provider_payment_method_id: Stripe payment method ID (e.g., 'pm_...'). Required only for new delegations.
spending_limit_cents: Maximum spending limit in cents. Required only for new delegations.
duration_secs: Duration of the delegation in seconds. Required only for new delegations.
currency: Currency code (default: 'usd')
merchant_account_id: Stripe Connect merchant account ID
max_transactions: Maximum number of transactions allowed
"""

provider_payment_method_id: str = Field(alias="providerPaymentMethodId")
spending_limit_cents: int = Field(alias="spendingLimitCents")
duration_secs: int = Field(alias="durationSecs")
card_id: Optional[str] = Field(None, alias="cardId")
delegation_id: Optional[str] = Field(None, alias="delegationId")
provider_payment_method_id: Optional[str] = Field(
None, alias="providerPaymentMethodId"
)
spending_limit_cents: Optional[int] = Field(None, alias="spendingLimitCents")
duration_secs: Optional[int] = Field(None, alias="durationSecs")
currency: Optional[str] = None
merchant_account_id: Optional[str] = Field(None, alias="merchantAccountId")
max_transactions: Optional[int] = Field(None, alias="maxTransactions")
Expand Down
Loading