uvd-x402-sdk 0.2.2__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.
@@ -1,237 +1,237 @@
1
- """
2
- Django integration for x402 payments.
3
-
4
- Provides:
5
- - DjangoX402Middleware: Middleware for protecting views
6
- - django_require_payment: Decorator for view functions
7
- """
8
-
9
- from decimal import Decimal
10
- from functools import wraps
11
- from typing import Any, Callable, Dict, List, Optional, TypeVar, Union
12
- import json
13
-
14
- try:
15
- from django.http import JsonResponse, HttpRequest, HttpResponse
16
- from django.conf import settings
17
- except ImportError:
18
- raise ImportError(
19
- "Django is required for Django integration. "
20
- "Install with: pip install uvd-x402-sdk[django]"
21
- )
22
-
23
- from uvd_x402_sdk.client import X402Client
24
- from uvd_x402_sdk.config import X402Config
25
- from uvd_x402_sdk.exceptions import X402Error
26
- from uvd_x402_sdk.models import PaymentResult
27
- from uvd_x402_sdk.response import create_402_response, create_402_headers
28
-
29
- F = TypeVar("F", bound=Callable[..., Any])
30
-
31
-
32
- def _get_config_from_settings() -> X402Config:
33
- """Get x402 configuration from Django settings."""
34
- return X402Config(
35
- facilitator_url=getattr(
36
- settings,
37
- "X402_FACILITATOR_URL",
38
- "https://facilitator.ultravioletadao.xyz",
39
- ),
40
- recipient_evm=getattr(settings, "X402_RECIPIENT_EVM", ""),
41
- recipient_solana=getattr(settings, "X402_RECIPIENT_SOLANA", ""),
42
- recipient_near=getattr(settings, "X402_RECIPIENT_NEAR", ""),
43
- recipient_stellar=getattr(settings, "X402_RECIPIENT_STELLAR", ""),
44
- facilitator_solana=getattr(
45
- settings,
46
- "X402_FACILITATOR_SOLANA",
47
- "F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq",
48
- ),
49
- )
50
-
51
-
52
- class DjangoX402Middleware:
53
- """
54
- Django middleware for x402 payment protection.
55
-
56
- Configure protected paths in Django settings:
57
-
58
- # settings.py
59
- X402_RECIPIENT_EVM = "0xYourWallet..."
60
- X402_PROTECTED_PATHS = {
61
- "/api/premium/": "5.00",
62
- "/api/basic/": "1.00",
63
- }
64
-
65
- Example:
66
- # settings.py
67
- MIDDLEWARE = [
68
- ...
69
- 'uvd_x402_sdk.integrations.django_integration.DjangoX402Middleware',
70
- ]
71
- """
72
-
73
- def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None:
74
- self.get_response = get_response
75
- self._config = _get_config_from_settings()
76
- self._client = X402Client(config=self._config)
77
- self._protected_paths: Dict[str, Decimal] = {}
78
-
79
- # Load protected paths from settings
80
- paths_setting = getattr(settings, "X402_PROTECTED_PATHS", {})
81
- for path, amount in paths_setting.items():
82
- self._protected_paths[path] = Decimal(str(amount))
83
-
84
- def __call__(self, request: HttpRequest) -> HttpResponse:
85
- # Check if path is protected
86
- path = request.path
87
-
88
- # Try exact match first, then prefix match
89
- required_amount = None
90
- for protected_path, amount in self._protected_paths.items():
91
- if path == protected_path or path.startswith(protected_path):
92
- required_amount = amount
93
- break
94
-
95
- if required_amount is None:
96
- return self.get_response(request)
97
-
98
- # Get payment header (Django uses HTTP_X_PAYMENT format)
99
- payment_header = request.META.get("HTTP_X_PAYMENT")
100
-
101
- if not payment_header:
102
- response_body = create_402_response(
103
- amount_usd=required_amount,
104
- config=self._config,
105
- )
106
- response = JsonResponse(response_body, status=402)
107
- for key, value in create_402_headers().items():
108
- response[key] = value
109
- return response
110
-
111
- try:
112
- result = self._client.process_payment(
113
- x_payment_header=payment_header,
114
- expected_amount_usd=required_amount,
115
- )
116
- # Store result on request for view access
117
- request.payment_result = result # type: ignore
118
- return self.get_response(request)
119
-
120
- except X402Error as e:
121
- response = JsonResponse(e.to_dict(), status=402)
122
- for key, value in create_402_headers().items():
123
- response[key] = value
124
- return response
125
-
126
-
127
- def django_require_payment(
128
- amount_usd: Union[Decimal, float, str],
129
- config: Optional[X402Config] = None,
130
- message: Optional[str] = None,
131
- ) -> Callable[[F], F]:
132
- """
133
- Decorator that requires payment for a Django view.
134
-
135
- Args:
136
- amount_usd: Required payment amount in USD
137
- config: X402Config (uses Django settings if not provided)
138
- message: Custom message for 402 response
139
-
140
- Example:
141
- >>> from uvd_x402_sdk.integrations import django_require_payment
142
- >>>
143
- >>> @django_require_payment(amount_usd="1.00")
144
- >>> def my_view(request):
145
- ... # Access payment result via request.payment_result
146
- ... return JsonResponse({"payer": request.payment_result.payer_address})
147
- """
148
- required_amount = Decimal(str(amount_usd))
149
- _config = config or _get_config_from_settings()
150
- _client = X402Client(config=_config)
151
-
152
- def decorator(func: F) -> F:
153
- @wraps(func)
154
- def wrapper(request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
155
- # Get payment header
156
- payment_header = request.META.get("HTTP_X_PAYMENT")
157
-
158
- if not payment_header:
159
- response_body = create_402_response(
160
- amount_usd=required_amount,
161
- config=_config,
162
- message=message,
163
- )
164
- response = JsonResponse(response_body, status=402)
165
- for key, value in create_402_headers().items():
166
- response[key] = value
167
- return response
168
-
169
- try:
170
- result = _client.process_payment(
171
- x_payment_header=payment_header,
172
- expected_amount_usd=required_amount,
173
- )
174
- # Store result on request
175
- request.payment_result = result # type: ignore
176
- return func(request, *args, **kwargs)
177
-
178
- except X402Error as e:
179
- response = JsonResponse(e.to_dict(), status=402)
180
- for key, value in create_402_headers().items():
181
- response[key] = value
182
- return response
183
-
184
- return wrapper # type: ignore
185
-
186
- return decorator
187
-
188
-
189
- class X402PaymentView:
190
- """
191
- Mixin for Django class-based views requiring payment.
192
-
193
- Example:
194
- >>> from django.views import View
195
- >>> from uvd_x402_sdk.integrations.django_integration import X402PaymentView
196
- >>>
197
- >>> class PremiumAPIView(X402PaymentView, View):
198
- ... x402_amount = Decimal("5.00")
199
- ...
200
- ... def get(self, request):
201
- ... return JsonResponse({"payer": request.payment_result.payer_address})
202
- """
203
-
204
- x402_amount: Decimal = Decimal("1.00")
205
- x402_message: Optional[str] = None
206
- x402_config: Optional[X402Config] = None
207
-
208
- def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
209
- config = self.x402_config or _get_config_from_settings()
210
- client = X402Client(config=config)
211
-
212
- payment_header = request.META.get("HTTP_X_PAYMENT")
213
-
214
- if not payment_header:
215
- response_body = create_402_response(
216
- amount_usd=self.x402_amount,
217
- config=config,
218
- message=self.x402_message,
219
- )
220
- response = JsonResponse(response_body, status=402)
221
- for key, value in create_402_headers().items():
222
- response[key] = value
223
- return response
224
-
225
- try:
226
- result = client.process_payment(
227
- x_payment_header=payment_header,
228
- expected_amount_usd=self.x402_amount,
229
- )
230
- request.payment_result = result # type: ignore
231
- return super().dispatch(request, *args, **kwargs) # type: ignore
232
-
233
- except X402Error as e:
234
- response = JsonResponse(e.to_dict(), status=402)
235
- for key, value in create_402_headers().items():
236
- response[key] = value
237
- return response
1
+ """
2
+ Django integration for x402 payments.
3
+
4
+ Provides:
5
+ - DjangoX402Middleware: Middleware for protecting views
6
+ - django_require_payment: Decorator for view functions
7
+ """
8
+
9
+ from decimal import Decimal
10
+ from functools import wraps
11
+ from typing import Any, Callable, Dict, List, Optional, TypeVar, Union
12
+ import json
13
+
14
+ try:
15
+ from django.http import JsonResponse, HttpRequest, HttpResponse
16
+ from django.conf import settings
17
+ except ImportError:
18
+ raise ImportError(
19
+ "Django is required for Django integration. "
20
+ "Install with: pip install uvd-x402-sdk[django]"
21
+ )
22
+
23
+ from uvd_x402_sdk.client import X402Client
24
+ from uvd_x402_sdk.config import X402Config
25
+ from uvd_x402_sdk.exceptions import X402Error
26
+ from uvd_x402_sdk.models import PaymentResult
27
+ from uvd_x402_sdk.response import create_402_response, create_402_headers
28
+
29
+ F = TypeVar("F", bound=Callable[..., Any])
30
+
31
+
32
+ def _get_config_from_settings() -> X402Config:
33
+ """Get x402 configuration from Django settings."""
34
+ return X402Config(
35
+ facilitator_url=getattr(
36
+ settings,
37
+ "X402_FACILITATOR_URL",
38
+ "https://facilitator.ultravioletadao.xyz",
39
+ ),
40
+ recipient_evm=getattr(settings, "X402_RECIPIENT_EVM", ""),
41
+ recipient_solana=getattr(settings, "X402_RECIPIENT_SOLANA", ""),
42
+ recipient_near=getattr(settings, "X402_RECIPIENT_NEAR", ""),
43
+ recipient_stellar=getattr(settings, "X402_RECIPIENT_STELLAR", ""),
44
+ facilitator_solana=getattr(
45
+ settings,
46
+ "X402_FACILITATOR_SOLANA",
47
+ "F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq",
48
+ ),
49
+ )
50
+
51
+
52
+ class DjangoX402Middleware:
53
+ """
54
+ Django middleware for x402 payment protection.
55
+
56
+ Configure protected paths in Django settings:
57
+
58
+ # settings.py
59
+ X402_RECIPIENT_EVM = "0xYourWallet..."
60
+ X402_PROTECTED_PATHS = {
61
+ "/api/premium/": "5.00",
62
+ "/api/basic/": "1.00",
63
+ }
64
+
65
+ Example:
66
+ # settings.py
67
+ MIDDLEWARE = [
68
+ ...
69
+ 'uvd_x402_sdk.integrations.django_integration.DjangoX402Middleware',
70
+ ]
71
+ """
72
+
73
+ def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None:
74
+ self.get_response = get_response
75
+ self._config = _get_config_from_settings()
76
+ self._client = X402Client(config=self._config)
77
+ self._protected_paths: Dict[str, Decimal] = {}
78
+
79
+ # Load protected paths from settings
80
+ paths_setting = getattr(settings, "X402_PROTECTED_PATHS", {})
81
+ for path, amount in paths_setting.items():
82
+ self._protected_paths[path] = Decimal(str(amount))
83
+
84
+ def __call__(self, request: HttpRequest) -> HttpResponse:
85
+ # Check if path is protected
86
+ path = request.path
87
+
88
+ # Try exact match first, then prefix match
89
+ required_amount = None
90
+ for protected_path, amount in self._protected_paths.items():
91
+ if path == protected_path or path.startswith(protected_path):
92
+ required_amount = amount
93
+ break
94
+
95
+ if required_amount is None:
96
+ return self.get_response(request)
97
+
98
+ # Get payment header (Django uses HTTP_X_PAYMENT format)
99
+ payment_header = request.META.get("HTTP_X_PAYMENT")
100
+
101
+ if not payment_header:
102
+ response_body = create_402_response(
103
+ amount_usd=required_amount,
104
+ config=self._config,
105
+ )
106
+ response = JsonResponse(response_body, status=402)
107
+ for key, value in create_402_headers().items():
108
+ response[key] = value
109
+ return response
110
+
111
+ try:
112
+ result = self._client.process_payment(
113
+ x_payment_header=payment_header,
114
+ expected_amount_usd=required_amount,
115
+ )
116
+ # Store result on request for view access
117
+ request.payment_result = result # type: ignore
118
+ return self.get_response(request)
119
+
120
+ except X402Error as e:
121
+ response = JsonResponse(e.to_dict(), status=402)
122
+ for key, value in create_402_headers().items():
123
+ response[key] = value
124
+ return response
125
+
126
+
127
+ def django_require_payment(
128
+ amount_usd: Union[Decimal, float, str],
129
+ config: Optional[X402Config] = None,
130
+ message: Optional[str] = None,
131
+ ) -> Callable[[F], F]:
132
+ """
133
+ Decorator that requires payment for a Django view.
134
+
135
+ Args:
136
+ amount_usd: Required payment amount in USD
137
+ config: X402Config (uses Django settings if not provided)
138
+ message: Custom message for 402 response
139
+
140
+ Example:
141
+ >>> from uvd_x402_sdk.integrations import django_require_payment
142
+ >>>
143
+ >>> @django_require_payment(amount_usd="1.00")
144
+ >>> def my_view(request):
145
+ ... # Access payment result via request.payment_result
146
+ ... return JsonResponse({"payer": request.payment_result.payer_address})
147
+ """
148
+ required_amount = Decimal(str(amount_usd))
149
+ _config = config or _get_config_from_settings()
150
+ _client = X402Client(config=_config)
151
+
152
+ def decorator(func: F) -> F:
153
+ @wraps(func)
154
+ def wrapper(request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
155
+ # Get payment header
156
+ payment_header = request.META.get("HTTP_X_PAYMENT")
157
+
158
+ if not payment_header:
159
+ response_body = create_402_response(
160
+ amount_usd=required_amount,
161
+ config=_config,
162
+ message=message,
163
+ )
164
+ response = JsonResponse(response_body, status=402)
165
+ for key, value in create_402_headers().items():
166
+ response[key] = value
167
+ return response
168
+
169
+ try:
170
+ result = _client.process_payment(
171
+ x_payment_header=payment_header,
172
+ expected_amount_usd=required_amount,
173
+ )
174
+ # Store result on request
175
+ request.payment_result = result # type: ignore
176
+ return func(request, *args, **kwargs)
177
+
178
+ except X402Error as e:
179
+ response = JsonResponse(e.to_dict(), status=402)
180
+ for key, value in create_402_headers().items():
181
+ response[key] = value
182
+ return response
183
+
184
+ return wrapper # type: ignore
185
+
186
+ return decorator
187
+
188
+
189
+ class X402PaymentView:
190
+ """
191
+ Mixin for Django class-based views requiring payment.
192
+
193
+ Example:
194
+ >>> from django.views import View
195
+ >>> from uvd_x402_sdk.integrations.django_integration import X402PaymentView
196
+ >>>
197
+ >>> class PremiumAPIView(X402PaymentView, View):
198
+ ... x402_amount = Decimal("5.00")
199
+ ...
200
+ ... def get(self, request):
201
+ ... return JsonResponse({"payer": request.payment_result.payer_address})
202
+ """
203
+
204
+ x402_amount: Decimal = Decimal("1.00")
205
+ x402_message: Optional[str] = None
206
+ x402_config: Optional[X402Config] = None
207
+
208
+ def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
209
+ config = self.x402_config or _get_config_from_settings()
210
+ client = X402Client(config=config)
211
+
212
+ payment_header = request.META.get("HTTP_X_PAYMENT")
213
+
214
+ if not payment_header:
215
+ response_body = create_402_response(
216
+ amount_usd=self.x402_amount,
217
+ config=config,
218
+ message=self.x402_message,
219
+ )
220
+ response = JsonResponse(response_body, status=402)
221
+ for key, value in create_402_headers().items():
222
+ response[key] = value
223
+ return response
224
+
225
+ try:
226
+ result = client.process_payment(
227
+ x_payment_header=payment_header,
228
+ expected_amount_usd=self.x402_amount,
229
+ )
230
+ request.payment_result = result # type: ignore
231
+ return super().dispatch(request, *args, **kwargs) # type: ignore
232
+
233
+ except X402Error as e:
234
+ response = JsonResponse(e.to_dict(), status=402)
235
+ for key, value in create_402_headers().items():
236
+ response[key] = value
237
+ return response