certifieddata-agent-commerce 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ node_modules/
2
+ dist/
3
+ .turbo/
4
+ *.tsbuildinfo
5
+ .env
6
+ .env.local
7
+ .env.*.local
8
+ *.log
9
+ .DS_Store
10
+ coverage/
11
+ .nyc_output/
12
+ __pycache__/
13
+ *.pyc
14
+ *.pyo
15
+ .pytest_cache/
16
+ .venv/
17
+ dist-info/
18
+ *.egg-info/
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: certifieddata-agent-commerce
3
+ Version: 0.1.0
4
+ Summary: CertifiedData Agent Commerce Python SDK — provenance-aware payments for AI artifacts
5
+ Project-URL: Homepage, https://certifieddata.io/agent-commerce
6
+ Project-URL: Repository, https://github.com/certifieddata/certifieddata-agent-commerce-public
7
+ Project-URL: Documentation, https://certifieddata.io/agent-commerce/docs
8
+ Project-URL: Bug Tracker, https://github.com/certifieddata/certifieddata-agent-commerce-public/issues
9
+ License: Apache-2.0
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Office/Business :: Financial
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.9
21
+ Requires-Dist: httpx>=0.27.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: mypy>=1.8; extra == 'dev'
24
+ Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
25
+ Requires-Dist: pytest>=8.0; extra == 'dev'
26
+ Requires-Dist: ruff>=0.3; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # certifieddata-agent-commerce
30
+
31
+ Python SDK for [CertifiedData Agent Commerce](https://certifieddata.io/agent-commerce) — provenance-aware, policy-governed payments for AI agents and artifacts.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install certifieddata-agent-commerce
37
+ ```
38
+
39
+ > **Note:** Install name is `certifieddata-agent-commerce`. Import name is `certifieddata_agent_commerce` (underscore, as required by Python naming conventions).
40
+
41
+ ## Quick start
42
+
43
+ ```python
44
+ from certifieddata_agent_commerce import CertifiedDataAgentCommerceClient
45
+
46
+ # Reads CDAC_API_KEY and CDAC_BASE_URL from environment automatically
47
+ client = CertifiedDataAgentCommerceClient()
48
+
49
+ # Phase 1 — agent declares intent
50
+ tx = client.transactions.create(
51
+ amount=2500, # cents ($25.00)
52
+ currency="usd",
53
+ rail="stripe",
54
+ )
55
+
56
+ # Phase 2 — attach provenance (binds AI decision record to payment)
57
+ client.transactions.attach_links(
58
+ tx["id"],
59
+ decision_record_id="dec_abc123",
60
+ )
61
+
62
+ # Phase 3+4 — capture: policy eval → settle → inline signed receipt
63
+ capture = client.transactions.capture(tx["id"])
64
+ receipt = capture["receipt"]
65
+
66
+ print(receipt["receipt_id"])
67
+ print(receipt["decision_record_id"])
68
+ print(receipt["sha256_hash"])
69
+ print(receipt["ed25519_sig"])
70
+ ```
71
+
72
+ ## Configure
73
+
74
+ ```bash
75
+ export CDAC_API_KEY=cdp_test_xxx
76
+ export CDAC_BASE_URL=https://sandbox.certifieddata.io # omit for live
77
+ ```
78
+
79
+ | Environment | Key prefix | Base URL |
80
+ |-------------|--------------|------------------------------------|
81
+ | Sandbox | `cdp_test_` | `https://sandbox.certifieddata.io` |
82
+ | Live | `cdp_live_` | `https://certifieddata.io` |
83
+
84
+ ## Verify receipts publicly
85
+
86
+ Verification requires no API key:
87
+
88
+ ```bash
89
+ curl https://certifieddata.io/api/payments/verify/<receipt_id>
90
+ ```
91
+
92
+ Returns:
93
+
94
+ ```json
95
+ {
96
+ "valid": true,
97
+ "hashValid": true,
98
+ "signatureValid": true,
99
+ "signingKeyId": "cd_root_2026",
100
+ "signatureAlg": "Ed25519"
101
+ }
102
+ ```
103
+
104
+ ## Local development (mock server)
105
+
106
+ ```bash
107
+ # Start mock server on port 3456
108
+ pip install flask
109
+ python examples/claude-demo/mock_server.py
110
+
111
+ # Run demo against mock
112
+ CDAC_API_KEY=cdp_test_any CDAC_BASE_URL=http://localhost:3456 \
113
+ python examples/claude-demo/demo.py
114
+ ```
115
+
116
+ ## Receipt shape
117
+
118
+ Every successful capture returns an inline signed receipt:
119
+
120
+ ```json
121
+ {
122
+ "receipt_id": "rcpt_...",
123
+ "transaction_id": "txn_...",
124
+ "policy_id": "pol_...",
125
+ "decision_record_id": "dec_...",
126
+ "sha256_hash": "sha256:...",
127
+ "ed25519_sig": "..."
128
+ }
129
+ ```
130
+
131
+ `decision_record_id` is the canonical provenance field linking the payment to the AI decision that triggered it.
132
+
133
+ ## Package names
134
+
135
+ | Context | Name |
136
+ |---------|------|
137
+ | `pip install` | `certifieddata-agent-commerce` |
138
+ | `import` | `certifieddata_agent_commerce` |
139
+ | Main class | `CertifiedDataAgentCommerceClient` |
140
+
141
+ Python requires hyphens in distribution names and underscores in import names — these are the same package, different naming conventions.
142
+
143
+ ## Resources
144
+
145
+ - [Agent Commerce docs](https://certifieddata.io/agent-commerce)
146
+ - [Public repo](https://github.com/certifieddata/certifieddata-agent-commerce-public)
147
+ - [OpenAPI spec](https://github.com/certifieddata/certifieddata-agent-commerce-public/blob/main/openapi/certifieddata-agent-commerce-v1.openapi.yaml)
148
+ - [Receipt verification](https://certifieddata.io/agent-commerce/payment-verification)
149
+
150
+ ## License
151
+
152
+ Apache-2.0
@@ -0,0 +1,124 @@
1
+ # certifieddata-agent-commerce
2
+
3
+ Python SDK for [CertifiedData Agent Commerce](https://certifieddata.io/agent-commerce) — provenance-aware, policy-governed payments for AI agents and artifacts.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install certifieddata-agent-commerce
9
+ ```
10
+
11
+ > **Note:** Install name is `certifieddata-agent-commerce`. Import name is `certifieddata_agent_commerce` (underscore, as required by Python naming conventions).
12
+
13
+ ## Quick start
14
+
15
+ ```python
16
+ from certifieddata_agent_commerce import CertifiedDataAgentCommerceClient
17
+
18
+ # Reads CDAC_API_KEY and CDAC_BASE_URL from environment automatically
19
+ client = CertifiedDataAgentCommerceClient()
20
+
21
+ # Phase 1 — agent declares intent
22
+ tx = client.transactions.create(
23
+ amount=2500, # cents ($25.00)
24
+ currency="usd",
25
+ rail="stripe",
26
+ )
27
+
28
+ # Phase 2 — attach provenance (binds AI decision record to payment)
29
+ client.transactions.attach_links(
30
+ tx["id"],
31
+ decision_record_id="dec_abc123",
32
+ )
33
+
34
+ # Phase 3+4 — capture: policy eval → settle → inline signed receipt
35
+ capture = client.transactions.capture(tx["id"])
36
+ receipt = capture["receipt"]
37
+
38
+ print(receipt["receipt_id"])
39
+ print(receipt["decision_record_id"])
40
+ print(receipt["sha256_hash"])
41
+ print(receipt["ed25519_sig"])
42
+ ```
43
+
44
+ ## Configure
45
+
46
+ ```bash
47
+ export CDAC_API_KEY=cdp_test_xxx
48
+ export CDAC_BASE_URL=https://sandbox.certifieddata.io # omit for live
49
+ ```
50
+
51
+ | Environment | Key prefix | Base URL |
52
+ |-------------|--------------|------------------------------------|
53
+ | Sandbox | `cdp_test_` | `https://sandbox.certifieddata.io` |
54
+ | Live | `cdp_live_` | `https://certifieddata.io` |
55
+
56
+ ## Verify receipts publicly
57
+
58
+ Verification requires no API key:
59
+
60
+ ```bash
61
+ curl https://certifieddata.io/api/payments/verify/<receipt_id>
62
+ ```
63
+
64
+ Returns:
65
+
66
+ ```json
67
+ {
68
+ "valid": true,
69
+ "hashValid": true,
70
+ "signatureValid": true,
71
+ "signingKeyId": "cd_root_2026",
72
+ "signatureAlg": "Ed25519"
73
+ }
74
+ ```
75
+
76
+ ## Local development (mock server)
77
+
78
+ ```bash
79
+ # Start mock server on port 3456
80
+ pip install flask
81
+ python examples/claude-demo/mock_server.py
82
+
83
+ # Run demo against mock
84
+ CDAC_API_KEY=cdp_test_any CDAC_BASE_URL=http://localhost:3456 \
85
+ python examples/claude-demo/demo.py
86
+ ```
87
+
88
+ ## Receipt shape
89
+
90
+ Every successful capture returns an inline signed receipt:
91
+
92
+ ```json
93
+ {
94
+ "receipt_id": "rcpt_...",
95
+ "transaction_id": "txn_...",
96
+ "policy_id": "pol_...",
97
+ "decision_record_id": "dec_...",
98
+ "sha256_hash": "sha256:...",
99
+ "ed25519_sig": "..."
100
+ }
101
+ ```
102
+
103
+ `decision_record_id` is the canonical provenance field linking the payment to the AI decision that triggered it.
104
+
105
+ ## Package names
106
+
107
+ | Context | Name |
108
+ |---------|------|
109
+ | `pip install` | `certifieddata-agent-commerce` |
110
+ | `import` | `certifieddata_agent_commerce` |
111
+ | Main class | `CertifiedDataAgentCommerceClient` |
112
+
113
+ Python requires hyphens in distribution names and underscores in import names — these are the same package, different naming conventions.
114
+
115
+ ## Resources
116
+
117
+ - [Agent Commerce docs](https://certifieddata.io/agent-commerce)
118
+ - [Public repo](https://github.com/certifieddata/certifieddata-agent-commerce-public)
119
+ - [OpenAPI spec](https://github.com/certifieddata/certifieddata-agent-commerce-public/blob/main/openapi/certifieddata-agent-commerce-v1.openapi.yaml)
120
+ - [Receipt verification](https://certifieddata.io/agent-commerce/payment-verification)
121
+
122
+ ## License
123
+
124
+ Apache-2.0
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "certifieddata-agent-commerce"
7
+ version = "0.1.0"
8
+ description = "CertifiedData Agent Commerce Python SDK — provenance-aware payments for AI artifacts"
9
+ readme = "README.md"
10
+ license = { text = "Apache-2.0" }
11
+ requires-python = ">=3.9"
12
+ dependencies = [
13
+ "httpx>=0.27.0",
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: Apache Software License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ "Topic :: Office/Business :: Financial",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://certifieddata.io/agent-commerce"
30
+ Repository = "https://github.com/certifieddata/certifieddata-agent-commerce-public"
31
+ Documentation = "https://certifieddata.io/agent-commerce/docs"
32
+ "Bug Tracker" = "https://github.com/certifieddata/certifieddata-agent-commerce-public/issues"
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["pytest>=8.0", "pytest-httpx>=0.30", "ruff>=0.3", "mypy>=1.8"]
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/certifieddata_agent_commerce"]
39
+
40
+ [tool.ruff]
41
+ line-length = 100
42
+ target-version = "py39"
43
+
44
+ [tool.mypy]
45
+ python_version = "3.9"
46
+ strict = true
@@ -0,0 +1,43 @@
1
+ """
2
+ CertifiedData Agent Commerce Python SDK
3
+
4
+ Provenance-aware payments and settlement for AI artifact commerce.
5
+
6
+ Usage::
7
+
8
+ from certifieddata_agent_commerce import CertifiedDataAgentCommerceClient
9
+
10
+ client = CertifiedDataAgentCommerceClient(api_key="cdp_test_...")
11
+
12
+ payee = client.payees.create(
13
+ entity_type="company",
14
+ legal_name="Atlas Synthetic Labs, Inc.",
15
+ idempotency_key="create-payee-001",
16
+ )
17
+
18
+ See https://github.com/certifieddata/certifieddata-agent-commerce-public for full documentation.
19
+ """
20
+
21
+ from .client import CertifiedDataAgentCommerceClient
22
+ from .errors import (
23
+ CDACError,
24
+ CDACAuthError,
25
+ CDACValidationError,
26
+ CDACNotFoundError,
27
+ CDACConflictError,
28
+ CDACRateLimitError,
29
+ )
30
+ from .webhooks import verify_webhook_signature
31
+
32
+ __all__ = [
33
+ "CertifiedDataAgentCommerceClient",
34
+ "CDACError",
35
+ "CDACAuthError",
36
+ "CDACValidationError",
37
+ "CDACNotFoundError",
38
+ "CDACConflictError",
39
+ "CDACRateLimitError",
40
+ "verify_webhook_signature",
41
+ ]
42
+
43
+ __version__ = "0.1.0"
@@ -0,0 +1,72 @@
1
+ """Internal HTTP client wrapper using httpx."""
2
+
3
+ from typing import Any, Optional
4
+ import httpx
5
+ from .errors import _raise_for_status
6
+
7
+
8
+ class HttpClient:
9
+ def __init__(
10
+ self,
11
+ api_key: str,
12
+ base_url: str = "https://api.certifieddata.io",
13
+ api_version: str = "2025-01-01",
14
+ timeout: float = 30.0,
15
+ ) -> None:
16
+ self._base_url = base_url.rstrip("/")
17
+ self._client = httpx.Client(
18
+ base_url=self._base_url,
19
+ headers={
20
+ "Authorization": f"Bearer {api_key}",
21
+ "CDAC-API-Version": api_version,
22
+ "Content-Type": "application/json",
23
+ "User-Agent": "certifieddata-agent-commerce-python/0.1.0",
24
+ },
25
+ timeout=timeout,
26
+ )
27
+
28
+ def request(
29
+ self,
30
+ method: str,
31
+ path: str,
32
+ *,
33
+ params: Optional[dict[str, Any]] = None,
34
+ json: Optional[Any] = None,
35
+ idempotency_key: Optional[str] = None,
36
+ ) -> Any:
37
+ headers: dict[str, str] = {}
38
+ if idempotency_key:
39
+ headers["Idempotency-Key"] = idempotency_key
40
+
41
+ # Remove None values from query params
42
+ if params:
43
+ params = {k: v for k, v in params.items() if v is not None}
44
+
45
+ response = self._client.request(
46
+ method,
47
+ path,
48
+ params=params or None,
49
+ json=json,
50
+ headers=headers,
51
+ )
52
+
53
+ body: Any = None
54
+ if response.content:
55
+ try:
56
+ body = response.json()
57
+ except Exception:
58
+ body = {"raw": response.text}
59
+
60
+ if not response.is_success:
61
+ _raise_for_status(response.status_code, body)
62
+
63
+ return body
64
+
65
+ def close(self) -> None:
66
+ self._client.close()
67
+
68
+ def __enter__(self) -> "HttpClient":
69
+ return self
70
+
71
+ def __exit__(self, *_: Any) -> None:
72
+ self.close()
@@ -0,0 +1,90 @@
1
+ """CertifiedData Agent Commerce Python SDK — main client."""
2
+
3
+ import os
4
+ from typing import Optional
5
+
6
+ from ._http import HttpClient
7
+ from .resources.payees import PayeesResource
8
+ from .resources.payment_intents import PaymentIntentsResource
9
+ from .resources.transactions import TransactionsResource
10
+ from .resources.settlements import SettlementsResource
11
+ from .resources.refunds import RefundsResource
12
+ from .resources.events import EventsResource
13
+ from .webhooks import verify_webhook_signature as _verify_webhook_signature
14
+
15
+
16
+ class CertifiedDataAgentCommerceClient:
17
+ """
18
+ CertifiedData Agent Commerce API client.
19
+
20
+ Usage::
21
+
22
+ from certifieddata_agent_commerce import CertifiedDataAgentCommerceClient
23
+
24
+ # api_key reads CDAC_API_KEY, base_url reads CDAC_BASE_URL
25
+ client = CertifiedDataAgentCommerceClient()
26
+
27
+ # or pass explicitly:
28
+ client = CertifiedDataAgentCommerceClient(
29
+ api_key="cdp_test_...",
30
+ base_url="https://sandbox.certifieddata.io",
31
+ )
32
+
33
+ Use ``cdp_test_`` keys for sandbox, ``cdp_live_`` keys for production.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ *,
39
+ api_key: Optional[str] = None,
40
+ base_url: Optional[str] = None,
41
+ api_version: str = "2025-01-01",
42
+ timeout: float = 30.0,
43
+ ) -> None:
44
+ resolved_key = api_key or os.environ.get("CDAC_API_KEY")
45
+ if not resolved_key:
46
+ raise ValueError("api_key is required. Pass it directly or set CDAC_API_KEY.")
47
+ resolved_url = base_url or os.environ.get("CDAC_BASE_URL") or "https://certifieddata.io"
48
+ self._http = HttpClient(
49
+ api_key=resolved_key,
50
+ base_url=resolved_url,
51
+ api_version=api_version,
52
+ timeout=timeout,
53
+ )
54
+ self.payees = PayeesResource(self._http)
55
+ self.payment_intents = PaymentIntentsResource(self._http)
56
+ self.transactions = TransactionsResource(self._http)
57
+ self.settlements = SettlementsResource(self._http)
58
+ self.refunds = RefundsResource(self._http)
59
+ self.events = EventsResource(self._http)
60
+
61
+ def verify_webhook_signature(
62
+ self,
63
+ raw_body: str | bytes,
64
+ signature_header: str,
65
+ timestamp_header: str,
66
+ secret: str,
67
+ tolerance_seconds: int = 300,
68
+ ) -> bool:
69
+ """
70
+ Verify a CDP webhook signature.
71
+
72
+ :param raw_body: Raw request body before any JSON parsing.
73
+ :param signature_header: ``CDAC-Signature`` header value.
74
+ :param timestamp_header: ``CDAC-Timestamp`` header value.
75
+ :param secret: Webhook endpoint secret.
76
+ :param tolerance_seconds: Timestamp tolerance in seconds (default 300).
77
+ """
78
+ return _verify_webhook_signature(
79
+ raw_body, signature_header, timestamp_header, secret, tolerance_seconds
80
+ )
81
+
82
+ def close(self) -> None:
83
+ """Close the underlying HTTP client."""
84
+ self._http.close()
85
+
86
+ def __enter__(self) -> "CertifiedDataAgentCommerceClient":
87
+ return self
88
+
89
+ def __exit__(self, *_: object) -> None:
90
+ self.close()
@@ -0,0 +1,80 @@
1
+ """CDP error classes. All errors inherit from CDACError."""
2
+
3
+ from typing import Any, Optional
4
+
5
+
6
+ class CDACError(Exception):
7
+ """Base class for all CertifiedData Agent Commerce SDK errors."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ *,
13
+ http_status: Optional[int] = None,
14
+ code: Optional[str] = None,
15
+ retryable: bool = False,
16
+ raw: Optional[Any] = None,
17
+ ) -> None:
18
+ super().__init__(message)
19
+ self.message = message
20
+ self.http_status = http_status
21
+ self.code = code
22
+ self.retryable = retryable
23
+ self.raw = raw
24
+
25
+ def __repr__(self) -> str:
26
+ return f"{self.__class__.__name__}(code={self.code!r}, http_status={self.http_status}, message={self.message!r})"
27
+
28
+
29
+ class CDACAuthError(CDACError):
30
+ """Authentication failed (401/403)."""
31
+
32
+
33
+ class CDACValidationError(CDACError):
34
+ """Request validation failed (400)."""
35
+
36
+ def __init__(self, message: str, *, validation_errors: Optional[list[Any]] = None, **kwargs: Any) -> None:
37
+ super().__init__(message, **kwargs)
38
+ self.validation_errors = validation_errors or []
39
+
40
+
41
+ class CDACNotFoundError(CDACError):
42
+ """Resource not found (404)."""
43
+
44
+
45
+ class CDACConflictError(CDACError):
46
+ """Conflict — state transition error or idempotency conflict (409)."""
47
+
48
+
49
+ class CDACRateLimitError(CDACError):
50
+ """Rate limit exceeded (429). Retryable."""
51
+
52
+ def __init__(self, message: str, *, retry_after: Optional[int] = None, **kwargs: Any) -> None:
53
+ super().__init__(message, retryable=True, **kwargs)
54
+ self.retry_after = retry_after
55
+
56
+
57
+ def _raise_for_status(http_status: int, body: Any) -> None:
58
+ """Raise an appropriate CDACError from an HTTP status and error body."""
59
+ error = body.get("error", {}) if isinstance(body, dict) else {}
60
+ code = error.get("code", "unknown")
61
+ message = error.get("message", "An unexpected error occurred.")
62
+ retryable = error.get("retryable", False)
63
+
64
+ if http_status == 401 or http_status == 403:
65
+ raise CDACAuthError(message, http_status=http_status, code=code, raw=body)
66
+ if http_status == 400:
67
+ raise CDACValidationError(
68
+ message,
69
+ http_status=http_status,
70
+ code=code,
71
+ raw=body,
72
+ validation_errors=error.get("errors", []),
73
+ )
74
+ if http_status == 404:
75
+ raise CDACNotFoundError(message, http_status=http_status, code=code, raw=body)
76
+ if http_status == 409:
77
+ raise CDACConflictError(message, http_status=http_status, code=code, raw=body)
78
+ if http_status == 429:
79
+ raise CDACRateLimitError(message, http_status=http_status, code=code, raw=body)
80
+ raise CDACError(message, http_status=http_status, code=code, retryable=retryable, raw=body)
@@ -0,0 +1,29 @@
1
+ """Events resource."""
2
+
3
+ from typing import Any, Optional
4
+ from .._http import HttpClient
5
+
6
+
7
+ class EventsResource:
8
+ def __init__(self, http: HttpClient) -> None:
9
+ self._http = http
10
+
11
+ def get(self, event_id: str) -> dict[str, Any]:
12
+ return self._http.request("GET", f"/v1/events/{event_id}")
13
+
14
+ def list(
15
+ self,
16
+ *,
17
+ event_type: Optional[str] = None,
18
+ limit: Optional[int] = None,
19
+ starting_after: Optional[str] = None,
20
+ ) -> dict[str, Any]:
21
+ return self._http.request(
22
+ "GET",
23
+ "/v1/events",
24
+ params={
25
+ "event_type": event_type,
26
+ "limit": limit,
27
+ "starting_after": starting_after,
28
+ },
29
+ )
@@ -0,0 +1,136 @@
1
+ """Payees resource."""
2
+
3
+ from typing import Any, Optional
4
+ from .._http import HttpClient
5
+
6
+
7
+ class PayeesResource:
8
+ def __init__(self, http: HttpClient) -> None:
9
+ self._http = http
10
+
11
+ def create(
12
+ self,
13
+ *,
14
+ entity_type: str,
15
+ legal_name: Optional[str] = None,
16
+ email: Optional[str] = None,
17
+ default_payout_method: Optional[str] = None,
18
+ metadata: Optional[dict[str, str]] = None,
19
+ idempotency_key: Optional[str] = None,
20
+ ) -> dict[str, Any]:
21
+ return self._http.request(
22
+ "POST",
23
+ "/v1/payees",
24
+ json={
25
+ "entity_type": entity_type,
26
+ "legal_name": legal_name,
27
+ "email": email,
28
+ "default_payout_method": default_payout_method,
29
+ "metadata": metadata,
30
+ },
31
+ idempotency_key=idempotency_key,
32
+ )
33
+
34
+ def get(self, payee_id: str) -> dict[str, Any]:
35
+ return self._http.request("GET", f"/v1/payees/{payee_id}")
36
+
37
+ def list(
38
+ self,
39
+ *,
40
+ limit: Optional[int] = None,
41
+ starting_after: Optional[str] = None,
42
+ ) -> dict[str, Any]:
43
+ return self._http.request(
44
+ "GET",
45
+ "/v1/payees",
46
+ params={"limit": limit, "starting_after": starting_after},
47
+ )
48
+
49
+ def update(
50
+ self,
51
+ payee_id: str,
52
+ *,
53
+ legal_name: Optional[str] = None,
54
+ email: Optional[str] = None,
55
+ default_payout_method: Optional[str] = None,
56
+ metadata: Optional[dict[str, str]] = None,
57
+ idempotency_key: Optional[str] = None,
58
+ ) -> dict[str, Any]:
59
+ body = {}
60
+ if legal_name is not None:
61
+ body["legal_name"] = legal_name
62
+ if email is not None:
63
+ body["email"] = email
64
+ if default_payout_method is not None:
65
+ body["default_payout_method"] = default_payout_method
66
+ if metadata is not None:
67
+ body["metadata"] = metadata
68
+ return self._http.request(
69
+ "PATCH",
70
+ f"/v1/payees/{payee_id}",
71
+ json=body,
72
+ idempotency_key=idempotency_key,
73
+ )
74
+
75
+ def create_alias(
76
+ self,
77
+ payee_id: str,
78
+ *,
79
+ external_system: str,
80
+ external_ref: str,
81
+ idempotency_key: Optional[str] = None,
82
+ ) -> dict[str, Any]:
83
+ return self._http.request(
84
+ "POST",
85
+ f"/v1/payees/{payee_id}/aliases",
86
+ json={"external_system": external_system, "external_ref": external_ref},
87
+ idempotency_key=idempotency_key,
88
+ )
89
+
90
+ def list_aliases(
91
+ self,
92
+ payee_id: str,
93
+ *,
94
+ limit: Optional[int] = None,
95
+ starting_after: Optional[str] = None,
96
+ ) -> dict[str, Any]:
97
+ return self._http.request(
98
+ "GET",
99
+ f"/v1/payees/{payee_id}/aliases",
100
+ params={"limit": limit, "starting_after": starting_after},
101
+ )
102
+
103
+ def create_payout_destination(
104
+ self,
105
+ payee_id: str,
106
+ *,
107
+ rail_type: str,
108
+ is_default: bool = False,
109
+ processor_ref: Optional[str] = None,
110
+ metadata: Optional[dict[str, str]] = None,
111
+ idempotency_key: Optional[str] = None,
112
+ ) -> dict[str, Any]:
113
+ return self._http.request(
114
+ "POST",
115
+ f"/v1/payees/{payee_id}/payout-destinations",
116
+ json={
117
+ "rail_type": rail_type,
118
+ "is_default": is_default,
119
+ "processor_ref": processor_ref,
120
+ "metadata": metadata,
121
+ },
122
+ idempotency_key=idempotency_key,
123
+ )
124
+
125
+ def list_payout_destinations(
126
+ self,
127
+ payee_id: str,
128
+ *,
129
+ limit: Optional[int] = None,
130
+ starting_after: Optional[str] = None,
131
+ ) -> dict[str, Any]:
132
+ return self._http.request(
133
+ "GET",
134
+ f"/v1/payees/{payee_id}/payout-destinations",
135
+ params={"limit": limit, "starting_after": starting_after},
136
+ )
@@ -0,0 +1,75 @@
1
+ """Payment intents resource."""
2
+
3
+ from typing import Any, Optional
4
+ from .._http import HttpClient
5
+
6
+
7
+ class PaymentIntentsResource:
8
+ def __init__(self, http: HttpClient) -> None:
9
+ self._http = http
10
+
11
+ def create(
12
+ self,
13
+ *,
14
+ amount: int,
15
+ currency: str,
16
+ rail: str,
17
+ customer_id: Optional[str] = None,
18
+ payee_id: Optional[str] = None,
19
+ description: Optional[str] = None,
20
+ metadata: Optional[dict[str, str]] = None,
21
+ idempotency_key: Optional[str] = None,
22
+ ) -> dict[str, Any]:
23
+ return self._http.request(
24
+ "POST",
25
+ "/v1/payment-intents",
26
+ json={
27
+ "amount": amount,
28
+ "currency": currency,
29
+ "rail": rail,
30
+ "customer_id": customer_id,
31
+ "payee_id": payee_id,
32
+ "description": description,
33
+ "metadata": metadata,
34
+ },
35
+ idempotency_key=idempotency_key,
36
+ )
37
+
38
+ def get(self, payment_intent_id: str) -> dict[str, Any]:
39
+ return self._http.request("GET", f"/v1/payment-intents/{payment_intent_id}")
40
+
41
+ def list(
42
+ self,
43
+ *,
44
+ limit: Optional[int] = None,
45
+ starting_after: Optional[str] = None,
46
+ ) -> dict[str, Any]:
47
+ return self._http.request(
48
+ "GET",
49
+ "/v1/payment-intents",
50
+ params={"limit": limit, "starting_after": starting_after},
51
+ )
52
+
53
+ def confirm(
54
+ self,
55
+ payment_intent_id: str,
56
+ *,
57
+ idempotency_key: Optional[str] = None,
58
+ ) -> dict[str, Any]:
59
+ return self._http.request(
60
+ "POST",
61
+ f"/v1/payment-intents/{payment_intent_id}/confirm",
62
+ idempotency_key=idempotency_key,
63
+ )
64
+
65
+ def cancel(
66
+ self,
67
+ payment_intent_id: str,
68
+ *,
69
+ idempotency_key: Optional[str] = None,
70
+ ) -> dict[str, Any]:
71
+ return self._http.request(
72
+ "POST",
73
+ f"/v1/payment-intents/{payment_intent_id}/cancel",
74
+ idempotency_key=idempotency_key,
75
+ )
@@ -0,0 +1,45 @@
1
+ """Refunds resource."""
2
+
3
+ from typing import Any, Optional
4
+ from .._http import HttpClient
5
+
6
+
7
+ class RefundsResource:
8
+ def __init__(self, http: HttpClient) -> None:
9
+ self._http = http
10
+
11
+ def create(
12
+ self,
13
+ *,
14
+ transaction_id: str,
15
+ amount: int,
16
+ reason: Optional[str] = None,
17
+ metadata: Optional[dict[str, str]] = None,
18
+ idempotency_key: Optional[str] = None,
19
+ ) -> dict[str, Any]:
20
+ return self._http.request(
21
+ "POST",
22
+ "/v1/refunds",
23
+ json={
24
+ "transaction_id": transaction_id,
25
+ "amount": amount,
26
+ "reason": reason,
27
+ "metadata": metadata,
28
+ },
29
+ idempotency_key=idempotency_key,
30
+ )
31
+
32
+ def get(self, refund_id: str) -> dict[str, Any]:
33
+ return self._http.request("GET", f"/v1/refunds/{refund_id}")
34
+
35
+ def list(
36
+ self,
37
+ *,
38
+ limit: Optional[int] = None,
39
+ starting_after: Optional[str] = None,
40
+ ) -> dict[str, Any]:
41
+ return self._http.request(
42
+ "GET",
43
+ "/v1/refunds",
44
+ params={"limit": limit, "starting_after": starting_after},
45
+ )
@@ -0,0 +1,73 @@
1
+ """Settlements resource."""
2
+
3
+ from typing import Any, Optional
4
+ from .._http import HttpClient
5
+
6
+
7
+ class SettlementsResource:
8
+ def __init__(self, http: HttpClient) -> None:
9
+ self._http = http
10
+
11
+ def create(
12
+ self,
13
+ *,
14
+ payee_id: str,
15
+ amount: int,
16
+ currency: str,
17
+ transaction_ids: list[str],
18
+ destination_id: Optional[str] = None,
19
+ metadata: Optional[dict[str, str]] = None,
20
+ idempotency_key: Optional[str] = None,
21
+ ) -> dict[str, Any]:
22
+ return self._http.request(
23
+ "POST",
24
+ "/v1/settlements",
25
+ json={
26
+ "payee_id": payee_id,
27
+ "amount": amount,
28
+ "currency": currency,
29
+ "transaction_ids": transaction_ids,
30
+ "destination_id": destination_id,
31
+ "metadata": metadata,
32
+ },
33
+ idempotency_key=idempotency_key,
34
+ )
35
+
36
+ def get(self, settlement_id: str) -> dict[str, Any]:
37
+ return self._http.request("GET", f"/v1/settlements/{settlement_id}")
38
+
39
+ def list(
40
+ self,
41
+ *,
42
+ limit: Optional[int] = None,
43
+ starting_after: Optional[str] = None,
44
+ ) -> dict[str, Any]:
45
+ return self._http.request(
46
+ "GET",
47
+ "/v1/settlements",
48
+ params={"limit": limit, "starting_after": starting_after},
49
+ )
50
+
51
+ def submit(
52
+ self,
53
+ settlement_id: str,
54
+ *,
55
+ idempotency_key: Optional[str] = None,
56
+ ) -> dict[str, Any]:
57
+ return self._http.request(
58
+ "POST",
59
+ f"/v1/settlements/{settlement_id}/submit",
60
+ idempotency_key=idempotency_key,
61
+ )
62
+
63
+ def cancel(
64
+ self,
65
+ settlement_id: str,
66
+ *,
67
+ idempotency_key: Optional[str] = None,
68
+ ) -> dict[str, Any]:
69
+ return self._http.request(
70
+ "POST",
71
+ f"/v1/settlements/{settlement_id}/cancel",
72
+ idempotency_key=idempotency_key,
73
+ )
@@ -0,0 +1,110 @@
1
+ """Transactions resource."""
2
+
3
+ from typing import Any, Optional
4
+ from .._http import HttpClient
5
+
6
+
7
+ class TransactionsResource:
8
+ def __init__(self, http: HttpClient) -> None:
9
+ self._http = http
10
+
11
+ def create(
12
+ self,
13
+ *,
14
+ amount: int,
15
+ currency: str,
16
+ rail: str,
17
+ payment_intent_id: Optional[str] = None,
18
+ payee_id: Optional[str] = None,
19
+ customer_id: Optional[str] = None,
20
+ description: Optional[str] = None,
21
+ metadata: Optional[dict[str, str]] = None,
22
+ idempotency_key: Optional[str] = None,
23
+ ) -> dict[str, Any]:
24
+ return self._http.request(
25
+ "POST",
26
+ "/v1/transactions",
27
+ json={
28
+ "amount": amount,
29
+ "currency": currency,
30
+ "rail": rail,
31
+ "payment_intent_id": payment_intent_id,
32
+ "payee_id": payee_id,
33
+ "customer_id": customer_id,
34
+ "description": description,
35
+ "metadata": metadata,
36
+ },
37
+ idempotency_key=idempotency_key,
38
+ )
39
+
40
+ def get(self, transaction_id: str) -> dict[str, Any]:
41
+ return self._http.request("GET", f"/v1/transactions/{transaction_id}")
42
+
43
+ def list(
44
+ self,
45
+ *,
46
+ limit: Optional[int] = None,
47
+ starting_after: Optional[str] = None,
48
+ ) -> dict[str, Any]:
49
+ return self._http.request(
50
+ "GET",
51
+ "/v1/transactions",
52
+ params={"limit": limit, "starting_after": starting_after},
53
+ )
54
+
55
+ def attach_links(
56
+ self,
57
+ transaction_id: str,
58
+ *,
59
+ artifact_id: Optional[str] = None,
60
+ certificate_id: Optional[str] = None,
61
+ decision_record_id: Optional[str] = None,
62
+ decision_id: Optional[str] = None,
63
+ dataset_id: Optional[str] = None,
64
+ model_id: Optional[str] = None,
65
+ output_id: Optional[str] = None,
66
+ receipt_hash: Optional[str] = None,
67
+ external_reference: Optional[str] = None,
68
+ provenance_metadata: Optional[dict[str, str]] = None,
69
+ idempotency_key: Optional[str] = None,
70
+ ) -> dict[str, Any]:
71
+ return self._http.request(
72
+ "POST",
73
+ f"/v1/transactions/{transaction_id}/attach-links",
74
+ json={
75
+ "artifact_id": artifact_id,
76
+ "certificate_id": certificate_id,
77
+ "decision_record_id": decision_record_id or decision_id,
78
+ "dataset_id": dataset_id,
79
+ "model_id": model_id,
80
+ "output_id": output_id,
81
+ "receipt_hash": receipt_hash,
82
+ "external_reference": external_reference,
83
+ "provenance_metadata": provenance_metadata,
84
+ },
85
+ idempotency_key=idempotency_key,
86
+ )
87
+
88
+ def capture(
89
+ self,
90
+ transaction_id: str,
91
+ *,
92
+ idempotency_key: Optional[str] = None,
93
+ ) -> dict[str, Any]:
94
+ return self._http.request(
95
+ "POST",
96
+ f"/v1/transactions/{transaction_id}/capture",
97
+ idempotency_key=idempotency_key,
98
+ )
99
+
100
+ def cancel(
101
+ self,
102
+ transaction_id: str,
103
+ *,
104
+ idempotency_key: Optional[str] = None,
105
+ ) -> dict[str, Any]:
106
+ return self._http.request(
107
+ "POST",
108
+ f"/v1/transactions/{transaction_id}/cancel",
109
+ idempotency_key=idempotency_key,
110
+ )
@@ -0,0 +1,60 @@
1
+ """Webhook signature verification for CDP webhooks."""
2
+
3
+ import hashlib
4
+ import hmac
5
+ import time
6
+
7
+
8
+ def verify_webhook_signature(
9
+ raw_body: str | bytes,
10
+ signature_header: str,
11
+ timestamp_header: str,
12
+ secret: str,
13
+ tolerance_seconds: int = 300,
14
+ ) -> bool:
15
+ """
16
+ Verify a CDP webhook signature.
17
+
18
+ CDP signs webhooks using HMAC-SHA256 over the string:
19
+ ``{timestamp}.{raw_body}``
20
+
21
+ The ``CDAC-Signature`` header has the format::
22
+ ``t={timestamp},v1={hmac_hex}``
23
+
24
+ :param raw_body: Raw request body (str or bytes) — before any JSON parsing.
25
+ :param signature_header: Value of the ``CDAC-Signature`` header.
26
+ :param timestamp_header: Value of the ``CDAC-Timestamp`` header.
27
+ :param secret: Webhook endpoint secret.
28
+ :param tolerance_seconds: Maximum age of the webhook in seconds (default 300).
29
+ :returns: True if the signature is valid and within the timestamp tolerance.
30
+ :raises ValueError: If the signature header format is invalid.
31
+ """
32
+ # Parse timestamp
33
+ try:
34
+ ts = int(timestamp_header)
35
+ except (ValueError, TypeError) as exc:
36
+ raise ValueError(f"Invalid CDAC-Timestamp header: {timestamp_header!r}") from exc
37
+
38
+ # Check timestamp tolerance
39
+ now = int(time.time())
40
+ if abs(now - ts) > tolerance_seconds:
41
+ return False
42
+
43
+ # Extract v1 signature from header
44
+ v1_sig: str | None = None
45
+ for part in signature_header.split(","):
46
+ part = part.strip()
47
+ if part.startswith("v1="):
48
+ v1_sig = part[3:]
49
+ break
50
+
51
+ if not v1_sig:
52
+ return False
53
+
54
+ # Compute expected signature
55
+ body_bytes = raw_body.encode("utf-8") if isinstance(raw_body, str) else raw_body
56
+ signed_payload = f"{ts}.".encode("utf-8") + body_bytes
57
+ expected = hmac.new(secret.encode("utf-8"), signed_payload, hashlib.sha256).hexdigest()
58
+
59
+ # Constant-time comparison
60
+ return hmac.compare_digest(expected, v1_sig)