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 BaseHttpApi
24
24
from servicelib .fastapi .httpx_utils import to_curl_command
29
29
from tenacity .retry import retry_if_exception_type
30
30
from tenacity .wait import wait_exponential
31
31
32
- from ..core .errors import PaymentsGatewayNotReadyError
32
+ from .._constants import MSG_GATEWAY_UNAVAILABLE_ERROR , PAG
33
+ from ..core .errors import BasePaymentsGatewayError , PaymentsGatewayNotReadyError
33
34
from ..core .settings import ApplicationSettings
34
35
from ..models .payments_gateway import (
35
36
BatchGetPaymentMethods ,
@@ -53,13 +54,13 @@ def _parse_raw_as_or_none(cls: type, text: str | None):
53
54
return None
54
55
55
56
56
- class PaymentsGatewayError ( PydanticErrorMixin , ValueError ):
57
+ class PaymentsGatewayApiError ( BasePaymentsGatewayError ):
57
58
msg_template = "{operation_id} error {status_code}: {reason}"
58
59
59
60
@classmethod
60
61
def from_http_status_error (
61
62
cls , err : HTTPStatusError , operation_id : str
62
- ) -> "PaymentsGatewayError " :
63
+ ) -> "PaymentsGatewayApiError " :
63
64
return cls (
64
65
operation_id = f"PaymentsGatewayApi.{ operation_id } " ,
65
66
reason = f"{ err } " ,
@@ -82,22 +83,37 @@ def get_detailed_message(self) -> str:
82
83
83
84
84
85
@contextlib .contextmanager
85
- def _raise_as_payments_gateway_error (operation_id : str ):
86
+ def _reraise_as_service_errors_context (operation_id : str ):
86
87
try :
87
88
yield
88
89
89
- except HTTPStatusError as err :
90
- error = PaymentsGatewayError .from_http_status_error (
90
+ except httpx .RequestError as err :
91
+ _logger .exception ("%s: request error" , PAG )
92
+ raise PaymentServiceUnavailableError (
93
+ human_reason = MSG_GATEWAY_UNAVAILABLE_ERROR
94
+ ) from err
95
+
96
+ except httpx .HTTPStatusError as err :
97
+ error = PaymentsGatewayApiError .from_http_status_error (
91
98
err , operation_id = operation_id
92
99
)
93
- _logger .warning (error .get_detailed_message ())
94
- raise error from err
100
+
101
+ if err .response .is_client_error :
102
+ _logger .warning (error .get_detailed_message ())
103
+ raise error from err
104
+
105
+ if err .response .is_server_error :
106
+ # 5XX in server -> turn into unavailable
107
+ _logger .exception (error .get_detailed_message ())
108
+ raise PaymentServiceUnavailableError (
109
+ human_reason = MSG_GATEWAY_UNAVAILABLE_ERROR
110
+ ) from err
95
111
96
112
97
- def _handle_status_errors (coro : Callable ):
113
+ def _handle_httpx_errors (coro : Callable ):
98
114
@functools .wraps (coro )
99
115
async def _wrapper (self , * args , ** kwargs ):
100
- with _raise_as_payments_gateway_error (operation_id = coro .__name__ ):
116
+ with _reraise_as_service_errors_context (operation_id = coro .__name__ ):
101
117
return await coro (self , * args , ** kwargs )
102
118
103
119
return _wrapper
@@ -119,7 +135,7 @@ class PaymentsGatewayApi(BaseHttpApi, SingletonInAppStateMixin):
119
135
# api: one-time-payment workflow
120
136
#
121
137
122
- @_handle_status_errors
138
+ @_handle_httpx_errors
123
139
async def init_payment (self , payment : InitPayment ) -> PaymentInitiated :
124
140
response = await self .client .post (
125
141
"/init" ,
@@ -131,7 +147,7 @@ async def init_payment(self, payment: InitPayment) -> PaymentInitiated:
131
147
def get_form_payment_url (self , id_ : PaymentID ) -> URL :
132
148
return self .client .base_url .copy_with (path = "/pay" , params = {"id" : f"{ id_ } " })
133
149
134
- @_handle_status_errors
150
+ @_handle_httpx_errors
135
151
async def cancel_payment (
136
152
self , payment_initiated : PaymentInitiated
137
153
) -> PaymentCancelled :
@@ -146,7 +162,7 @@ async def cancel_payment(
146
162
# api: payment method workflows
147
163
#
148
164
149
- @_handle_status_errors
165
+ @_handle_httpx_errors
150
166
async def init_payment_method (
151
167
self ,
152
168
payment_method : InitPaymentMethod ,
@@ -165,7 +181,7 @@ def get_form_payment_method_url(self, id_: PaymentMethodID) -> URL:
165
181
166
182
# CRUD
167
183
168
- @_handle_status_errors
184
+ @_handle_httpx_errors
169
185
async def get_many_payment_methods (
170
186
self , ids_ : list [PaymentMethodID ]
171
187
) -> list [GetPaymentMethod ]:
@@ -178,18 +194,18 @@ async def get_many_payment_methods(
178
194
response .raise_for_status ()
179
195
return PaymentMethodsBatch .parse_obj (response .json ()).items
180
196
181
- @_handle_status_errors
197
+ @_handle_httpx_errors
182
198
async def get_payment_method (self , id_ : PaymentMethodID ) -> GetPaymentMethod :
183
199
response = await self .client .get (f"/payment-methods/{ id_ } " )
184
200
response .raise_for_status ()
185
201
return GetPaymentMethod .parse_obj (response .json ())
186
202
187
- @_handle_status_errors
203
+ @_handle_httpx_errors
188
204
async def delete_payment_method (self , id_ : PaymentMethodID ) -> None :
189
205
response = await self .client .delete (f"/payment-methods/{ id_ } " )
190
206
response .raise_for_status ()
191
207
192
- @_handle_status_errors
208
+ @_handle_httpx_errors
193
209
async def pay_with_payment_method (
194
210
self , id_ : PaymentMethodID , payment : InitPayment
195
211
) -> AckPaymentWithPaymentMethod :
0 commit comments