1- import enum
21import json
32import logging
43import typing as t # noqa
@@ -79,6 +78,8 @@ def __init__(
7978 ),
8079 )
8180
81+ # --- Internal Checks & Actions during the payment flow -------------------
82+
8283 def get_checkout_start_data (
8384 self ,
8485 order_skel : SkeletonInstance ,
@@ -193,12 +194,7 @@ def check_payment_state(
193194 logger .info (f"Payment #{ idx } has a capture that is not completed ({ capture .status = !r} )" )
194195 continue # This payment is incomplete
195196
196- if capture .invoice_id != order_skel ["order_uid" ]:
197- logger .info (f"Payment #{ idx } has a capture that does not match the order_uid "
198- f"({ capture .invoice_id = } != { order_skel ["order_uid" ]= } )" )
199- continue
200-
201- if capture .invoice_id != order_skel ["order_uid" ]:
197+ if float (capture .amount .value ) != order_skel ["total" ]:
202198 logger .error (f"Payment #{ idx } has a fully captured payment, but amount does not match"
203199 f"({ capture .amount .value = } != { order_skel ["total" ]} )" )
204200 raise InvalidStateError (f"Payment #{ idx } has been captured with invalid amount" )
@@ -207,6 +203,8 @@ def check_payment_state(
207203
208204 return False , payment_results
209205
206+ # --- API Endpoints ------------------------------------------------------
207+
210208 @exposed
211209 def return_handler (self ):
212210 raise errors .NotImplemented ()
@@ -217,7 +215,7 @@ def return_handler(self):
217215 def webhook (self , * args , ** kwargs ):
218216 """Webhook for PayPal.
219217
220- Listens to all events, but handle payment-complete as backup currently only.
218+ Listens to all events, but handle PAYMENT.CAPTURE.COMPLETED as backup currently only.
221219 """
222220 try :
223221 payload = json .loads (current .request .get ().request .body )
@@ -227,6 +225,11 @@ def webhook(self, *args, **kwargs):
227225 logger .info (f"{ payload = } " )
228226 logger .info (f"headers={ dict (current .request .get ().request .headers )!r} " )
229227
228+ # TODO: PayPal has many large IP-Ranges
229+ # https://www.paypal.com/li/cshelp/article/what-are-the-internet-protocol-ip-addresses-for-paypal-server-endpoints-ts1056
230+ # Verifying the request source might be more safe, but since we use this webhook only as trigger
231+ # for an payment check (that load the state from the API-Server anyway),
232+ # this is not really a security issue.
230233 # ip = current.request.get().request.remote_addr
231234 # logger.info(f"{ip=}")
232235 # if ip not in IP_ADDRESS:
@@ -315,6 +318,8 @@ def capture_order(
315318 """
316319 Capture payment for the created order to complete the transaction.
317320
321+ Has to be called by the Frontend after the user has approved the payment.
322+
318323 @see https://developer.paypal.com/docs/api/orders/v2/#orders_capture
319324 """
320325 order_key = self .shop .api ._normalize_external_key (order_key , "order_key" )
@@ -347,13 +352,7 @@ def capture_order(
347352 })
348353
349354 @classmethod
350- def model_to_dict (cls , obj ) :
355+ def model_to_dict (cls , obj : t . Any ) -> t . Any :
351356 """Convert any nested PayPal model to dict representation"""
352357 obj = ApiHelper .json_serialize (obj , should_encode = False ) # Convert to dict first, then process recursively
353- if isinstance (obj , dict ):
354- return {k : cls .model_to_dict (v ) for k , v in obj .items ()}
355- elif isinstance (obj , list | tuple ):
356- return [cls .model_to_dict (v ) for v in obj ]
357- elif isinstance (obj , enum .Enum ):
358- return f"{ obj !r} "
359- return obj
358+ return super ().model_to_dict (obj )
0 commit comments