1616from fastapi import FastAPI
1717from fastapi .encoders import jsonable_encoder
1818from httpx import URL , HTTPStatusError
19+ from models_library .api_schemas_payments .errors import PaymentServiceUnavailableError
1920from models_library .api_schemas_webserver .wallets import PaymentID , PaymentMethodID
2021from pydantic import ValidationError , parse_raw_as
21- from pydantic .errors import PydanticErrorMixin
2222from servicelib .fastapi .app_state import SingletonInAppStateMixin
2323from servicelib .fastapi .http_client import BaseHttpApi
2424from servicelib .fastapi .httpx_utils import to_curl_command
2929from tenacity .retry import retry_if_exception_type
3030from tenacity .wait import wait_exponential
3131
32- from ..core .errors import PaymentsGatewayNotReadyError
32+ from .._constants import MSG_GATEWAY_UNAVAILABLE_ERROR , PAG
33+ from ..core .errors import BasePaymentsGatewayError , PaymentsGatewayNotReadyError
3334from ..core .settings import ApplicationSettings
3435from ..models .payments_gateway import (
3536 BatchGetPaymentMethods ,
@@ -53,13 +54,13 @@ def _parse_raw_as_or_none(cls: type, text: str | None):
5354 return None
5455
5556
56- class PaymentsGatewayError ( PydanticErrorMixin , ValueError ):
57+ class PaymentsGatewayApiError ( BasePaymentsGatewayError ):
5758 msg_template = "{operation_id} error {status_code}: {reason}"
5859
5960 @classmethod
6061 def from_http_status_error (
6162 cls , err : HTTPStatusError , operation_id : str
62- ) -> "PaymentsGatewayError " :
63+ ) -> "PaymentsGatewayApiError " :
6364 return cls (
6465 operation_id = f"PaymentsGatewayApi.{ operation_id } " ,
6566 reason = f"{ err } " ,
@@ -82,22 +83,37 @@ def get_detailed_message(self) -> str:
8283
8384
8485@contextlib .contextmanager
85- def _raise_as_payments_gateway_error (operation_id : str ):
86+ def _reraise_as_service_errors_context (operation_id : str ):
8687 try :
8788 yield
8889
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 (
9198 err , operation_id = operation_id
9299 )
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
95111
96112
97- def _handle_status_errors (coro : Callable ):
113+ def _handle_httpx_errors (coro : Callable ):
98114 @functools .wraps (coro )
99115 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__ ):
101117 return await coro (self , * args , ** kwargs )
102118
103119 return _wrapper
@@ -119,7 +135,7 @@ class PaymentsGatewayApi(BaseHttpApi, SingletonInAppStateMixin):
119135 # api: one-time-payment workflow
120136 #
121137
122- @_handle_status_errors
138+ @_handle_httpx_errors
123139 async def init_payment (self , payment : InitPayment ) -> PaymentInitiated :
124140 response = await self .client .post (
125141 "/init" ,
@@ -131,7 +147,7 @@ async def init_payment(self, payment: InitPayment) -> PaymentInitiated:
131147 def get_form_payment_url (self , id_ : PaymentID ) -> URL :
132148 return self .client .base_url .copy_with (path = "/pay" , params = {"id" : f"{ id_ } " })
133149
134- @_handle_status_errors
150+ @_handle_httpx_errors
135151 async def cancel_payment (
136152 self , payment_initiated : PaymentInitiated
137153 ) -> PaymentCancelled :
@@ -146,7 +162,7 @@ async def cancel_payment(
146162 # api: payment method workflows
147163 #
148164
149- @_handle_status_errors
165+ @_handle_httpx_errors
150166 async def init_payment_method (
151167 self ,
152168 payment_method : InitPaymentMethod ,
@@ -165,7 +181,7 @@ def get_form_payment_method_url(self, id_: PaymentMethodID) -> URL:
165181
166182 # CRUD
167183
168- @_handle_status_errors
184+ @_handle_httpx_errors
169185 async def get_many_payment_methods (
170186 self , ids_ : list [PaymentMethodID ]
171187 ) -> list [GetPaymentMethod ]:
@@ -178,18 +194,18 @@ async def get_many_payment_methods(
178194 response .raise_for_status ()
179195 return PaymentMethodsBatch .parse_obj (response .json ()).items
180196
181- @_handle_status_errors
197+ @_handle_httpx_errors
182198 async def get_payment_method (self , id_ : PaymentMethodID ) -> GetPaymentMethod :
183199 response = await self .client .get (f"/payment-methods/{ id_ } " )
184200 response .raise_for_status ()
185201 return GetPaymentMethod .parse_obj (response .json ())
186202
187- @_handle_status_errors
203+ @_handle_httpx_errors
188204 async def delete_payment_method (self , id_ : PaymentMethodID ) -> None :
189205 response = await self .client .delete (f"/payment-methods/{ id_ } " )
190206 response .raise_for_status ()
191207
192- @_handle_status_errors
208+ @_handle_httpx_errors
193209 async def pay_with_payment_method (
194210 self , id_ : PaymentMethodID , payment : InitPayment
195211 ) -> AckPaymentWithPaymentMethod :
0 commit comments