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.
- uvd_x402_sdk/__init__.py +185 -169
- uvd_x402_sdk/client.py +527 -527
- uvd_x402_sdk/config.py +248 -248
- uvd_x402_sdk/decorators.py +325 -325
- uvd_x402_sdk/exceptions.py +254 -254
- uvd_x402_sdk/integrations/__init__.py +74 -74
- uvd_x402_sdk/integrations/django_integration.py +237 -237
- uvd_x402_sdk/integrations/fastapi_integration.py +330 -330
- uvd_x402_sdk/integrations/flask_integration.py +259 -259
- uvd_x402_sdk/integrations/lambda_integration.py +320 -320
- uvd_x402_sdk/models.py +397 -397
- uvd_x402_sdk/networks/__init__.py +80 -54
- uvd_x402_sdk/networks/base.py +498 -347
- uvd_x402_sdk/networks/evm.py +356 -215
- uvd_x402_sdk/networks/near.py +397 -397
- uvd_x402_sdk/networks/solana.py +282 -282
- uvd_x402_sdk/networks/stellar.py +129 -129
- uvd_x402_sdk/response.py +439 -439
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.3.0.dist-info}/LICENSE +21 -21
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.3.0.dist-info}/METADATA +927 -778
- uvd_x402_sdk-0.3.0.dist-info/RECORD +23 -0
- uvd_x402_sdk-0.2.2.dist-info/RECORD +0 -23
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.3.0.dist-info}/WHEEL +0 -0
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,259 +1,259 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Flask integration for x402 payments.
|
|
3
|
-
|
|
4
|
-
Provides:
|
|
5
|
-
- FlaskX402: Extension for initializing x402 with Flask apps
|
|
6
|
-
- flask_require_payment: Decorator for protecting routes
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from decimal import Decimal
|
|
10
|
-
from functools import wraps
|
|
11
|
-
from typing import Any, Callable, Optional, TypeVar, Union
|
|
12
|
-
|
|
13
|
-
try:
|
|
14
|
-
from flask import Flask, request, jsonify, make_response, g
|
|
15
|
-
except ImportError:
|
|
16
|
-
raise ImportError(
|
|
17
|
-
"Flask is required for Flask integration. "
|
|
18
|
-
"Install with: pip install uvd-x402-sdk[flask]"
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
from uvd_x402_sdk.client import X402Client
|
|
22
|
-
from uvd_x402_sdk.config import X402Config
|
|
23
|
-
from uvd_x402_sdk.exceptions import X402Error
|
|
24
|
-
from uvd_x402_sdk.response import create_402_response, create_402_headers
|
|
25
|
-
|
|
26
|
-
F = TypeVar("F", bound=Callable[..., Any])
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class FlaskX402:
|
|
30
|
-
"""
|
|
31
|
-
Flask extension for x402 payment integration.
|
|
32
|
-
|
|
33
|
-
Example:
|
|
34
|
-
>>> from flask import Flask
|
|
35
|
-
>>> from uvd_x402_sdk.integrations import FlaskX402
|
|
36
|
-
>>>
|
|
37
|
-
>>> app = Flask(__name__)
|
|
38
|
-
>>> x402 = FlaskX402(app, recipient_address="0xYourWallet...")
|
|
39
|
-
>>>
|
|
40
|
-
>>> @app.route("/premium")
|
|
41
|
-
>>> @x402.require_payment(amount_usd=Decimal("1.00"))
|
|
42
|
-
>>> def premium():
|
|
43
|
-
... return {"message": f"Paid by {g.payment_result.payer_address}"}
|
|
44
|
-
"""
|
|
45
|
-
|
|
46
|
-
def __init__(
|
|
47
|
-
self,
|
|
48
|
-
app: Optional[Flask] = None,
|
|
49
|
-
config: Optional[X402Config] = None,
|
|
50
|
-
recipient_address: Optional[str] = None,
|
|
51
|
-
**kwargs: Any,
|
|
52
|
-
) -> None:
|
|
53
|
-
"""
|
|
54
|
-
Initialize Flask x402 extension.
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
app: Flask application (optional, can use init_app later)
|
|
58
|
-
config: X402Config object
|
|
59
|
-
recipient_address: Default recipient for EVM chains
|
|
60
|
-
**kwargs: Additional config parameters
|
|
61
|
-
"""
|
|
62
|
-
self.app = app
|
|
63
|
-
self._config = config
|
|
64
|
-
self._config_kwargs = {"recipient_evm": recipient_address, **kwargs}
|
|
65
|
-
self._client: Optional[X402Client] = None
|
|
66
|
-
|
|
67
|
-
if app is not None:
|
|
68
|
-
self.init_app(app)
|
|
69
|
-
|
|
70
|
-
def init_app(self, app: Flask) -> None:
|
|
71
|
-
"""
|
|
72
|
-
Initialize extension with Flask app.
|
|
73
|
-
|
|
74
|
-
Args:
|
|
75
|
-
app: Flask application
|
|
76
|
-
"""
|
|
77
|
-
self.app = app
|
|
78
|
-
|
|
79
|
-
# Get config from app config if not provided
|
|
80
|
-
if self._config is None:
|
|
81
|
-
self._config = X402Config(
|
|
82
|
-
facilitator_url=app.config.get(
|
|
83
|
-
"X402_FACILITATOR_URL",
|
|
84
|
-
"https://facilitator.ultravioletadao.xyz",
|
|
85
|
-
),
|
|
86
|
-
recipient_evm=app.config.get("X402_RECIPIENT_EVM", "")
|
|
87
|
-
or self._config_kwargs.get("recipient_evm", ""),
|
|
88
|
-
recipient_solana=app.config.get("X402_RECIPIENT_SOLANA", ""),
|
|
89
|
-
recipient_near=app.config.get("X402_RECIPIENT_NEAR", ""),
|
|
90
|
-
recipient_stellar=app.config.get("X402_RECIPIENT_STELLAR", ""),
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
self._client = X402Client(config=self._config)
|
|
94
|
-
|
|
95
|
-
# Store extension on app
|
|
96
|
-
if not hasattr(app, "extensions"):
|
|
97
|
-
app.extensions = {}
|
|
98
|
-
app.extensions["x402"] = self
|
|
99
|
-
|
|
100
|
-
@property
|
|
101
|
-
def client(self) -> X402Client:
|
|
102
|
-
"""Get the x402 client."""
|
|
103
|
-
if self._client is None:
|
|
104
|
-
raise RuntimeError("FlaskX402 not initialized. Call init_app() first.")
|
|
105
|
-
return self._client
|
|
106
|
-
|
|
107
|
-
@property
|
|
108
|
-
def config(self) -> X402Config:
|
|
109
|
-
"""Get the x402 config."""
|
|
110
|
-
if self._config is None:
|
|
111
|
-
raise RuntimeError("FlaskX402 not initialized. Call init_app() first.")
|
|
112
|
-
return self._config
|
|
113
|
-
|
|
114
|
-
def require_payment(
|
|
115
|
-
self,
|
|
116
|
-
amount_usd: Optional[Union[Decimal, float, str]] = None,
|
|
117
|
-
amount_callback: Optional[Callable[[], Decimal]] = None,
|
|
118
|
-
message: Optional[str] = None,
|
|
119
|
-
store_in_g: bool = True,
|
|
120
|
-
) -> Callable[[F], F]:
|
|
121
|
-
"""
|
|
122
|
-
Decorator that requires payment for a Flask route.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
amount_usd: Fixed payment amount in USD
|
|
126
|
-
amount_callback: Callable that returns dynamic amount
|
|
127
|
-
message: Custom message for 402 response
|
|
128
|
-
store_in_g: Store PaymentResult in flask.g.payment_result
|
|
129
|
-
|
|
130
|
-
Returns:
|
|
131
|
-
Decorated route function
|
|
132
|
-
|
|
133
|
-
Example:
|
|
134
|
-
>>> @app.route("/api/premium")
|
|
135
|
-
>>> @x402.require_payment(amount_usd="5.00")
|
|
136
|
-
>>> def premium_endpoint():
|
|
137
|
-
... return {"payer": g.payment_result.payer_address}
|
|
138
|
-
"""
|
|
139
|
-
|
|
140
|
-
def decorator(func: F) -> F:
|
|
141
|
-
@wraps(func)
|
|
142
|
-
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
143
|
-
# Get payment header
|
|
144
|
-
payment_header = request.headers.get("X-PAYMENT")
|
|
145
|
-
|
|
146
|
-
# Determine amount
|
|
147
|
-
if amount_callback:
|
|
148
|
-
required_amount = Decimal(str(amount_callback()))
|
|
149
|
-
elif amount_usd:
|
|
150
|
-
required_amount = Decimal(str(amount_usd))
|
|
151
|
-
else:
|
|
152
|
-
raise ValueError("Either amount_usd or amount_callback is required")
|
|
153
|
-
|
|
154
|
-
# No payment header - return 402
|
|
155
|
-
if not payment_header:
|
|
156
|
-
response_body = create_402_response(
|
|
157
|
-
amount_usd=required_amount,
|
|
158
|
-
config=self.config,
|
|
159
|
-
message=message,
|
|
160
|
-
)
|
|
161
|
-
response = make_response(jsonify(response_body), 402)
|
|
162
|
-
for key, value in create_402_headers().items():
|
|
163
|
-
response.headers[key] = value
|
|
164
|
-
return response
|
|
165
|
-
|
|
166
|
-
# Process payment
|
|
167
|
-
try:
|
|
168
|
-
result = self.client.process_payment(
|
|
169
|
-
x_payment_header=payment_header,
|
|
170
|
-
expected_amount_usd=required_amount,
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
# Store result in g
|
|
174
|
-
if store_in_g:
|
|
175
|
-
g.payment_result = result
|
|
176
|
-
|
|
177
|
-
return func(*args, **kwargs)
|
|
178
|
-
|
|
179
|
-
except X402Error as e:
|
|
180
|
-
response_body = create_402_response(
|
|
181
|
-
amount_usd=required_amount,
|
|
182
|
-
config=self.config,
|
|
183
|
-
message=message,
|
|
184
|
-
)
|
|
185
|
-
response_body["error"] = e.message
|
|
186
|
-
response_body["details"] = e.details
|
|
187
|
-
response = make_response(jsonify(response_body), 402)
|
|
188
|
-
for key, value in create_402_headers().items():
|
|
189
|
-
response.headers[key] = value
|
|
190
|
-
return response
|
|
191
|
-
|
|
192
|
-
return wrapper # type: ignore
|
|
193
|
-
|
|
194
|
-
return decorator
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def flask_require_payment(
|
|
198
|
-
amount_usd: Union[Decimal, float, str],
|
|
199
|
-
message: Optional[str] = None,
|
|
200
|
-
) -> Callable[[F], F]:
|
|
201
|
-
"""
|
|
202
|
-
Standalone decorator using global Flask x402 extension.
|
|
203
|
-
|
|
204
|
-
This decorator uses the x402 extension stored in current_app.extensions.
|
|
205
|
-
|
|
206
|
-
Example:
|
|
207
|
-
>>> @app.route("/api/resource")
|
|
208
|
-
>>> @flask_require_payment(amount_usd="1.00")
|
|
209
|
-
>>> def resource():
|
|
210
|
-
... return {"success": True}
|
|
211
|
-
"""
|
|
212
|
-
|
|
213
|
-
def decorator(func: F) -> F:
|
|
214
|
-
@wraps(func)
|
|
215
|
-
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
216
|
-
from flask import current_app, g
|
|
217
|
-
|
|
218
|
-
# Get extension from app
|
|
219
|
-
if "x402" not in current_app.extensions:
|
|
220
|
-
raise RuntimeError(
|
|
221
|
-
"FlaskX402 not initialized. "
|
|
222
|
-
"Add FlaskX402(app, ...) to your app setup."
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
x402_ext: FlaskX402 = current_app.extensions["x402"]
|
|
226
|
-
required_amount = Decimal(str(amount_usd))
|
|
227
|
-
|
|
228
|
-
# Get payment header
|
|
229
|
-
payment_header = request.headers.get("X-PAYMENT")
|
|
230
|
-
|
|
231
|
-
# No payment - return 402
|
|
232
|
-
if not payment_header:
|
|
233
|
-
response_body = create_402_response(
|
|
234
|
-
amount_usd=required_amount,
|
|
235
|
-
config=x402_ext.config,
|
|
236
|
-
message=message,
|
|
237
|
-
)
|
|
238
|
-
response = make_response(jsonify(response_body), 402)
|
|
239
|
-
for key, value in create_402_headers().items():
|
|
240
|
-
response.headers[key] = value
|
|
241
|
-
return response
|
|
242
|
-
|
|
243
|
-
# Process payment
|
|
244
|
-
try:
|
|
245
|
-
result = x402_ext.client.process_payment(
|
|
246
|
-
x_payment_header=payment_header,
|
|
247
|
-
expected_amount_usd=required_amount,
|
|
248
|
-
)
|
|
249
|
-
g.payment_result = result
|
|
250
|
-
return func(*args, **kwargs)
|
|
251
|
-
|
|
252
|
-
except X402Error as e:
|
|
253
|
-
response_body = e.to_dict()
|
|
254
|
-
response = make_response(jsonify(response_body), 402)
|
|
255
|
-
return response
|
|
256
|
-
|
|
257
|
-
return wrapper # type: ignore
|
|
258
|
-
|
|
259
|
-
return decorator
|
|
1
|
+
"""
|
|
2
|
+
Flask integration for x402 payments.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
- FlaskX402: Extension for initializing x402 with Flask apps
|
|
6
|
+
- flask_require_payment: Decorator for protecting routes
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from decimal import Decimal
|
|
10
|
+
from functools import wraps
|
|
11
|
+
from typing import Any, Callable, Optional, TypeVar, Union
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from flask import Flask, request, jsonify, make_response, g
|
|
15
|
+
except ImportError:
|
|
16
|
+
raise ImportError(
|
|
17
|
+
"Flask is required for Flask integration. "
|
|
18
|
+
"Install with: pip install uvd-x402-sdk[flask]"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from uvd_x402_sdk.client import X402Client
|
|
22
|
+
from uvd_x402_sdk.config import X402Config
|
|
23
|
+
from uvd_x402_sdk.exceptions import X402Error
|
|
24
|
+
from uvd_x402_sdk.response import create_402_response, create_402_headers
|
|
25
|
+
|
|
26
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FlaskX402:
|
|
30
|
+
"""
|
|
31
|
+
Flask extension for x402 payment integration.
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> from flask import Flask
|
|
35
|
+
>>> from uvd_x402_sdk.integrations import FlaskX402
|
|
36
|
+
>>>
|
|
37
|
+
>>> app = Flask(__name__)
|
|
38
|
+
>>> x402 = FlaskX402(app, recipient_address="0xYourWallet...")
|
|
39
|
+
>>>
|
|
40
|
+
>>> @app.route("/premium")
|
|
41
|
+
>>> @x402.require_payment(amount_usd=Decimal("1.00"))
|
|
42
|
+
>>> def premium():
|
|
43
|
+
... return {"message": f"Paid by {g.payment_result.payer_address}"}
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
app: Optional[Flask] = None,
|
|
49
|
+
config: Optional[X402Config] = None,
|
|
50
|
+
recipient_address: Optional[str] = None,
|
|
51
|
+
**kwargs: Any,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Initialize Flask x402 extension.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
app: Flask application (optional, can use init_app later)
|
|
58
|
+
config: X402Config object
|
|
59
|
+
recipient_address: Default recipient for EVM chains
|
|
60
|
+
**kwargs: Additional config parameters
|
|
61
|
+
"""
|
|
62
|
+
self.app = app
|
|
63
|
+
self._config = config
|
|
64
|
+
self._config_kwargs = {"recipient_evm": recipient_address, **kwargs}
|
|
65
|
+
self._client: Optional[X402Client] = None
|
|
66
|
+
|
|
67
|
+
if app is not None:
|
|
68
|
+
self.init_app(app)
|
|
69
|
+
|
|
70
|
+
def init_app(self, app: Flask) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Initialize extension with Flask app.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
app: Flask application
|
|
76
|
+
"""
|
|
77
|
+
self.app = app
|
|
78
|
+
|
|
79
|
+
# Get config from app config if not provided
|
|
80
|
+
if self._config is None:
|
|
81
|
+
self._config = X402Config(
|
|
82
|
+
facilitator_url=app.config.get(
|
|
83
|
+
"X402_FACILITATOR_URL",
|
|
84
|
+
"https://facilitator.ultravioletadao.xyz",
|
|
85
|
+
),
|
|
86
|
+
recipient_evm=app.config.get("X402_RECIPIENT_EVM", "")
|
|
87
|
+
or self._config_kwargs.get("recipient_evm", ""),
|
|
88
|
+
recipient_solana=app.config.get("X402_RECIPIENT_SOLANA", ""),
|
|
89
|
+
recipient_near=app.config.get("X402_RECIPIENT_NEAR", ""),
|
|
90
|
+
recipient_stellar=app.config.get("X402_RECIPIENT_STELLAR", ""),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
self._client = X402Client(config=self._config)
|
|
94
|
+
|
|
95
|
+
# Store extension on app
|
|
96
|
+
if not hasattr(app, "extensions"):
|
|
97
|
+
app.extensions = {}
|
|
98
|
+
app.extensions["x402"] = self
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def client(self) -> X402Client:
|
|
102
|
+
"""Get the x402 client."""
|
|
103
|
+
if self._client is None:
|
|
104
|
+
raise RuntimeError("FlaskX402 not initialized. Call init_app() first.")
|
|
105
|
+
return self._client
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def config(self) -> X402Config:
|
|
109
|
+
"""Get the x402 config."""
|
|
110
|
+
if self._config is None:
|
|
111
|
+
raise RuntimeError("FlaskX402 not initialized. Call init_app() first.")
|
|
112
|
+
return self._config
|
|
113
|
+
|
|
114
|
+
def require_payment(
|
|
115
|
+
self,
|
|
116
|
+
amount_usd: Optional[Union[Decimal, float, str]] = None,
|
|
117
|
+
amount_callback: Optional[Callable[[], Decimal]] = None,
|
|
118
|
+
message: Optional[str] = None,
|
|
119
|
+
store_in_g: bool = True,
|
|
120
|
+
) -> Callable[[F], F]:
|
|
121
|
+
"""
|
|
122
|
+
Decorator that requires payment for a Flask route.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
amount_usd: Fixed payment amount in USD
|
|
126
|
+
amount_callback: Callable that returns dynamic amount
|
|
127
|
+
message: Custom message for 402 response
|
|
128
|
+
store_in_g: Store PaymentResult in flask.g.payment_result
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Decorated route function
|
|
132
|
+
|
|
133
|
+
Example:
|
|
134
|
+
>>> @app.route("/api/premium")
|
|
135
|
+
>>> @x402.require_payment(amount_usd="5.00")
|
|
136
|
+
>>> def premium_endpoint():
|
|
137
|
+
... return {"payer": g.payment_result.payer_address}
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def decorator(func: F) -> F:
|
|
141
|
+
@wraps(func)
|
|
142
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
143
|
+
# Get payment header
|
|
144
|
+
payment_header = request.headers.get("X-PAYMENT")
|
|
145
|
+
|
|
146
|
+
# Determine amount
|
|
147
|
+
if amount_callback:
|
|
148
|
+
required_amount = Decimal(str(amount_callback()))
|
|
149
|
+
elif amount_usd:
|
|
150
|
+
required_amount = Decimal(str(amount_usd))
|
|
151
|
+
else:
|
|
152
|
+
raise ValueError("Either amount_usd or amount_callback is required")
|
|
153
|
+
|
|
154
|
+
# No payment header - return 402
|
|
155
|
+
if not payment_header:
|
|
156
|
+
response_body = create_402_response(
|
|
157
|
+
amount_usd=required_amount,
|
|
158
|
+
config=self.config,
|
|
159
|
+
message=message,
|
|
160
|
+
)
|
|
161
|
+
response = make_response(jsonify(response_body), 402)
|
|
162
|
+
for key, value in create_402_headers().items():
|
|
163
|
+
response.headers[key] = value
|
|
164
|
+
return response
|
|
165
|
+
|
|
166
|
+
# Process payment
|
|
167
|
+
try:
|
|
168
|
+
result = self.client.process_payment(
|
|
169
|
+
x_payment_header=payment_header,
|
|
170
|
+
expected_amount_usd=required_amount,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Store result in g
|
|
174
|
+
if store_in_g:
|
|
175
|
+
g.payment_result = result
|
|
176
|
+
|
|
177
|
+
return func(*args, **kwargs)
|
|
178
|
+
|
|
179
|
+
except X402Error as e:
|
|
180
|
+
response_body = create_402_response(
|
|
181
|
+
amount_usd=required_amount,
|
|
182
|
+
config=self.config,
|
|
183
|
+
message=message,
|
|
184
|
+
)
|
|
185
|
+
response_body["error"] = e.message
|
|
186
|
+
response_body["details"] = e.details
|
|
187
|
+
response = make_response(jsonify(response_body), 402)
|
|
188
|
+
for key, value in create_402_headers().items():
|
|
189
|
+
response.headers[key] = value
|
|
190
|
+
return response
|
|
191
|
+
|
|
192
|
+
return wrapper # type: ignore
|
|
193
|
+
|
|
194
|
+
return decorator
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def flask_require_payment(
|
|
198
|
+
amount_usd: Union[Decimal, float, str],
|
|
199
|
+
message: Optional[str] = None,
|
|
200
|
+
) -> Callable[[F], F]:
|
|
201
|
+
"""
|
|
202
|
+
Standalone decorator using global Flask x402 extension.
|
|
203
|
+
|
|
204
|
+
This decorator uses the x402 extension stored in current_app.extensions.
|
|
205
|
+
|
|
206
|
+
Example:
|
|
207
|
+
>>> @app.route("/api/resource")
|
|
208
|
+
>>> @flask_require_payment(amount_usd="1.00")
|
|
209
|
+
>>> def resource():
|
|
210
|
+
... return {"success": True}
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
def decorator(func: F) -> F:
|
|
214
|
+
@wraps(func)
|
|
215
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
216
|
+
from flask import current_app, g
|
|
217
|
+
|
|
218
|
+
# Get extension from app
|
|
219
|
+
if "x402" not in current_app.extensions:
|
|
220
|
+
raise RuntimeError(
|
|
221
|
+
"FlaskX402 not initialized. "
|
|
222
|
+
"Add FlaskX402(app, ...) to your app setup."
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
x402_ext: FlaskX402 = current_app.extensions["x402"]
|
|
226
|
+
required_amount = Decimal(str(amount_usd))
|
|
227
|
+
|
|
228
|
+
# Get payment header
|
|
229
|
+
payment_header = request.headers.get("X-PAYMENT")
|
|
230
|
+
|
|
231
|
+
# No payment - return 402
|
|
232
|
+
if not payment_header:
|
|
233
|
+
response_body = create_402_response(
|
|
234
|
+
amount_usd=required_amount,
|
|
235
|
+
config=x402_ext.config,
|
|
236
|
+
message=message,
|
|
237
|
+
)
|
|
238
|
+
response = make_response(jsonify(response_body), 402)
|
|
239
|
+
for key, value in create_402_headers().items():
|
|
240
|
+
response.headers[key] = value
|
|
241
|
+
return response
|
|
242
|
+
|
|
243
|
+
# Process payment
|
|
244
|
+
try:
|
|
245
|
+
result = x402_ext.client.process_payment(
|
|
246
|
+
x_payment_header=payment_header,
|
|
247
|
+
expected_amount_usd=required_amount,
|
|
248
|
+
)
|
|
249
|
+
g.payment_result = result
|
|
250
|
+
return func(*args, **kwargs)
|
|
251
|
+
|
|
252
|
+
except X402Error as e:
|
|
253
|
+
response_body = e.to_dict()
|
|
254
|
+
response = make_response(jsonify(response_body), 402)
|
|
255
|
+
return response
|
|
256
|
+
|
|
257
|
+
return wrapper # type: ignore
|
|
258
|
+
|
|
259
|
+
return decorator
|