16
16
from fastapi import FastAPI
17
17
from fastapi .encoders import jsonable_encoder
18
18
from httpx import URL , HTTPStatusError
19
+ from models_library .api_schemas_payments .errors import PaymentServiceUnavailableError
19
20
from models_library .api_schemas_webserver .wallets import PaymentID , PaymentMethodID
20
21
from pydantic import ValidationError , parse_raw_as
21
- from pydantic .errors import PydanticErrorMixin
22
22
from servicelib .fastapi .app_state import SingletonInAppStateMixin
23
23
from servicelib .fastapi .http_client import (
24
24
AttachLifespanMixin ,
33
33
from tenacity .retry import retry_if_exception_type
34
34
from tenacity .wait import wait_exponential
35
35
36
- from ..core .errors import PaymentsGatewayNotReadyError
36
+ from .._constants import MSG_GATEWAY_UNAVAILABLE_ERROR , PAG
37
+ from ..core .errors import BasePaymentsGatewayError , PaymentsGatewayNotReadyError
37
38
from ..core .settings import ApplicationSettings
38
39
from ..models .payments_gateway import (
39
40
BatchGetPaymentMethods ,
@@ -57,13 +58,13 @@ def _parse_raw_as_or_none(cls: type, text: str | None):
57
58
return None
58
59
59
60
60
- class PaymentsGatewayError ( PydanticErrorMixin , ValueError ):
61
+ class PaymentsGatewayApiError ( BasePaymentsGatewayError ):
61
62
msg_template = "{operation_id} error {status_code}: {reason}"
62
63
63
64
@classmethod
64
65
def from_http_status_error (
65
66
cls , err : HTTPStatusError , operation_id : str
66
- ) -> "PaymentsGatewayError " :
67
+ ) -> "PaymentsGatewayApiError " :
67
68
return cls (
68
69
operation_id = f"PaymentsGatewayApi.{ operation_id } " ,
69
70
reason = f"{ err } " ,
@@ -86,22 +87,37 @@ def get_detailed_message(self) -> str:
86
87
87
88
88
89
@contextlib .contextmanager
89
- def _raise_as_payments_gateway_error (operation_id : str ):
90
+ def _reraise_as_service_errors_context (operation_id : str ):
90
91
try :
91
92
yield
92
93
93
- except HTTPStatusError as err :
94
- error = PaymentsGatewayError .from_http_status_error (
94
+ except httpx .RequestError as err :
95
+ _logger .exception ("%s: request error" , PAG )
96
+ raise PaymentServiceUnavailableError (
97
+ human_reason = MSG_GATEWAY_UNAVAILABLE_ERROR
98
+ ) from err
99
+
100
+ except httpx .HTTPStatusError as err :
101
+ error = PaymentsGatewayApiError .from_http_status_error (
95
102
err , operation_id = operation_id
96
103
)
97
- _logger .warning (error .get_detailed_message ())
98
- raise error from err
104
+
105
+ if err .response .is_client_error :
106
+ _logger .warning (error .get_detailed_message ())
107
+ raise error from err
108
+
109
+ if err .response .is_server_error :
110
+ # 5XX in server -> turn into unavailable
111
+ _logger .exception (error .get_detailed_message ())
112
+ raise PaymentServiceUnavailableError (
113
+ human_reason = MSG_GATEWAY_UNAVAILABLE_ERROR
114
+ ) from err
99
115
100
116
101
- def _handle_status_errors (coro : Callable ):
117
+ def _handle_httpx_errors (coro : Callable ):
102
118
@functools .wraps (coro )
103
119
async def _wrapper (self , * args , ** kwargs ):
104
- with _raise_as_payments_gateway_error (operation_id = coro .__name__ ):
120
+ with _reraise_as_service_errors_context (operation_id = coro .__name__ ):
105
121
return await coro (self , * args , ** kwargs )
106
122
107
123
return _wrapper
@@ -125,7 +141,7 @@ class PaymentsGatewayApi(
125
141
# api: one-time-payment workflow
126
142
#
127
143
128
- @_handle_status_errors
144
+ @_handle_httpx_errors
129
145
async def init_payment (self , payment : InitPayment ) -> PaymentInitiated :
130
146
response = await self .client .post (
131
147
"/init" ,
@@ -137,7 +153,7 @@ async def init_payment(self, payment: InitPayment) -> PaymentInitiated:
137
153
def get_form_payment_url (self , id_ : PaymentID ) -> URL :
138
154
return self .client .base_url .copy_with (path = "/pay" , params = {"id" : f"{ id_ } " })
139
155
140
- @_handle_status_errors
156
+ @_handle_httpx_errors
141
157
async def cancel_payment (
142
158
self , payment_initiated : PaymentInitiated
143
159
) -> PaymentCancelled :
@@ -152,7 +168,7 @@ async def cancel_payment(
152
168
# api: payment method workflows
153
169
#
154
170
155
- @_handle_status_errors
171
+ @_handle_httpx_errors
156
172
async def init_payment_method (
157
173
self ,
158
174
payment_method : InitPaymentMethod ,
@@ -171,7 +187,7 @@ def get_form_payment_method_url(self, id_: PaymentMethodID) -> URL:
171
187
172
188
# CRUD
173
189
174
- @_handle_status_errors
190
+ @_handle_httpx_errors
175
191
async def get_many_payment_methods (
176
192
self , ids_ : list [PaymentMethodID ]
177
193
) -> list [GetPaymentMethod ]:
@@ -184,18 +200,18 @@ async def get_many_payment_methods(
184
200
response .raise_for_status ()
185
201
return PaymentMethodsBatch .parse_obj (response .json ()).items
186
202
187
- @_handle_status_errors
203
+ @_handle_httpx_errors
188
204
async def get_payment_method (self , id_ : PaymentMethodID ) -> GetPaymentMethod :
189
205
response = await self .client .get (f"/payment-methods/{ id_ } " )
190
206
response .raise_for_status ()
191
207
return GetPaymentMethod .parse_obj (response .json ())
192
208
193
- @_handle_status_errors
209
+ @_handle_httpx_errors
194
210
async def delete_payment_method (self , id_ : PaymentMethodID ) -> None :
195
211
response = await self .client .delete (f"/payment-methods/{ id_ } " )
196
212
response .raise_for_status ()
197
213
198
- @_handle_status_errors
214
+ @_handle_httpx_errors
199
215
async def pay_with_payment_method (
200
216
self , id_ : PaymentMethodID , payment : InitPayment
201
217
) -> AckPaymentWithPaymentMethod :
0 commit comments