Skip to content

feat: Payment Identifier Extension SDK - Python#1111

Merged
CarsonRoscoe merged 23 commits intomainfrom
feat/payment-identifier-python
Feb 11, 2026
Merged

feat: Payment Identifier Extension SDK - Python#1111
CarsonRoscoe merged 23 commits intomainfrom
feat/payment-identifier-python

Conversation

@apmcdermott
Copy link
Contributor

@apmcdermott apmcdermott commented Feb 6, 2026

Description

Adds Python implementation of payment-identifier extension for idempotency keys in payment requests. Follows bazaar extension patterns and matches TypeScript implementation.

Structure

payment_identifier/
├── types.py          # Pydantic models + constants
├── utils.py          # ID generation & validation
├── schema.py         # JSON Schema definition
├── validation.py     # Validation & extraction functions
├── client.py          # Client utilities
└── server.py          # Server utilities

Key Implementation Decisions

  1. Lazy jsonschema import: Only imported when schema validation is needed, allowing module import without optional dependency
  2. Type naming: PaymentIdentifierValidationResult avoids conflict with bazaar.ValidationResult; ValidationResult alias maintained for backward compatibility
  3. Export verification: Added test to ensure all __all__ exports are importable

Files Changed

New:

  • python/x402/extensions/payment_identifier/ (6 modules)
  • python/x402/tests/unit/extensions/payment_identifier/ (6 test files)

Modified:

  • python/x402/extensions/__init__.py - Added exports
  • python/x402/tests/unit/extensions/test_exports.py - Export verification

Related

  • Spec: specs/extensions/payment_identifier.md
  • TS impl: typescript/packages/extensions/src/payment-identifier/

Checklist

  • I have formatted and linted my code
  • All new and existing tests pass
  • My commits are signed (required for merge) -- you may need to rebase if you initially pushed unsigned commits
  • I added a changelog fragment for user-facing changes (docs-only changes can skip)

- Add PaymentIdentifierInfo and PaymentIdentifierExtension Pydantic models
- Add constants: PAYMENT_IDENTIFIER, PAYMENT_ID_MIN_LENGTH, PAYMENT_ID_MAX_LENGTH, PAYMENT_ID_PATTERN
- Define PaymentIdentifierSchema type alias
- Add generate_payment_id() to create UUID v4-based payment IDs
- Add is_valid_payment_id() to validate ID format (16-128 chars, alphanumeric + hyphens/underscores)
- Add payment_identifier_schema compliant with JSON Schema Draft 2020-12
- Schema validates required boolean and optional id string field
- Add ValidationResult dataclass for validation results
- Add is_payment_identifier_extension() type guard
- Add validate_payment_identifier() for full extension validation
- Add extract_payment_identifier() to extract ID from PaymentPayload
- Add extract_and_validate_payment_identifier() combined function
- Add has_payment_identifier() to check extension presence
- Add is_payment_identifier_required() to check requirement flag
- Add validate_payment_identifier_requirement() for requirement compliance
- Add append_payment_identifier_to_extensions() to append payment ID to extensions dict
- Only appends if server declared the extension
- Generates new ID if not provided
- Add declare_payment_identifier_extension() to create extension declaration
- Add PaymentIdentifierResourceServerExtension class implementing ResourceServerExtension protocol
- Add payment_identifier_resource_server_extension singleton instance
- Export all public APIs from payment-identifier extension
- Add comprehensive usage documentation with examples for servers, clients, and idempotency
- Add payment-identifier imports and exports to main extensions __init__.py
- Export all constants, types, functions, and classes for public API
- Export payment_identifier.ValidationResult as ValidationResult (matches payment_identifier function return types)
- Export bazaar.ValidationResult as BazaarValidationResult for explicit bazaar usage
- Both ValidationResult classes are structurally equivalent (same dataclass fields)
- Fixes type mismatch where payment_identifier functions returned payment_identifier.ValidationResult but ValidationResult imported from x402.extensions was bazaar.ValidationResult
…ntIdentifierValidationResult

- Rename ValidationResult class to PaymentIdentifierValidationResult in payment_identifier/validation.py
- Update all function return types and usages throughout validation.py
- Update exports in payment_identifier/__init__.py
- Update imports/exports in extensions/__init__.py
- Keep ValidationResult from bazaar as default export for backward compatibility
- Export PaymentIdentifierValidationResult separately for explicit payment_identifier usage
- Makes naming consistent with BazaarValidationResult pattern
…ension

- Add test_types.py: Test constants and Pydantic models
- Add test_utils.py: Test ID generation and validation functions
- Add test_validation.py: Test all validation and extraction functions
- Add test_client.py: Test client-side utilities
- Add test_server.py: Test server-side utilities
- Tests cover all major functionality including edge cases and error handling
- Follows same structure as bazaar extension tests
- Fix create_extension_with_id helper to handle invalid IDs for testing
- Update test assertion to check for 'identifier' in error message
- All 67 tests now passing
- Add ValidationResult = BazaarValidationResult alias after bazaar import
- Fixes AttributeError when importing ValidationResult from x402.extensions
- ValidationResult was imported as BazaarValidationResult but exported in __all__
- Now both ValidationResult and BazaarValidationResult are available
- Add test_exports.py to verify all items in __all__ can be imported
- Tests that ValidationResult alias works correctly
- Tests that 'import *' works as expected
- This would have caught the ValidationResult AttributeError bug
@cb-heimdall
Copy link

cb-heimdall commented Feb 6, 2026

✅ Heimdall Review Status

Requirement Status More Info
Reviews 1/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@vercel
Copy link

vercel bot commented Feb 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
x402 Ready Ready Preview, Comment Feb 11, 2026 3:34pm

Request Review

@github-actions github-actions bot added sdk Changes to core v2 packages python labels Feb 6, 2026
…tion

- Remove module-level jsonschema import that raised ImportError
- Import jsonschema lazily only when schema validation is needed
- Return validation error instead of raising ImportError at module level
- Allows payment_identifier extension to be imported without jsonschema
- Users can still use non-validation functions without optional dependency
@apmcdermott apmcdermott changed the title feat: Payment identifier SDK - Python feat: Payment Identifier Extension SDK - Python Feb 6, 2026
- Remove unused variable in test_types.py
- Fix import sorting in extensions/__init__.py
- Add noqa comment for intentional E402 (import after alias)
Add client and server examples demonstrating how to use the
payment-identifier extension for idempotent payment processing.

Client example shows:
- Generating payment IDs with generate_payment_id()
- Using on_before_payment_creation hook to inject the ID
- Making duplicate requests to demonstrate cache behavior

Server example shows:
- Declaring extension support with declare_payment_identifier_extension()
- Extracting payment IDs with extract_payment_identifier()
- Caching responses after settlement for idempotency
@github-actions github-actions bot added the legacy Changes to legacy sdk or examples label Feb 9, 2026
@CarsonRoscoe
Copy link
Contributor

@apmcdermott Please add a changelog fragment
uv run towncrier create --content "Added payment-identifier extension for tracking and validating payment identifiers" 1111.bugfix.md

@CarsonRoscoe CarsonRoscoe merged commit e9d9b3a into main Feb 11, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

examples Changes to examples legacy Changes to legacy sdk or examples python sdk Changes to core v2 packages

Development

Successfully merging this pull request may close these issues.

3 participants